From 42e403a51da3539cc5062fde13884678dba55b5a Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 4 Jul 2019 13:28:51 +0200 Subject: Notify QQItem::mouseUngrabEvent() when an Event Handler steals grab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already took care of cases when the handler is a child of the Item, grab transitions involving Flickable etc.; but the bug is about a simpler case when the handler is in the parent of the item that has the grab, and steals from it. Amends 38a016c7b1337d83d77879f45b4a2e6fec11d049 Fixes: QTBUG-71218 Fixes: QTBUG-75025 Change-Id: Id1d6d370e0db75c59ec7dce4a8e545701c501827 Reviewed-by: Jan Arve Sæther --- src/quick/items/qquickevents.cpp | 10 +++-- .../data/dragParentOfMPTA.qml | 50 ++++++++++++++++++++++ .../tst_multipointtoucharea_interop.cpp | 43 +++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/dragParentOfMPTA.qml diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 28b217b7b3..8303c3fed1 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -913,10 +913,14 @@ void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber, b passiveGrabber->onGrabChanged(grabber, OverrideGrabPassive, this); } } - if (oldGrabberHandler) + if (oldGrabberHandler) { oldGrabberHandler->onGrabChanged(oldGrabberHandler, (grabber ? CancelGrabExclusive : UngrabExclusive), this); - else if (oldGrabberItem && pointerEvent()->asPointerTouchEvent()) - oldGrabberItem->touchUngrabEvent(); + } else if (oldGrabberItem) { + if (pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + else if (pointerEvent()->asPointerMouseEvent()) + oldGrabberItem->mouseUngrabEvent(); + } // touchUngrabEvent() can result in the grabber being set to null (MPTA does that, for example). // So set it again to ensure that final state is what we want. m_exclusiveGrabber = QPointer(grabber); diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/dragParentOfMPTA.qml b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/dragParentOfMPTA.qml new file mode 100644 index 0000000000..dc7e5f6411 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/data/dragParentOfMPTA.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 + +Item { + width: 640 + height: 480 + property alias touchpointPressed: tp1.pressed + + Rectangle { + color: tp1.pressed ? "lightsteelblue" : drag.active ? "tomato" : "wheat" + width: 180 + height: 180 + + DragHandler { id: drag } + + MultiPointTouchArea { + anchors.fill: parent + touchPoints: [ + TouchPoint { id: tp1 } + ] + } + } +} diff --git a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp index bf582b820b..cd18580ccf 100644 --- a/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp +++ b/tests/auto/quick/pointerhandlers/multipointtoucharea_interop/tst_multipointtoucharea_interop.cpp @@ -56,6 +56,7 @@ private slots: void touchDrag(); void touchesThenPinch(); void unloadHandlerWithPassiveGrab(); + void dragHandlerInParentStealingGrabFromItem(); private: void createView(QScopedPointer &window, const char *fileName); @@ -301,6 +302,48 @@ void tst_MptaInterop::unloadHandlerWithPassiveGrab() QTest::mouseRelease(window, Qt::LeftButton, 0, point); // QTBUG-73819: don't crash } +void tst_MptaInterop::dragHandlerInParentStealingGrabFromItem() // QTBUG-75025 +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QScopedPointer windowPtr; + createView(windowPtr, "dragParentOfMPTA.qml"); + QQuickView * window = windowPtr.data(); + auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(QQuickPointerDevice::genericMouseDevice()); + + QPointer handler = window->rootObject()->findChild(); + QVERIFY(handler); + QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild(); + QVERIFY(mpta); + + // In QTBUG-75025 there is a QQ Controls Button; the MPTA here stands in for that, + // simply as an Item that grabs the mouse. The bug has nothing to do with MPTA specifically. + + QPoint point(20, 20); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, point); + QCOMPARE(window->mouseGrabberItem(), mpta); + QCOMPARE(window->rootObject()->property("touchpointPressed").toBool(), true); + + // Start dragging + // DragHandler keeps monitoring, due to its passive grab, + // and eventually steals the exclusive grab from MPTA + int dragStoleGrab = 0; + for (int i = 0; i < 4; ++i) { + point += QPoint(dragThreshold / 2, 0); + QTest::mouseMove(window, point); + if (!dragStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == handler) + dragStoleGrab = i; + } + if (dragStoleGrab) + qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab); + QVERIFY(dragStoleGrab > 1); + QCOMPARE(handler->active(), true); + QCOMPARE(window->rootObject()->property("touchpointPressed").toBool(), false); + + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, point); + QCOMPARE(handler->active(), false); + QCOMPARE(window->rootObject()->property("touchpointPressed").toBool(), false); +} + QTEST_MAIN(tst_MptaInterop) #include "tst_multipointtoucharea_interop.moc" -- cgit v1.2.3 From 9b01e2e5d4b38533f02ba9ba907505e8c341cd0a Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 3 Jul 2019 17:17:53 +0200 Subject: TapHandler: wait until after tapped is emitted to reset point.position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't want it to hold its position indefinitely after the button is released. But in practice, reset() gets called again anyway in QQuickSinglePointHandler::handlePointerEventImpl(), _after_ handleEventPoint(), which means after tapped() is emitted. Having the point hold its position that much longer is convenient for applications and more consistent with the state expressed by the release event. Also amend the documentation. Partially reverts 17237efaefabe924599abe00e92d8b54032d7915 [ChangeLog][Event Handlers][Important Behavior Changes] TapHandler.point now holds the release position while the tapped() signal is emitted. Fixes: QTBUG-76871 Task-number: QTBUG-64847 Change-Id: I621a2eba4507a498788e9384344e8b4b7da32403 Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickhandlerpoint.cpp | 15 +++++---------- src/quick/handlers/qquicktaphandler.cpp | 9 --------- .../pointerhandlers/qquicktaphandler/data/Button.qml | 2 ++ .../qquicktaphandler/tst_qquicktaphandler.cpp | 8 ++++++++ 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/quick/handlers/qquickhandlerpoint.cpp b/src/quick/handlers/qquickhandlerpoint.cpp index de21537f27..f3d92cf200 100644 --- a/src/quick/handlers/qquickhandlerpoint.cpp +++ b/src/quick/handlers/qquickhandlerpoint.cpp @@ -51,13 +51,14 @@ Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) A QML representation of a QQuickEventPoint. - It's possible to make bindings to properties of a \l SinglePointHandler's - current point. For example: + It's possible to make bindings to properties of a handler's current + \l {SinglePointHandler::point}{point} or + \l {MultiPointHandler::centroid}{centroid}. For example: \snippet pointerHandlers/dragHandlerNullTarget.qml 0 The point is kept up-to-date when the DragHandler is actively responding to - an EventPoint; but when the point is released, or the current point is + an EventPoint; but after the point is released, or when the current point is being handled by a different handler, \c position.x and \c position.y are 0. \note This is practically identical to QtQuick::EventPoint; however an @@ -68,7 +69,7 @@ Q_DECLARE_LOGGING_CATEGORY(DBG_TOUCH_TARGET) handler is handling. HandlerPoint is a Q_GADGET that the handler owns. This allows you to make lifetime bindings to its properties. - \sa SinglePointHandler::point + \sa SinglePointHandler::point, MultiPointHandler::centroid */ QQuickHandlerPoint::QQuickHandlerPoint() @@ -106,12 +107,6 @@ void QQuickHandlerPoint::reset(const QQuickEventPoint *point) m_scenePressPosition = point->scenePosition(); m_pressedButtons = event->buttons(); break; - case QQuickEventPoint::Released: - if (event->buttons() == Qt::NoButton) { - reset(); - return; - } - break; default: break; } diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index 73c559c998..61f97868e8 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -391,9 +391,6 @@ void QQuickTapHandler::updateTimeHeld() from the release event about the point that was tapped: \snippet pointerHandlers/tapHandlerOnTapped.qml 0 - - \note At the time this signal is emitted, \l point has been reset - (all coordinates are \c 0). */ /*! @@ -405,9 +402,6 @@ void QQuickTapHandler::updateTimeHeld() it can be tapped again; but if the time until the next tap is less, \l tapCount will increase. The \c eventPoint signal parameter contains information from the release event about the point that was tapped. - - \note At the time this signal is emitted, \l point has been reset - (all coordinates are \c 0). */ /*! @@ -421,9 +415,6 @@ void QQuickTapHandler::updateTimeHeld() \l singleTapped, \l tapped, and \l tapCountChanged. The \c eventPoint signal parameter contains information from the release event about the point that was tapped. - - \note At the time this signal is emitted, \l point has been reset - (all coordinates are \c 0). */ /*! diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml index 221e7df139..042b730799 100644 --- a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/Button.qml @@ -34,6 +34,7 @@ Rectangle { property alias pressed: tap.pressed property bool checked: false property alias gesturePolicy: tap.gesturePolicy + property point tappedPosition: Qt.point(0, 0) signal tapped signal canceled @@ -51,6 +52,7 @@ Rectangle { longPressThreshold: 100 // CI can be insanely slow, so don't demand a timely release to generate onTapped onTapped: { tapFlash.start() + root.tappedPosition = point.scenePosition root.tapped() } onCanceled: root.canceled() diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp index 33cea69147..e77ea97518 100644 --- a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp @@ -107,6 +107,8 @@ void tst_TapHandler::touchGesturePolicyDragThreshold() QQuickItem *buttonDragThreshold = window->rootObject()->findChild("DragThreshold"); QVERIFY(buttonDragThreshold); + QQuickTapHandler *tapHandler = buttonDragThreshold->findChild(); + QVERIFY(tapHandler); QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped())); // DragThreshold button stays pressed while touchpoint stays within dragThreshold, emits tapped on release @@ -122,6 +124,8 @@ void tst_TapHandler::touchGesturePolicyDragThreshold() QQuickTouchUtils::flush(window); QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); QCOMPARE(dragThresholdTappedSpy.count(), 1); + QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1); + QCOMPARE(tapHandler->point().position(), QPointF()); // DragThreshold button is no longer pressed if touchpoint goes beyond dragThreshold dragThresholdTappedSpy.clear(); @@ -152,6 +156,8 @@ void tst_TapHandler::mouseGesturePolicyDragThreshold() QQuickItem *buttonDragThreshold = window->rootObject()->findChild("DragThreshold"); QVERIFY(buttonDragThreshold); + QQuickTapHandler *tapHandler = buttonDragThreshold->findChild(); + QVERIFY(tapHandler); QSignalSpy dragThresholdTappedSpy(buttonDragThreshold, SIGNAL(tapped())); // DragThreshold button stays pressed while mouse stays within dragThreshold, emits tapped on release @@ -164,6 +170,8 @@ void tst_TapHandler::mouseGesturePolicyDragThreshold() QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(!buttonDragThreshold->property("pressed").toBool()); QTRY_COMPARE(dragThresholdTappedSpy.count(), 1); + QCOMPARE(buttonDragThreshold->property("tappedPosition").toPoint(), p1); + QCOMPARE(tapHandler->point().position(), QPointF()); // DragThreshold button is no longer pressed if mouse goes beyond dragThreshold dragThresholdTappedSpy.clear(); -- cgit v1.2.3 From 1982d1b1aa55ae44a1a775a5745e5c2f11001398 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 4 Jul 2019 11:44:52 +0200 Subject: Move Event Handler acceptedButtons check back up to QQPDeviceHandler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts what's left of e53510944169ac9f6753e0d14e1b24a24ff7bd9a (amends 73258eca7ab7e3981d9f4aaa5484020cb67854a0): MultiPointHandler is not only for touch handling anymore. DragHandler in particular needs to respect the acceptedButtons property. Fixes: QTBUG-76875 Fixes: QTBUG-76582 Change-Id: I414e785dd09b297c93e5e9f162be23e4a44eca54 Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickhoverhandler.cpp | 3 + src/quick/handlers/qquickpointerdevicehandler.cpp | 4 ++ src/quick/handlers/qquicksinglepointhandler.cpp | 3 - .../qquickdraghandler/tst_qquickdraghandler.cpp | 82 +++++++++++++++------- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp index 61955cad03..bdcd5dc4cc 100644 --- a/src/quick/handlers/qquickhoverhandler.cpp +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qquickhoverhandler_p.h" +#include QT_BEGIN_NAMESPACE @@ -59,6 +60,8 @@ Q_LOGGING_CATEGORY(lcHoverHandler, "qt.quick.handler.hover") QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent) : QQuickSinglePointHandler(parent) { + // Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state + d_func()->acceptedButtons = Qt::NoButton; // Rule out the touchscreen for now (can be overridden in QML in case a hover-detecting touchscreen exists) setAcceptedDevices(static_cast( static_cast(QQuickPointerDevice::AllDevices) ^ static_cast(QQuickPointerDevice::TouchScreen))); diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp index 096fad2071..246686e4f4 100644 --- a/src/quick/handlers/qquickpointerdevicehandler.cpp +++ b/src/quick/handlers/qquickpointerdevicehandler.cpp @@ -256,6 +256,10 @@ bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event) return false; if (d->acceptedModifiers != Qt::KeyboardModifierMask && event->modifiers() != d->acceptedModifiers) return false; + // HoverHandler sets acceptedButtons to Qt::NoButton to indicate that button state is irrelevant. + if (event->device()->pointerType() != QQuickPointerDevice::Finger && acceptedButtons() != Qt::NoButton && + (event->buttons() & acceptedButtons()) == 0 && (event->button() & acceptedButtons()) == 0) + return false; return true; } diff --git a/src/quick/handlers/qquicksinglepointhandler.cpp b/src/quick/handlers/qquicksinglepointhandler.cpp index ae162bed87..81859f6c80 100644 --- a/src/quick/handlers/qquicksinglepointhandler.cpp +++ b/src/quick/handlers/qquicksinglepointhandler.cpp @@ -67,9 +67,6 @@ bool QQuickSinglePointHandler::wantsPointerEvent(QQuickPointerEvent *event) { if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) return false; - if (event->device()->pointerType() != QQuickPointerDevice::Finger && - (event->buttons() & acceptedButtons()) == 0 && (event->button() & acceptedButtons()) == 0) - return false; if (m_pointInfo.m_id) { // We already know which one we want, so check whether it's there. diff --git a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp index cc8c567e5c..fb0192893f 100644 --- a/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickdraghandler/tst_qquickdraghandler.cpp @@ -54,6 +54,7 @@ private slots: void defaultPropertyValues(); void touchDrag(); + void mouseDrag_data(); void mouseDrag(); void dragFromMargin(); void touchDragMulti(); @@ -193,8 +194,22 @@ void tst_DragHandler::touchDrag() QCOMPARE(centroidChangedSpy.count(), 5); } +void tst_DragHandler::mouseDrag_data() +{ + QTest::addColumn("acceptedButtons"); + QTest::addColumn("dragButton"); + QTest::newRow("left: drag") << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::LeftButton); + QTest::newRow("right: don't drag") << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::RightButton); + QTest::newRow("left: don't drag") << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::LeftButton); + QTest::newRow("right or middle: drag") << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::MiddleButton); +} + void tst_DragHandler::mouseDrag() { + QFETCH(Qt::MouseButtons, acceptedButtons); + QFETCH(Qt::MouseButtons, dragButton); + bool shouldDrag = bool(acceptedButtons & dragButton); + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "draggables.qml"); @@ -204,6 +219,7 @@ void tst_DragHandler::mouseDrag() QVERIFY(ball); QQuickDragHandler *dragHandler = ball->findChild(); QVERIFY(dragHandler); + dragHandler->setAcceptedButtons(acceptedButtons); // QTBUG-76875 QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged())); @@ -211,45 +227,57 @@ void tst_DragHandler::mouseDrag() QPointF ballCenter = ball->clipRect().center(); QPointF scenePressPos = ball->mapToScene(ballCenter); QPoint p1 = scenePressPos.toPoint(); - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); + QTest::mousePress(window, static_cast(int(dragButton)), Qt::NoModifier, p1); QVERIFY(!dragHandler->active()); - QCOMPARE(dragHandler->centroid().position(), ballCenter); - QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); - QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); - QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); - QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); - QCOMPARE(centroidChangedSpy.count(), 1); + if (shouldDrag) { + QCOMPARE(dragHandler->centroid().position(), ballCenter); + QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); + QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); + QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); + QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); + QCOMPARE(centroidChangedSpy.count(), 1); + } p1 += QPoint(dragThreshold, 0); QTest::mouseMove(window, p1); - QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0); - QCOMPARE(centroidChangedSpy.count(), 2); - QVERIFY(!dragHandler->active()); + if (shouldDrag) { + QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0); + QCOMPARE(centroidChangedSpy.count(), 2); + QVERIFY(!dragHandler->active()); + } p1 += QPoint(1, 0); QTest::mouseMove(window, p1); - QTRY_VERIFY(dragHandler->active()); + if (shouldDrag) + QTRY_VERIFY(dragHandler->active()); + else + QVERIFY(!dragHandler->active()); QCOMPARE(translationChangedSpy.count(), 0); - QCOMPARE(centroidChangedSpy.count(), 3); + if (shouldDrag) + QCOMPARE(centroidChangedSpy.count(), 3); QCOMPARE(dragHandler->translation().x(), 0.0); QPointF sceneGrabPos = p1; - QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); + if (shouldDrag) + QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); p1 += QPoint(19, 0); QTest::mouseMove(window, p1); - QTRY_VERIFY(dragHandler->active()); - QCOMPARE(dragHandler->centroid().position(), ballCenter); - QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); - QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter)); - QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); - QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); - QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); - QCOMPARE(dragHandler->translation().y(), 0.0); - QVERIFY(dragHandler->centroid().velocity().x() > 0); - QCOMPARE(centroidChangedSpy.count(), 4); - QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); + QVERIFY(shouldDrag ? dragHandler->active() : !dragHandler->active()); + if (shouldDrag) { + QCOMPARE(dragHandler->centroid().position(), ballCenter); + QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); + QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter)); + QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); + QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); + QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); + QCOMPARE(dragHandler->translation().y(), 0.0); + QVERIFY(dragHandler->centroid().velocity().x() > 0); + QCOMPARE(centroidChangedSpy.count(), 4); + } + QTest::mouseRelease(window, static_cast(int(dragButton)), Qt::NoModifier, p1); QTRY_VERIFY(!dragHandler->active()); QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton); - QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); - QCOMPARE(translationChangedSpy.count(), 1); - QCOMPARE(centroidChangedSpy.count(), 5); + if (shouldDrag) + QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); + QCOMPARE(translationChangedSpy.count(), shouldDrag ? 1 : 0); + QCOMPARE(centroidChangedSpy.count(), shouldDrag ? 5 : 0); } void tst_DragHandler::dragFromMargin() // QTBUG-74966 -- cgit v1.2.3 From b15a745c384436f706cd9b87c04a7f252e109962 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Sat, 6 Jul 2019 20:56:11 +0200 Subject: Fix compilation with C++20 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implicit capture of 'this' in [=] is deprecated in C++20. Fix by using explicit captures. Change-Id: I49b0fd2751c1d239c4f801224b71872c227fd697 Reviewed-by: Mårten Nordheim --- tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp b/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp index cbe3be2e70..2f41e57324 100644 --- a/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp +++ b/tests/auto/qml/ecmascripttests/qjstest/test262runner.cpp @@ -597,7 +597,7 @@ void SingleTest::run() void SingleTest::runExternalTest() { - auto runTest = [=] (const char *header, TestCase::Result *result) { + auto runTest = [this] (const char *header, TestCase::Result *result) { QTemporaryFile tempFile; tempFile.open(); tempFile.write(header); -- cgit v1.2.3 From f3d40896c1d8601703fcbf30214e22f50eb72727 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Tue, 9 Jul 2019 16:39:25 +0200 Subject: Fix promise chaining Fixes: QTBUG-71329 Change-Id: I261b25ff281bb44d03650ab05258743f104f3cc9 Reviewed-by: Ulf Hermann --- src/qml/jsruntime/qv4promiseobject.cpp | 201 ++++++++++++++++++----- src/qml/jsruntime/qv4promiseobject_p.h | 7 +- tests/auto/qml/qqmlpromise/data/promisechain.qml | 24 +++ tests/auto/qml/qqmlpromise/tst_qqmlpromise.cpp | 15 ++ 4 files changed, 201 insertions(+), 46 deletions(-) create mode 100644 tests/auto/qml/qqmlpromise/data/promisechain.qml diff --git a/src/qml/jsruntime/qv4promiseobject.cpp b/src/qml/jsruntime/qv4promiseobject.cpp index 8450655334..40a0dfaa57 100644 --- a/src/qml/jsruntime/qv4promiseobject.cpp +++ b/src/qml/jsruntime/qv4promiseobject.cpp @@ -86,6 +86,7 @@ namespace QV4 { namespace Promise { const int PROMISE_REACTION_EVENT = QEvent::registerEventType(); +const int PROMISE_RESOLVE_THENABLE_EVENT = QEvent::registerEventType(); struct ReactionEvent : public QEvent { @@ -99,6 +100,18 @@ struct ReactionEvent : public QEvent QV4::PersistentValue resolution; }; +struct ResolveThenableEvent : public QEvent +{ + ResolveThenableEvent(ExecutionEngine *e, const PromiseObject *promise_, const Object *thenable_, const FunctionObject *then_) + : QEvent(QEvent::Type(PROMISE_RESOLVE_THENABLE_EVENT)), promise(e, *promise_), thenable(e, *thenable_), then(e, *then_) + {} + + QV4::PersistentValue promise; + QV4::PersistentValue thenable; + QV4::PersistentValue then; +}; + + } // namespace Promise } // namespace QV4 QT_END_NAMESPACE @@ -115,6 +128,11 @@ void ReactionHandler::addReaction(ExecutionEngine *e, const Value *reaction, con QCoreApplication::postEvent(this, new ReactionEvent(e, reaction, value)); } +void ReactionHandler::addResolveThenable(ExecutionEngine *e, const PromiseObject *promise, const Object *thenable, const FunctionObject *then) +{ + QCoreApplication::postEvent(this, new ResolveThenableEvent(e, promise, thenable, then)); +} + void ReactionHandler::customEvent(QEvent *event) { if (event) @@ -122,6 +140,9 @@ void ReactionHandler::customEvent(QEvent *event) const int type = event->type(); if (type == PROMISE_REACTION_EVENT) executeReaction(static_cast(event)); + + if (type == PROMISE_RESOLVE_THENABLE_EVENT) + executeResolveThenable(static_cast(event)); } } @@ -159,6 +180,7 @@ void ReactionHandler::executeReaction(ReactionEvent *event) } } + namespace { class FunctionBuilder { @@ -200,6 +222,26 @@ public: } + +void ReactionHandler::executeResolveThenable(ResolveThenableEvent *event) +{ + Scope scope(event->then.engine()); + JSCallData jsCallData(scope, 2); + PromiseObject *promise = event->promise.as(); + ScopedFunctionObject resolve {scope, FunctionBuilder::makeResolveFunction(scope.engine, promise->d())}; + ScopedFunctionObject reject {scope, FunctionBuilder::makeRejectFunction(scope.engine, promise->d())}; + jsCallData->args[0] = resolve; + jsCallData.args[1] = reject; + jsCallData->thisObject = event->thenable.as(); + event->then.as()->call(jsCallData); + if (scope.engine->hasException) { + JSCallData rejectCallData(scope, 1); + rejectCallData->args[0] = scope.engine->catchException(); + Scoped reject {scope, scope.engine->memoryManager->allocate()}; + reject->call(rejectCallData); + } +} + void Heap::PromiseObject::setState(PromiseObject::State state) { this->state = state; @@ -363,23 +405,36 @@ void Heap::RejectWrapper::init() Heap::FunctionObject::init(); } +ReturnedValue PromiseCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + // 25.4.3.1 Promise ( executor ) + // 1. If NewTarget is undefined, throw a TypeError exception. + Scope scope(f); + THROW_TYPE_ERROR(); +} ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { + // 25.4.3.1 Promise ( executor ) + Scope scope(f); - if (argc != 1) + if (argc == 0) // If there are no arguments, argument 1 will be undefined ==> thus not callable ==> type error THROW_TYPE_ERROR(); ScopedFunctionObject executor(scope, argv[0].as()); - if (!executor) - THROW_TYPE_ERROR(); + if (!executor) //2. If IsCallable(executor) is false + THROW_TYPE_ERROR(); // throw a TypeError exception Scoped a(scope, scope.engine->newPromiseObject()); if (scope.engine->hasException) return Encode::undefined(); - a->d()->state = Heap::PromiseObject::Pending; + a->d()->state = Heap::PromiseObject::Pending; //4. Set promise.[[PromiseState]] to "pending" + // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. + // 6. Set promise.[[PromiseRejectReactions]] to a new empty List. + // 7. Set promise.[[PromiseIsHandled]] to false. + // happens in constructor of a ScopedFunctionObject resolve(scope, FunctionBuilder::makeResolveFunction(scope.engine, a->d())); ScopedFunctionObject reject(scope, FunctionBuilder::makeRejectFunction(scope.engine, a->d())); @@ -387,13 +442,15 @@ ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, con JSCallData jsCallData(scope, 2); jsCallData->args[0] = resolve; jsCallData->args[1] = reject; - jsCallData->thisObject = a; + //jsCallData->thisObject = a; VERIFY corretness, but this should be undefined (see below) - executor->call(jsCallData); + executor->call(jsCallData); // 9. Let completion be Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). if (scope.engine->hasException) { - a->d()->state = Heap::PromiseObject::Rejected; - a->d()->resolution.set(scope.engine, Value::fromReturnedValue(scope.engine->catchException())); + ScopedValue exception {scope, scope.engine->catchException()}; + JSCallData callData {scope, 1}; + callData.args[0] = exception; + reject->call(callData); } if (newTarget) @@ -402,42 +459,42 @@ ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, con return a->asReturnedValue(); } -ReturnedValue PromiseCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) -{ - Scope scope(f); - THROW_TYPE_ERROR(); -} ReturnedValue PromiseCtor::method_resolve(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.4.5Promise.resolve ( x ) Scope scope(f); ExecutionEngine* e = scope.engine; - if (!thisObject || !thisObject->isObject()) + if (!thisObject || !thisObject->isObject()) // 2. If Type(C) is not Object, throw a TypeError exception THROW_TYPE_ERROR(); - ScopedValue argument(scope); + ScopedValue x(scope); if (argc < 1) { - argument = Encode::undefined(); + x = Encode::undefined(); } else { - argument = argv[0]; + x = argv[0]; } - if (isPromise(argument) && argument->isObject()) { + // 3. If IsPromise(x) is true, then + if (isPromise(x) && x->isObject()) { ScopedObject so(scope, thisObject); - ScopedObject constructor(scope, argument->objectValue()->get(e->id_constructor())); - if (so->d() == constructor->d()) - return argument->asReturnedValue(); + // Let xConstructor be ? Get(x, "constructor"). + ScopedObject constructor(scope, x->objectValue()->get(e->id_constructor())); + if (so->d() == constructor->d()) // If SameValue(xConstructor, C) is true, return x. + return x->asReturnedValue(); } + // Let promiseCapability be ? NewPromiseCapability(C). Scoped capability(scope, e->memoryManager->allocate()); ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as(), capability)); if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) THROW_TYPE_ERROR(); + // Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). ScopedValue undefined(scope, Value::undefinedValue()); ScopedFunctionObject resolve(scope, capability->d()->resolve); - resolve->call(undefined, argument, 1); + resolve->call(undefined, x, 1); return newPromise.asReturnedValue(); } @@ -447,16 +504,18 @@ ReturnedValue PromiseCtor::method_reject(const FunctionObject *f, const Value *t Scope scope(f); ExecutionEngine *e = scope.engine; + // 2. If Type(C) is not Object, throw a TypeError exception. if (!thisObject || !thisObject->isObject()) THROW_TYPE_ERROR(); - ScopedValue argument(scope); + ScopedValue r(scope); if (argc < 1) { - argument = Encode::undefined(); + r = Encode::undefined(); } else { - argument = argv[0]; + r = argv[0]; } + // 3. Let promiseCapability be ? NewPromiseCapability(C). Scoped capability(scope, e->memoryManager->allocate()); ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as(), capability)); @@ -465,7 +524,8 @@ ReturnedValue PromiseCtor::method_reject(const FunctionObject *f, const Value *t ScopedValue undefined(scope, Value::undefinedValue()); ScopedFunctionObject reject(scope, capability->d()->reject.as()); - reject->call(undefined, argument, 1); + // Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). + reject->call(undefined, r, 1); return newPromise.asReturnedValue(); } @@ -475,6 +535,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this Scope scope(f); ExecutionEngine* e = scope.engine; + // 2. If Type(C) is not Object, throw a TypeError exception. if (!thisObject || !thisObject->isObject()) THROW_TYPE_ERROR(); @@ -777,6 +838,7 @@ void PromisePrototype::init(ExecutionEngine *engine, Object *ctor) ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.5.3 Promise.prototype.then Scope scope(f); ExecutionEngine* e = scope.engine; @@ -804,6 +866,7 @@ ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value if (!constructor || scope.hasException()) THROW_TYPE_ERROR(); + // 4. Let resultCapability be ? NewPromiseCapability(C). ScopedObject nextPromise(scope, e->newPromiseObject(constructor, capability)); capability->d()->promise.set(scope.engine, nextPromise); @@ -811,21 +874,25 @@ ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value Scoped rejectReaction(scope, Heap::PromiseReaction::createRejectReaction(scope.engine, capability, onRejected)); ScopedValue resolution(scope, promise->d()->resolution); - if (promise->d()->isPending()) { + if (promise->d()->isPending()) { // 7. If promise.[[PromiseState]] is "pending" { + // Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]]. ScopedArrayObject a(scope, promise->d()->fulfillReactions); ScopedValue newValue(scope, fulfillReaction->d()); a->push_back(newValue); } { + // Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]]. ScopedArrayObject a(scope, promise->d()->rejectReactions); ScopedValue newValue(scope, rejectReaction->d()); a->push_back(newValue); } - } else if (promise->d()->isFulfilled()) { - fulfillReaction->as()->d()->triggerWithValue(e, resolution); - } else if (promise->d()->isRejected()) { + } else if (promise->d()->isFulfilled()) { // 8. Else if promise.[[PromiseState]] is "fulfilled", then + // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »). + fulfillReaction->as()->d()->triggerWithValue(e, resolution); // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »). + } else if (promise->d()->isRejected()) { // 9. Else + // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « rejectReaction, reason »). rejectReaction->as()->d()->triggerWithValue(e, resolution); } else { Q_ASSERT(false); @@ -889,7 +956,6 @@ ReturnedValue CapabilitiesExecutorWrapper::virtualCall(const FunctionObject *f, if (argc >= 2 && !argv[1].isUndefined()) capabilities->reject.set(scope.engine, argv[1]); - // TODO: return? return Encode::undefined(); } @@ -929,27 +995,60 @@ ReturnedValue ResolveElementWrapper::virtualCall(const FunctionObject *f, const ReturnedValue ResolveWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.1.3.2 (ecmase-262/8.0) + Q_UNUSED(thisObject); Scope scope(f); const ResolveWrapper *self = static_cast(f); Scoped promise(scope, self->d()->promise); - if (self->d()->alreadyResolved || !promise->d()->isPending()) + // 4. If alreadyRseolved.[[Value]] is true, return undefined + if (self->d()->alreadyResolved || !promise->d()->isPending()) // Why check for pending? return Encode::undefined(); - ScopedValue value(scope); + // 5. Set alreadyResolved.[[Value]] to true + self->d()->alreadyResolved = true; + + ScopedValue resolution(scope); if (argc == 1) { - value = argv[0]; + resolution = argv[0]; } else { - value = Encode::undefined(); + resolution = Encode::undefined(); } - self->d()->alreadyResolved = true; - promise->d()->setState(Heap::PromiseObject::Fulfilled); - promise->d()->resolution.set(scope.engine, value); - - promise->d()->triggerFullfillReactions(scope.engine); + if (!resolution->isObject()) { // 7 If Type(resolution) is not Object + // then Return FullFillPromise(promise, resolution) + // (FullFillPromise will return undefined, so we share the return with the other path which also returns undefined + promise->d()->setState(Heap::PromiseObject::Fulfilled); + promise->d()->resolution.set(scope.engine, resolution); + promise->d()->triggerFullfillReactions(scope.engine); + } else { + //PromiseObject *promise = resolution->as(); + auto resolutionObject = resolution->as(); + ScopedString thenName(scope, scope.engine->newIdentifier(QStringLiteral("then"))); + + // 8. Let then be Get(resolution, then) + ScopedFunctionObject thenAction { scope, resolutionObject->get(thenName)}; + // 9. If then is an abrupt completion, then + if (scope.engine->hasException) { + // Return RecjectPromise(promise, then.[[Value]] + ScopedValue thenValue {scope, scope.engine->catchException()}; + promise->d()->setState(Heap::PromiseObject::Rejected); + promise->d()->resolution.set(scope.engine, thenValue); + promise->d()->triggerRejectReactions(scope.engine); + } else { + // 10. Let thenAction be then.[[Value]] + if (!thenAction) { // 11. If IsCallable(thenAction) is false + promise->d()->setState(Heap::PromiseObject::Fulfilled); + promise->d()->resolution.set(scope.engine, resolution); + promise->d()->triggerFullfillReactions(scope.engine); + } else { + // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, « promise, resolution, thenAction »). + scope.engine->getPromiseReactionHandler()->addResolveThenable(scope.engine, promise.getPointer(), resolutionObject, thenAction); + } + } + } return Encode::undefined(); } @@ -972,11 +1071,25 @@ ReturnedValue RejectWrapper::virtualCall(const FunctionObject *f, const Value *t value = Encode::undefined(); } - self->d()->alreadyResolved = true; - promise->d()->setState(Heap::PromiseObject::Rejected); - promise->d()->resolution.set(scope.engine, value); + if (!isPromise(value)) { + self->d()->alreadyResolved = true; + promise->d()->setState(Heap::PromiseObject::Rejected); + promise->d()->resolution.set(scope.engine, value); + + promise->d()->triggerRejectReactions(scope.engine); - promise->d()->triggerRejectReactions(scope.engine); + } else { + PromiseObject *promise = value->as(); + ScopedString thenName(scope, scope.engine->newIdentifier(QStringLiteral("catch"))); + + ScopedFunctionObject then(scope, promise->get(thenName)); + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = *f; + jsCallData->args[1] = Encode::undefined(); + jsCallData->thisObject = value; + + then->call(jsCallData); + } return Encode::undefined(); } diff --git a/src/qml/jsruntime/qv4promiseobject_p.h b/src/qml/jsruntime/qv4promiseobject_p.h index bce59b19a7..8a3724e07d 100644 --- a/src/qml/jsruntime/qv4promiseobject_p.h +++ b/src/qml/jsruntime/qv4promiseobject_p.h @@ -62,6 +62,7 @@ struct PromiseCapability; namespace Promise { struct ReactionEvent; +struct ResolveThenableEvent; class ReactionHandler : public QObject { @@ -69,13 +70,15 @@ class ReactionHandler : public QObject public: ReactionHandler(QObject *parent = nullptr); - virtual ~ReactionHandler(); + virtual ~ReactionHandler() override; void addReaction(ExecutionEngine *e, const Value *reaction, const Value *value); + void addResolveThenable(ExecutionEngine *e, const PromiseObject *promise, const Object *thenable, const FunctionObject *then); protected: - void customEvent(QEvent *event); + void customEvent(QEvent *event) override; void executeReaction(ReactionEvent *event); + void executeResolveThenable(ResolveThenableEvent *event); }; } // Promise diff --git a/tests/auto/qml/qqmlpromise/data/promisechain.qml b/tests/auto/qml/qqmlpromise/data/promisechain.qml new file mode 100644 index 0000000000..fa1809aef0 --- /dev/null +++ b/tests/auto/qml/qqmlpromise/data/promisechain.qml @@ -0,0 +1,24 @@ +import QtQml 2.0 + +QtObject { + property int x: 0 + id: root; + Component.onCompleted: { + new Promise((res) => { + res(1) + }) + .then((data) => { + console.debug(data) + return new Promise((res) => {res(2)}); + }) + .then((data) => { + console.debug(data) + return new Promise((res) => {res(3)}); + }) + .then((data) => { + console.debug(data); + root.x = 42; + }); + } + +} diff --git a/tests/auto/qml/qqmlpromise/tst_qqmlpromise.cpp b/tests/auto/qml/qqmlpromise/tst_qqmlpromise.cpp index 0f4bb5cdcc..41850d0263 100644 --- a/tests/auto/qml/qqmlpromise/tst_qqmlpromise.cpp +++ b/tests/auto/qml/qqmlpromise/tst_qqmlpromise.cpp @@ -82,6 +82,7 @@ private slots: void then_fulfilled_non_callable(); void then_reject_non_callable(); void then_resolve_multiple_then(); + void promiseChain(); private: void execute_test(QString testName); @@ -270,6 +271,20 @@ void tst_qqmlpromise::execute_test(QString testName) QTRY_COMPARE(object->property("wasTestSuccessful").toBool(), true); } +void tst_qqmlpromise::promiseChain() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("promisechain.qml")); + QVERIFY(component.isReady()); + QTest::ignoreMessage(QtDebugMsg, "1"); + QTest::ignoreMessage(QtDebugMsg, "2"); + QTest::ignoreMessage(QtDebugMsg, "3"); + QScopedPointer root(component.create()); + QVERIFY(root); + QTRY_VERIFY(root->property("x") == 42); + +} + QTEST_MAIN(tst_qqmlpromise) -- cgit v1.2.3 From 9dcec8f016c1fdd9d0e99e0ee717523a8823bca7 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 12 Jul 2019 07:19:50 +0200 Subject: Fix static build Use the GL attribute name helper functions only from QtQuick to avoid a clash of symbols when linking statically. Change-Id: Ic95b984092f5db222db6dc1f4ac5fb443b5ab714 Fixes: QTBUG-77012 Reviewed-by: Andy Shaw --- src/imports/wavefrontmesh/qwavefrontmesh.cpp | 13 ------------- src/quick/items/qquickshadereffectmesh_p.h | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/imports/wavefrontmesh/qwavefrontmesh.cpp b/src/imports/wavefrontmesh/qwavefrontmesh.cpp index 101e6ab4b0..e973ef0103 100644 --- a/src/imports/wavefrontmesh/qwavefrontmesh.cpp +++ b/src/imports/wavefrontmesh/qwavefrontmesh.cpp @@ -53,19 +53,6 @@ QT_BEGIN_NAMESPACE -static const char qt_position_attribute_name[] = "qt_Vertex"; -static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0"; - -const char *qtPositionAttributeName() -{ - return qt_position_attribute_name; -} - -const char *qtTexCoordAttributeName() -{ - return qt_texcoord_attribute_name; -} - class QWavefrontMeshPrivate : public QObjectPrivate { public: diff --git a/src/quick/items/qquickshadereffectmesh_p.h b/src/quick/items/qquickshadereffectmesh_p.h index 62a9798e40..79e05a5f9f 100644 --- a/src/quick/items/qquickshadereffectmesh_p.h +++ b/src/quick/items/qquickshadereffectmesh_p.h @@ -66,8 +66,8 @@ QT_REQUIRE_CONFIG(quick_shadereffect); QT_BEGIN_NAMESPACE -const char *qtPositionAttributeName(); -const char *qtTexCoordAttributeName(); +Q_QUICK_PRIVATE_EXPORT const char *qtPositionAttributeName(); +Q_QUICK_PRIVATE_EXPORT const char *qtTexCoordAttributeName(); class QSGGeometry; class QRectF; -- cgit v1.2.3 From 141ffbe37e9263829a156fc1f4d7b93a2bf311be Mon Sep 17 00:00:00 2001 From: Pavel Tumakaev Date: Wed, 22 May 2019 17:32:25 +0300 Subject: Fix crashes in QQmlXMLHttpRequest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExecutionEngine::callingQmlContext() in some cases returns a null pointer. According to ISO/IEC 14882 §9.3.1/1 "If a nonstatic member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined". Thus, invoking a QQmlContextData::resolvedUrl() member function on a null instance results in undefined behavior, and leads to a crash in some cases. ExecutionEngine::qmlEngine() in some cases returns a null pointer. The QQmlEnginePrivate::get() method must return a pointer to a QQmlEngine private internal class. Call QQmlEnginePrivate::get() with passed null pointer leads to application crash. If the QQmlEngine pointer is null, the QQmlEnginePrivate pointer should also be null. Thus, if the pointer to QQmlEngine is null pointer, the null pointer to the private class should be passed to the QQmlEnginePrivate::warning(). Task-number: QTBUG-75983 Change-Id: Iad240bb6db0be58e9087b7a86f8d400b07623865 Reviewed-by: Ulf Hermann --- src/qml/qml/qqmlxmlhttprequest.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/qml/qml/qqmlxmlhttprequest.cpp b/src/qml/qml/qqmlxmlhttprequest.cpp index 9f629f974d..9877cc027f 100644 --- a/src/qml/qml/qqmlxmlhttprequest.cpp +++ b/src/qml/qml/qqmlxmlhttprequest.cpp @@ -1574,7 +1574,8 @@ void QQmlXMLHttpRequest::dispatchCallbackNow(Object *thisObj, bool done, bool er if (scope.engine->hasException) { QQmlError error = scope.engine->catchExceptionAsQmlError(); - QQmlEnginePrivate::warning(QQmlEnginePrivate::get(scope.engine->qmlEngine()), error); + QQmlEnginePrivate *qmlEnginePrivate = scope.engine->qmlEngine() ? QQmlEnginePrivate::get(scope.engine->qmlEngine()) : nullptr; + QQmlEnginePrivate::warning(qmlEnginePrivate, error); } }; @@ -1765,8 +1766,13 @@ ReturnedValue QQmlXMLHttpRequestCtor::method_open(const FunctionObject *b, const // Argument 1 - URL QUrl url = QUrl(argv[1].toQStringNoThrow()); - if (url.isRelative()) - url = scope.engine->callingQmlContext()->resolvedUrl(url); + if (url.isRelative()) { + QQmlContextData *qmlContextData = scope.engine->callingQmlContext(); + if (qmlContextData) + url = qmlContextData->resolvedUrl(url); + else + url = scope.engine->resolvedUrl(url.url()); + } bool async = true; // Argument 2 - async (optional) -- cgit v1.2.3 From f806d64249c6e961ab12270d3a045e9980e19cf4 Mon Sep 17 00:00:00 2001 From: Wang Chuan Date: Thu, 11 Jul 2019 10:24:38 +0800 Subject: QQuickItemView: refill itself before populate transition The view uses a visible items list, which is maintained by the refill() method, to determine which items should be triggered to do the populate transition. The refill() was only invoked when component completed before doing the populate transition; but if the size of the view depends on the size of window (for example, using anchors.fill), more delegates could become visible after component completed. In such a case, part of visible items were not be triggered to do the transition. [ChangeLog][QtQuick][Item Views] Item views such as ListView now properly populate delegates with a populate transition when the view is resized after componentComplete. Fixes: QTBUG-76487 Change-Id: Id90c3f73d9911c8a1d6d8b1ea0c51f6c27d0ed5b Reviewed-by: Shawn Rutledge --- src/quick/items/qquickitemview.cpp | 3 + .../data/resizeAfterComponentComplete.qml | 77 ++++++++++++++++++++++ .../quick/qquicklistview/tst_qquicklistview.cpp | 16 +++++ 3 files changed, 96 insertions(+) create mode 100644 tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index d0715cdb7f..21d644dad5 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -1845,6 +1845,9 @@ void QQuickItemViewPrivate::layout() forceLayout = false; if (transitioner && transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) { + // Give the view one more chance to refill itself, + // in case its size is changed such that more delegates become visible after component completed + refill(); for (FxViewItem *item : qAsConst(visibleItems)) { if (!item->transitionScheduledOrRunning()) item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::PopulateTransition, true); diff --git a/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml b/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml new file mode 100644 index 0000000000..851d8f9a0c --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/resizeAfterComponentComplete.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 + +ListView { + id: listView + property var lastItem + anchors.fill: parent + model: 10 + delegate: Rectangle { + width: parent.width + height: 40 + border.color: "lightsteelblue" + Text { + text: "Item" + (index + 1) + } + Component.onCompleted: { + if (index == 9) + listView.lastItem = this + } + } + + populate: Transition { + NumberAnimation { + properties: "x,y" + duration: 1000 + } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index cfd740f33d..d956d89292 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -276,6 +276,7 @@ private slots: void addOnCompleted(); void setPositionOnLayout(); void touchCancel(); + void resizeAfterComponentComplete(); private: template void items(const QUrl &source); @@ -8994,6 +8995,21 @@ void tst_QQuickListView::touchCancel() // QTBUG-74679 QTRY_COMPARE(listview->contentY(), 500.0); } +void tst_QQuickListView::resizeAfterComponentComplete() // QTBUG-76487 +{ + QScopedPointer window(createView()); + window->setSource(testFileUrl("resizeAfterComponentComplete.qml")); + window->resize(640, 480); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QObject *listView = window->rootObject(); + QVERIFY(listView); + + QObject *lastItem = qvariant_cast(listView->property("lastItem")); + QTRY_COMPARE(lastItem->property("y").toInt(), 9 * lastItem->property("height").toInt()); +} + QTEST_MAIN(tst_QQuickListView) #include "tst_qquicklistview.moc" -- cgit v1.2.3 From 8f28229cec8ce8f52cefe760b86f3410786c201a Mon Sep 17 00:00:00 2001 From: Kavindra Palaraja Date: Tue, 16 Jul 2019 08:37:38 +0200 Subject: doc: Update the diagram to mention the correct beforeSynchronizing() signal Fixes: QTBUG-76602 Change-Id: I82f92c5a8e3d2f5cbc3f00516d119be532add302 Reviewed-by: Andy Shaw Reviewed-by: Fabian Kosmale Reviewed-by: Simon Hausmann --- src/quick/doc/images/sg-renderloop-singlethreaded.jpg | Bin 29515 -> 0 bytes src/quick/doc/images/sg-renderloop-singlethreaded.png | Bin 0 -> 162831 bytes src/quick/doc/images/sg-renderloop-threaded.jpg | Bin 45262 -> 0 bytes src/quick/doc/images/sg-renderloop-threaded.png | Bin 0 -> 225933 bytes src/quick/doc/images/sg-renderloop-threaded.xml | 2 ++ .../doc/src/concepts/visualcanvas/scenegraph.qdoc | 4 ++-- 6 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 src/quick/doc/images/sg-renderloop-singlethreaded.jpg create mode 100644 src/quick/doc/images/sg-renderloop-singlethreaded.png delete mode 100644 src/quick/doc/images/sg-renderloop-threaded.jpg create mode 100644 src/quick/doc/images/sg-renderloop-threaded.png create mode 100644 src/quick/doc/images/sg-renderloop-threaded.xml diff --git a/src/quick/doc/images/sg-renderloop-singlethreaded.jpg b/src/quick/doc/images/sg-renderloop-singlethreaded.jpg deleted file mode 100644 index c6d1577138..0000000000 Binary files a/src/quick/doc/images/sg-renderloop-singlethreaded.jpg and /dev/null differ diff --git a/src/quick/doc/images/sg-renderloop-singlethreaded.png b/src/quick/doc/images/sg-renderloop-singlethreaded.png new file mode 100644 index 0000000000..ad5ce62690 Binary files /dev/null and b/src/quick/doc/images/sg-renderloop-singlethreaded.png differ diff --git a/src/quick/doc/images/sg-renderloop-threaded.jpg b/src/quick/doc/images/sg-renderloop-threaded.jpg deleted file mode 100644 index 2f7d97591b..0000000000 Binary files a/src/quick/doc/images/sg-renderloop-threaded.jpg and /dev/null differ diff --git a/src/quick/doc/images/sg-renderloop-threaded.png b/src/quick/doc/images/sg-renderloop-threaded.png new file mode 100644 index 0000000000..1b1d6c7b11 Binary files /dev/null and b/src/quick/doc/images/sg-renderloop-threaded.png differ diff --git a/src/quick/doc/images/sg-renderloop-threaded.xml b/src/quick/doc/images/sg-renderloop-threaded.xml new file mode 100644 index 0000000000..857720b93a --- /dev/null +++ b/src/quick/doc/images/sg-renderloop-threaded.xml @@ -0,0 +1,2 @@ +7Vptc5s4EP41nrn7kBsMhtofbcfNddq0ddxO7/qlI8MCuoDECeGX/vouQhgT2alvxj4mvptJHHYlgXiefdM6PWeabu4EyeJ7HkDSs61g03Nue7bdt0Y2/ik120rjWcNKEQka6EmNYkG/Q71SawsaQN6aKDlPJM3aSp8zBr5s6YgQfN2eFvKk/dSMRGAoFj5JTO0XGshYa/veqBn4HWgU60cP7VfVQErqyfpN8pgEfL2ncmY9Zyo4l9VVuplCUoJX41Kte31kdLcxAUyesuDtdDWYhvej8Gb91vs6mQfx5MONvksut/ULQ4Dvr0UuZMwjzkgya7QTwQsWQHnXPkrNnHecZ1r5F0i51WSSQnJUxTJN9ChuWGz/QMGqhT9L4Te3Fm83+4O3Wy2FnEl9076HcrX3csNHIanfjxfC17Pu6XIczInDvn5abLeWO2ffPtc4SCIikM/Mc3fEocUDTwH3h+sEJETSVXsfRJtetJvXsIMXmqDDZD23yRVJCv2k+byg/uMbCWnPGeNPkQVEwi+/Gqy2OVvHVMIiIwqSNTpum5+QJsmUJ1yotU5AYBj6qM+l4I+wN+L5Q1iGOyZWICRsnufCxK5e4Go/0YGiX/vNunE7e6R18Z7H1evODrd9ItwfeULz+DpAH3UNumOAfvf5Tbkox49lwv1HBPScQIdDH/yDQC+H7sC1LgM05sHTgN65wdmRHhhIj4MVYYiabY0ZxfxFOStRn63Kl7Stj4L7kOeURT0bH+kluMXJUuBVVF4RFqhIix+cXYEvDKzOKXINihaYoaRiwCL4y2BdFjWCpGAS8iEDdvcOx6eYN2Ejay9KSVBy7BdCQfPymHJttx21Bm7XqcIyYPw366idUNVR9j8opOwmwJ2lkPJOLKScLgspz/CrCUSUle+3ZX4sOKPfVfgznUqlIJyostLL95xR157zyqACUlqGqqrW+kKZOjSV1dYSQi5gsWMIw+C5iy4XhsHgEAlDe+l43mVIsDsPX0ODhCOVLqFMvscz/nXg3rnx2/b/aaNCYnRi2hh0mTZGhpvMqpr3Z0mjYBdLG514jtN5xKrbg6fnjQdAkMUFckYnidvpPHb1zW7UwgdWnixUK7g+bQiF+5kP7d2APuje7M2e1FGzJ6EEcV1WP+je6s3+VHXMrs/YCvp8TbJJEYYg8qvA3e3e8M1u1VHDV+2QBVKQQXAd8Hdv9mYn6nXVdKqCvM/TLAFZylUbkKvDtC8AXmIr8CkBXvf2b7YsVCnZVJp7CHt/F+WXiqpev8lVwT7GCX0v2yh86vG6PB1nWUJ9Xbta93jI+xQLIGbKriLcPj1tGhhn8IQzrSIJjRiKWCFgVkJFyQ8+MxnrgZQGgTreHLKHxmIOfQ2oZL3J/hnYf9oIdk4k37sY+WaT5GH2/nb2cCb+n9Zt/2n2jUr7cuyj2Hz9r8b2/onCmf0A5Zhbb9owFMc/TaTtYVIuEOARKPSidmubdp36ZuKTxMOxM8c00E8/O3EKNEyj09ZImQQi+R9fwvn5nGPH8qbp+lSgLLniGKjl2nhteSeW6zr2yFU/WtlUim8PKyEWBJtGWyEgz1D3NOqKYMj3GkrOqSTZvhhyxiCUexoSghf7zSJO92fNUAwNIQgRbaoPBMvEqI4/2hrOgMSJmXroDipDiurG5p/kCcK82JG8meVNBeeyukrXU6DaebVfZrd3PwbzydlDf3kT4Mfomkzpp2qw+Vu6vPwFAUz+8dAXrP/1+7q3XDznxWKTedgfxaaL/YToyvjr5mZFwuW5hNTyxuqzyjCSYDwgN7VbBV8xDHpox/ImRUIkBBkKtbVQC0lpiUypMUeE0imnXJR9PYxgGIVKz6XgS9ix+OEQFpGymMcCIWH9iuNvnOC8kFFLGngKUmxUPzNK3+1X45jV7NRwi+3acEdGS3aWRd9oyCzH+GXorcvVhfH6Gwg4RxK45pTkyYePnSUxaJuE2yBxen9uuT5VE08WQl3F+mqcZZSESBLOVOsrRPTPXSIA4QYb5TO5D2Df0YwzeEXFSIiSmKnbUPkYlD7RBNS0dGwMKcFYT3OQ+HZN2Hp4zqRJzI5f35uHdN6Lr3ckXv8v4E2WF9i5n82Dk/Pb4Fs2S9eXjwdSXSCR8imL9Wzqy6DQBUagFJrUv2TATi+Vfaqcp6mq2fKySmDQ1WslSm91NDjd90yTB+k10ySkRFOosuUDYWVt1vlyAREXEGxYmAjOyLMi3N206b5n2jxIppk2f1HAVKaUn9X+srswvNbDxHtrmNyCcrzodIh4rYdIr1l6VF3XdaM8dNW1RJQsoLmN6AiIXuvh0T8+PFCk9l3/QXT0Wo8OvwGl2mzVO60SR16gbLKKIhB5d1n0Ww+QwfEBUm6UA4UlA9xhJK2Hx7CBZF4dUaqiEfI0o6Df0NiIqdJhl8fSPBQArKtQ/NbjZNSAMsZPiIUaw5iRtH49MHsqT4X2teAh5Hl52Jw2T5cVuZwbfB2l9g9DSd1uX8SWtp3X2d7sJw== + diff --git a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc index 9383c78a42..ee6c501c71 100644 --- a/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc +++ b/src/quick/doc/src/concepts/visualcanvas/scenegraph.qdoc @@ -210,7 +210,7 @@ and when interaction with the scene graph can happen. The following is a simple outline of how a frame gets composed with the threaded render loop. -\image sg-renderloop-threaded.jpg +\image sg-renderloop-threaded.png \list 1 @@ -301,7 +301,7 @@ will make the code non-portable. The following is a simplified illustration of the frame rendering sequence in the non-threaded renderer. -\image sg-renderloop-singlethreaded.jpg +\image sg-renderloop-singlethreaded.png \section2 Custom control over rendering with QQuickRenderControl -- cgit v1.2.3 From f3b1568d05d6c31137aae186d2928fef0faba9b1 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 6 Feb 2019 10:32:31 +0100 Subject: V4: Add label for loop in spread in ArrayPattern This patch also rotates the loop back so that the condition is at the top of the loop. It's a cherry-picked from commit 0282b89ec672e25a465a8e51bc74c7fd58a624b1. Without explicitly setting the label, we get a default constructed Label from labelForOffset in PlatformAssemblerCommon::link, which leads to a jump into nirvana. This issue arises only with backward jumps, as we fill in the information for forward jump targets once we actually encounter the target. Fixes: QTBUG-77047 Change-Id: Id928831f90eace494adb1eb1190f674a6f033b20 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 3 ++- tests/auto/qml/qjsengine/tst_qjsengine.cpp | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 61e37ffd25..5acc64bd81 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -1191,12 +1191,13 @@ bool Codegen::visit(ArrayPattern *ast) ControlFlowLoop flow(this, &end, &in, cleanup); in.link(); + bytecodeGenerator->addLoopStart(in); iterator.loadInAccumulator(); Instruction::IteratorNext next; next.value = lhsValue.stackSlot(); next.done = iteratorDone.stackSlot(); bytecodeGenerator->addInstruction(next); - bytecodeGenerator->addTracingJumpInstruction(Instruction::JumpTrue()).link(end); + bytecodeGenerator->addJumpInstruction(Instruction::JumpTrue()).link(end); lhsValue.loadInAccumulator(); pushAccumulator(); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index aeb29d3cd9..cd7796827d 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -239,6 +239,8 @@ private slots: void aggressiveGc(); void noAccumulatorInTemplateLiteral(); + void triggerBackwardJumpWithDestructuring(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -4691,6 +4693,19 @@ void tst_QJSEngine::noAccumulatorInTemplateLiteral() qputenv("QV4_MM_AGGRESSIVE_GC", origAggressiveGc); } +void tst_QJSEngine::triggerBackwardJumpWithDestructuring() +{ + QJSEngine engine; + auto value = engine.evaluate( + "function makeArray(n) { return [...Array(n).keys()]; }\n" + "for (let i=0;i<100;++i) {\n" + " let arr = makeArray(20)\n" + " arr.sort( (a, b) => b - a )\n" + "}" + ); + QVERIFY(!value.isError()); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" -- cgit v1.2.3 From dd1df9a6025b5723d1b7f5163b292ee9655a6c90 Mon Sep 17 00:00:00 2001 From: Nico Vertriest Date: Wed, 17 Jul 2019 14:21:50 +0200 Subject: Doc: Add doc on "quit" shortcut behavior in macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-71098 Change-Id: Ifc30bfd5abc4a889a2436c8ae977c8e988fb700f Reviewed-by: Tor Arne Vestbø --- src/quick/items/qquickwindow.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 5dcd101462..2316a6c6af 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -3748,6 +3748,12 @@ bool QQuickWindow::isSceneGraphInitialized() const This signal is emitted when the window receives the event \a close from the windowing system. + + On \macOs, Qt will create a menu item \c Quit if there is no menu item + whose text is "quit" or "exit". This menu item calls the \c QCoreApplication::quit + signal, not the \c QQuickWindow::closing() signal. + + \sa {QMenuBar as a Global Menu Bar} */ /*! -- cgit v1.2.3 From 3d2ff1931f9e7c139bb7a4c9490b2cb595d1e7bf Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Wed, 17 Jul 2019 17:08:30 +0200 Subject: Lower severity of nullptr error handling for qmlRegisterSingleton Instead of aborting the execution directly, print a critical warning message and handle the value gracefully as undefined in QML. Change-Id: I8e508ba7fef7c61d9c5a5e13837c4a4cb2ed8486 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlengine.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 00d2a6b502..9388df5617 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -2478,17 +2478,20 @@ QJSValue QQmlEnginePrivate::singletonInstance(const QQmlType &type) } else if (siinfo->qobjectCallback) { QObject *o = siinfo->qobjectCallback(q, q); if (!o) { - qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", - qPrintable(QString::fromUtf8(type.typeName()))); + QQmlError error; + error.setMessageType(QtMsgType::QtCriticalMsg); + error.setDescription(QString::asprintf("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", + qPrintable(QString::fromUtf8(type.typeName())))); + warning(error); + } else { + // if this object can use a property cache, create it now + QQmlData::ensurePropertyCache(q, o); } - // if this object can use a property cache, create it now - QQmlData::ensurePropertyCache(q, o); // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) // should behave identically to QML singleton types. q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); value = q->newQObject(o); singletonInstances.insert(type, value); - } else if (!siinfo->url.isEmpty()) { QQmlComponent component(q, siinfo->url, QQmlComponent::PreferSynchronous); QObject *o = component.beginCreate(q->rootContext()); -- cgit v1.2.3 From 4a7f21da6e7d1f513fc60dccfff6d6b81a116f21 Mon Sep 17 00:00:00 2001 From: Paolo Angelelli Date: Sun, 24 Mar 2019 18:53:52 +0100 Subject: Add QQuickPathMultiLine And show our users even more love. This path element allows to specify a list of polylines as a single list of lists of points. [ChangeLog][QtQuick][Path] Added QQuickPathMultiLine. Change-Id: Idf1d1dcd928bb19b9ad995322f86822448811537 Reviewed-by: Shawn Rutledge --- src/quick/items/qquickitemsmodule.cpp | 1 + src/quick/util/qquickpath.cpp | 157 ++++++++++++++++++++- src/quick/util/qquickpath_p.h | 25 ++++ tests/auto/quick/qquickshape/data/pathitem8.png | Bin 0 -> 7323 bytes tests/auto/quick/qquickshape/data/pathitem8.qml | 88 ++++++++++++ tests/auto/quick/qquickshape/tst_qquickshape.cpp | 30 ++++ .../data/shape/shape_multiline.qml | 74 ++++++++++ 7 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 tests/auto/quick/qquickshape/data/pathitem8.png create mode 100644 tests/auto/quick/qquickshape/data/pathitem8.qml create mode 100644 tests/manual/scenegraph_lancelot/data/shape/shape_multiline.qml diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 15a417a8b4..4168903205 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -228,6 +228,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType("QtQuick",2,0,"PathSvg"); qmlRegisterType(uri, 2, 14, "Path"); qmlRegisterType("QtQuick", 2, 14, "PathPolyline"); + qmlRegisterType("QtQuick", 2, 14, "PathMultiline"); #endif #if QT_CONFIG(quick_pathview) qmlRegisterType(uri,major,minor,"PathView"); diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index 840a8c6a2c..d246ee7910 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -109,6 +109,11 @@ QT_BEGIN_NAMESPACE \li Yes \li Yes \li Yes + \li PathMultiLine + \li Yes + \li Yes + \li Yes + \li Yes \row \li PathQuad \li Yes @@ -246,7 +251,8 @@ bool QQuickPath::isClosed() const A path can contain the following path objects: \list \li \l PathLine - a straight line to a given position. - \li \l PathPolyline - a polyline specified as a list of normalized coordinates. + \li \l PathPolyline - a polyline specified as a list of coordinates. + \li \l PathMultiline - a list of polylines specified as a list of lists of coordinates. \li \l PathQuad - a quadratic Bezier curve to a given position with a control point. \li \l PathCubic - a cubic Bezier curve to a given position with two control points. \li \l PathArc - an arc to a given position with a radius. @@ -2416,9 +2422,14 @@ void QQuickPathPolyline::setPath(const QVariantList &path) pathList.append(c); } - if (m_path != pathList) { + setPath(pathList); +} + +void QQuickPathPolyline::setPath(const QVector &path) +{ + if (m_path != path) { const QPointF &oldStart = start(); - m_path = pathList; + m_path = path; const QPointF &newStart = start(); emit pathChanged(); if (oldStart != newStart) @@ -2446,6 +2457,146 @@ void QQuickPathPolyline::addToPath(QPainterPath &path, const QQuickPathData &/*d path.lineTo(m_path.at(i)); } + +/*! + \qmltype PathMultiline + \instantiates QQuickPathMultiline + \inqmlmodule QtQuick + \ingroup qtquick-animation-paths + \brief Defines a set of polylines through a list of lists of coordinates. + \since QtQuick 2.14 + + This element allows to define a list of polylines at once. + Each polyline in the list will be preceded by a \l{QPainterPath::moveTo}{moveTo} + command, effectively making each polyline a separate one. + The polylines in this list are supposed to be non-intersecting with each other. + In any case, when used in conjunction with a \l ShapePath, the containing ShapePath's + \l ShapePath::fillRule applies. + That is, with the default \c OddEvenFill and non intersecting shapes, the largest shape in the list defines an area to be filled; + areas where two shapes overlap are holes; areas where three shapes overlap are filled areas inside holes, etc. + + The example below creates a high voltage symbol by adding each path + of the symbol to the list of paths. + The coordinates of the vertices are normalized, and through the containing shape's + \l scale property, the path will be rescaled together with its containing shape. + + \qml + PathMultiline { + paths: [ + [Qt.point(0.5, 0.06698), + Qt.point(1, 0.93301), + Qt.point(0, 0.93301), + Qt.point(0.5, 0.06698)], + + [Qt.point(0.5, 0.12472), + Qt.point(0.95, 0.90414), + Qt.point(0.05, 0.90414), + Qt.point(0.5, 0.12472)], + + [Qt.point(0.47131, 0.32986), + Qt.point(0.36229, 0.64789), + Qt.point(0.51492, 0.58590), + Qt.point(0.47563, 0.76014), + Qt.point(0.44950, 0.73590), + Qt.point(0.46292, 0.83392), + Qt.point(0.52162, 0.75190), + Qt.point(0.48531, 0.76230), + Qt.point(0.57529, 0.53189), + Qt.point(0.41261, 0.59189), + Qt.point(0.53001, 0.32786), + Qt.point(0.47131, 0.32986)] + ] + } + \endqml + + \sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove +*/ + +/*! + \qmlproperty point QtQuick::PathMultiline::start + + This read-only property contains the beginning of the polylines. +*/ + +/*! + \qmlproperty list> QtQuick::PathMultiline::paths + + This property defines the vertices of the polylines. +*/ + +QQuickPathMultiline::QQuickPathMultiline(QObject *parent) : QQuickCurve(parent) +{ +} + +QVariantList QQuickPathMultiline::paths() const +{ + QVariantList res; + for (int j = 0; j < m_paths.length(); ++j) { + const QVector &path = m_paths.at(j); + QVariantList p; + for (int i = 0; i < path.length(); ++i) { + const QPointF &c = path.at(i); + p.append(QVariant::fromValue(c)); + } + res.append(p); + } + return res; +} + +void QQuickPathMultiline::setPaths(const QVariantList &paths) +{ + QVector> pathsList; + for (int j = 0; j < paths.length(); ++j) { + if (paths.at(j).type() != QVariant::List) + qWarning() << "QQuickPathMultiLine::setPaths: elements in argument not of type List"; + QVariantList path = paths.at(j).toList(); + QVector l; + for (int i = 0; i < path.length(); ++i) { + const QVariant &element = path.at(i); + const QVariant::Type elementType = element.type(); + if (elementType == QVariant::PointF || elementType == QVariant::Point) { + const QPointF c = element.toPointF(); + l.append(c); + } + } + if (l.size() >= 2) + pathsList.append(l); + } + + setPaths(pathsList); +} + +void QQuickPathMultiline::setPaths(const QVector> &paths) +{ + if (m_paths != paths) { + const QPointF &oldStart = start(); + m_paths = paths; + const QPointF &newStart = start(); + emit pathsChanged(); + if (oldStart != newStart) + emit startChanged(); + emit changed(); + } +} + +QPointF QQuickPathMultiline::start() const +{ + if (m_paths.size()) + return m_paths.first().first(); + return QPointF(); +} + +void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &) +{ + if (!m_paths.size()) + return; + for (const QVector &p: m_paths) { + path.moveTo(p.first()); + for (int i = 1; i < p.size(); ++i) + path.lineTo(p.at(i)); + } +} + QT_END_NAMESPACE #include "moc_qquickpath_p.cpp" diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h index 998f4e3123..aa3425ff6c 100644 --- a/src/quick/util/qquickpath_p.h +++ b/src/quick/util/qquickpath_p.h @@ -434,6 +434,7 @@ public: QVariantList path() const; void setPath(const QVariantList &path); + void setPath(const QVector &path); QPointF start() const; void addToPath(QPainterPath &path, const QQuickPathData &data) override; @@ -445,6 +446,30 @@ private: QVector m_path; }; +class Q_QUICK_PRIVATE_EXPORT QQuickPathMultiline : public QQuickCurve +{ + Q_OBJECT + Q_PROPERTY(QPointF start READ start NOTIFY startChanged) + Q_PROPERTY(QVariantList paths READ paths WRITE setPaths NOTIFY pathsChanged) +public: + QQuickPathMultiline(QObject *parent=nullptr); + + QVariantList paths() const; + void setPaths(const QVariantList &paths); + void setPaths(const QVector> &paths); + QPointF start() const; + void addToPath(QPainterPath &path, const QQuickPathData &) override; + +Q_SIGNALS: + void pathsChanged(); + void startChanged(); + +private: + QPointF absolute(const QPointF &relative) const; + + QVector> m_paths; +}; + struct QQuickCachedBezier { QQuickCachedBezier() {} diff --git a/tests/auto/quick/qquickshape/data/pathitem8.png b/tests/auto/quick/qquickshape/data/pathitem8.png new file mode 100644 index 0000000000..ee585958ec Binary files /dev/null and b/tests/auto/quick/qquickshape/data/pathitem8.png differ diff --git a/tests/auto/quick/qquickshape/data/pathitem8.qml b/tests/auto/quick/qquickshape/data/pathitem8.qml new file mode 100644 index 0000000000..9789ff90e0 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem8.qml @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Shapes 1.14 + +Item { + id: item + width: 200 + height: 150 + + Shape { + vendorExtensionsEnabled: false + objectName: "shape" + id: shape + anchors.fill: parent + + ShapePath { + strokeWidth: 4 + strokeColor: "red" + scale: Qt.size(shape.width - 1, shape.height - 1) + fillGradient: LinearGradient { + x1: 20; y1: 20 + x2: 180; y2: 130 + GradientStop { position: 0; color: "blue" } + GradientStop { position: 0.2; color: "green" } + GradientStop { position: 0.4; color: "red" } + GradientStop { position: 0.6; color: "yellow" } + GradientStop { position: 1; color: "cyan" } + } + strokeStyle: ShapePath.DashLine + dashPattern: [ 1, 4 ] + PathMultiline { + paths: [[Qt.point(20.0 / (item.width - 1.0), 20.0 / (item.height - 1.0)), + Qt.point(180.0 / (item.width - 1.0), 130.0 / (item.height - 1.0)), + Qt.point(20.0 / (item.width - 1.0), 130.0 / (item.height - 1.0)), + Qt.point(20.0 / (item.width - 1.0), 20.0 / (item.height - 1.0)) ], + [Qt.point( 0.45 , 0.67 ), + Qt.point( 0.414906666468 , 0.573581858547 ), + Qt.point( 0.32604722665 , 0.522278837048 ), + Qt.point( 0.225 , 0.540096189432 ), + Qt.point( 0.159046106882 , 0.618696978501 ), + Qt.point( 0.159046106882 , 0.721303021499 ), + Qt.point( 0.225 , 0.799903810568 ), + Qt.point( 0.32604722665 , 0.817721162952 ), + Qt.point( 0.414906666468 , 0.766418141453 ), + Qt.point( 0.45 , 0.67 ), + ], + [Qt.point( 0.69 , 0.75 ), + Qt.point( 0.668943999881 , 0.692149115128 ), + Qt.point( 0.61562833599 , 0.661367302229 ), + Qt.point( 0.555 , 0.672057713659 ), + Qt.point( 0.515427664129 , 0.719218187101 ), + Qt.point( 0.515427664129 , 0.780781812899 ), + Qt.point( 0.555 , 0.827942286341 ), + Qt.point( 0.61562833599 , 0.838632697771 ), + Qt.point( 0.668943999881 , 0.807850884872 ), + Qt.point( 0.69 , 0.75 ), + ]] + } + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index 174ada65a5..b3b8d2d148 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -59,6 +59,7 @@ private slots: void radialGrad(); void conicalGrad(); void renderPolyline(); + void renderMultiline(); }; tst_QQuickShape::tst_QQuickShape() @@ -343,6 +344,35 @@ void tst_QQuickShape::renderPolyline() QVERIFY2(res, qPrintable(errorMessage)); } +void tst_QQuickShape::renderMultiline() +{ + QScopedPointer window(createView()); + + window->setSource(testFileUrl("pathitem8.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + if ((QGuiApplication::platformName() == QLatin1String("offscreen")) + || (QGuiApplication::platformName() == QLatin1String("minimal"))) + QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimimal platforms", Abort); + + QImage img = window->grabWindow(); + QVERIFY(!img.isNull()); + + QImage refImg(testFileUrl("pathitem8.png").toLocalFile()); + QVERIFY(!refImg.isNull()); + + QString errorMessage; + const QImage actualImg = img.convertToFormat(refImg.format()); + const bool res = QQuickVisualTestUtil::compareImages(actualImg, refImg, &errorMessage); + if (!res) { // For visual inspection purposes. + QTest::qWait(5000); + const QString &tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + actualImg.save(tempLocation + QLatin1String("/pathitem8.png")); + } + QVERIFY2(res, qPrintable(errorMessage)); +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" diff --git a/tests/manual/scenegraph_lancelot/data/shape/shape_multiline.qml b/tests/manual/scenegraph_lancelot/data/shape/shape_multiline.qml new file mode 100644 index 0000000000..8524915bc4 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/shape/shape_multiline.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Shapes 1.14 + +Item { + id: root + width: 320 + height: 320 + + Shape { + vendorExtensionsEnabled: false + anchors.fill: parent + + ShapePath { + strokeWidth: 1 + strokeColor: "red" + fillColor: Qt.rgba(1,0,0,0.3) + scale: Qt.size(root.width - 1, root.height - 1) + PathMultiline { + paths: [ + [Qt.point(0.5, 0.06698), + Qt.point(1, 0.93301), + Qt.point(0, 0.93301), + Qt.point(0.5, 0.06698)], + + [Qt.point(0.5, 0.12472), + Qt.point(0.95, 0.90414), + Qt.point(0.05, 0.90414), + Qt.point(0.5, 0.12472)], + + [Qt.point(0.47131, 0.32986), + Qt.point(0.36229, 0.64789), + Qt.point(0.51492, 0.58590), + Qt.point(0.47563, 0.76014), + Qt.point(0.44950, 0.73590), + Qt.point(0.46292, 0.83392), + Qt.point(0.52162, 0.75190), + Qt.point(0.48531, 0.76230), + Qt.point(0.57529, 0.53189), + Qt.point(0.41261, 0.59189), + Qt.point(0.53001, 0.32786), + Qt.point(0.47131, 0.32986)] + ] + } + } + } +} -- cgit v1.2.3 From 2b433c3e89cf6ba4fca9a2da28562a2c8ae93c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Wed, 3 Jul 2019 13:43:20 +0200 Subject: Make sure timeZoneUpdated test cleans up properly if it fails It modifies the global time zone, so if it failed we want to make sure it doesn't affect the subsequent tests in this file. Task-number: QTBUG-73512 Change-Id: I3622acc3563ac27ede1b3f39d30e12bb844ac663 Reviewed-by: Edward Welbourne --- tests/auto/qml/qqmllocale/tst_qqmllocale.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp index cc13fb4b5f..a90749208c 100644 --- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp +++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include "../../shared/util.h" @@ -1270,13 +1272,21 @@ void tst_qqmllocale::timeZoneUpdated() // Set the timezone to Brisbane time, AEST-10:00 setTimeZone(QByteArray("Australia/Brisbane")); + QScopedPointer obj; + auto cleanup = qScopeGuard([&original, &obj] { + // Restore to original time zone + setTimeZone(original); + QMetaObject::invokeMethod(obj.data(), "resetTimeZone"); + }); + DateFormatter formatter; QQmlEngine e; e.rootContext()->setContextObject(&formatter); QQmlComponent c(&e, testFileUrl("timeZoneUpdated.qml")); - QScopedPointer obj(c.create()); + QVERIFY2(!c.isError(), qPrintable(c.errorString())); + obj.reset(c.create()); QVERIFY(obj); QVERIFY(obj->property("success").toBool()); @@ -1284,11 +1294,6 @@ void tst_qqmllocale::timeZoneUpdated() setTimeZone(QByteArray("Asia/Kolkata")); QMetaObject::invokeMethod(obj.data(), "check"); - - // Reset to original time - setTimeZone(original); - QMetaObject::invokeMethod(obj.data(), "resetTimeZone"); - QVERIFY(obj->property("success").toBool()); } #endif -- cgit v1.2.3 From 2519e4b73876102b46e72b795c96f1ea73c3d523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Wed, 3 Jul 2019 15:02:20 +0200 Subject: Fix tst_qqmllocale::timeZoneUpdated() test on Android Switching to the POSIX implementation of DaylightSavingTA() fixes it Fixes: QTBUG-77052 Task-number: QTBUG-73512 Change-Id: I24804f193ac6f85eb6764f656d351cdc272c7987 Reviewed-by: Edward Welbourne --- src/qml/jsruntime/qv4dateobject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index 1b26608bf3..bba88e5c9a 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -54,7 +54,7 @@ #include -#if defined(Q_OS_LINUX) && QT_CONFIG(timezone) +#if defined(Q_OS_LINUX) && QT_CONFIG(timezone) && !defined(Q_OS_ANDROID) /* See QTBUG-56899. Although we don't (yet) have a proper way to reset the system zone, the code below, that uses QTimeZone::systemTimeZone(), works -- cgit v1.2.3 From 1bfa31849d3a23bfdc3b60aec7f645de2e1f81d5 Mon Sep 17 00:00:00 2001 From: Paul Wicking Date: Thu, 18 Jul 2019 08:41:34 +0200 Subject: Doc: Fix typos in code snippets Fixes: QTBUG-77094 Change-Id: Ia974c4d8abeab48a206fb868ee5532d4aeae7319 Reviewed-by: Mitch Curtis --- src/qml/doc/src/cppintegration/definetypes.qdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc index 41bc9fd140..b7199393f7 100644 --- a/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -471,7 +471,7 @@ will be accessible to the \e attachee: class MessageBoardAttachedType : public QObject { Q_OBJECT - Q_PROPERTY(bool expired READ expired WRITE expired NOTIFY expiredChanged) + Q_PROPERTY(bool expired READ setExpired WRITE expired NOTIFY expiredChanged) public: MessageBoardAttachedType(QObject *parent); bool expired() const; @@ -493,7 +493,7 @@ class MessageBoard : public QObject { Q_OBJECT public: - static MessageBoard *qmlAttachedProperties(QObject *object) + static MessageBoardAttachedType *qmlAttachedProperties(QObject *object) { return new MessageBoardAttachedType(object); } -- cgit v1.2.3 From 35acf29a38d4b631dd27ce767466533223e8b2c4 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Fri, 5 Jul 2019 13:08:21 +0200 Subject: Support text color for color fonts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to support pen color for color fonts, we have to bake the color into the cache (since the cache contains actual color data and not alpha values). This is equivalent of 78caba7ae637bf4b33631c3425eb92ec3946c99e in Qt Base. [ChangeLog][Text] Added support for text color when using color fonts. Task-number: QTBUG-74761 Change-Id: I5910636c240bd4c0ec3f0b13db4e2f78d4b062ff Reviewed-by: Tor Arne Vestbø --- src/quick/scenegraph/qsgdefaultglyphnode.cpp | 2 +- src/quick/scenegraph/qsgdefaultglyphnode_p.cpp | 30 +++++++++++++++++++++----- src/quick/scenegraph/qsgdefaultglyphnode_p_p.h | 7 +++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/quick/scenegraph/qsgdefaultglyphnode.cpp b/src/quick/scenegraph/qsgdefaultglyphnode.cpp index 0d42102f36..cae0eda3f4 100644 --- a/src/quick/scenegraph/qsgdefaultglyphnode.cpp +++ b/src/quick/scenegraph/qsgdefaultglyphnode.cpp @@ -75,7 +75,7 @@ void QSGDefaultGlyphNode::update() QMargins margins(0, 0, 0, 0); if (m_style == QQuickText::Normal) { - m_material = new QSGTextMaskMaterial(font); + m_material = new QSGTextMaskMaterial(QVector4D(m_color.redF(), m_color.greenF(), m_color.blueF(), m_color.alphaF()), font); } else if (m_style == QQuickText::Outline) { QSGOutlinedTextMaterial *material = new QSGOutlinedTextMaterial(font); material->setStyleColor(m_styleColor); diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp index ce706d76f7..b9a22dd44b 100644 --- a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp +++ b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp @@ -399,10 +399,11 @@ public: } }; -QSGTextMaskMaterial::QSGTextMaskMaterial(const QRawFont &font, QFontEngine::GlyphFormat glyphFormat) +QSGTextMaskMaterial::QSGTextMaskMaterial(const QVector4D &color, const QRawFont &font, QFontEngine::GlyphFormat glyphFormat) : m_texture(nullptr) , m_glyphCache(nullptr) , m_font(font) + , m_color(color) { init(glyphFormat); } @@ -412,12 +413,30 @@ QSGTextMaskMaterial::~QSGTextMaskMaterial() delete m_texture; } +void QSGTextMaskMaterial::setColor(const QVector4D &color) +{ + if (m_color == color) + return; + + m_color = color; + + // If it is an RGB cache, then the pen color is actually part of the cache key + // so it has to be updated + if (m_glyphCache != nullptr && m_glyphCache->glyphFormat() == QFontEngine::Format_ARGB) + updateCache(QFontEngine::Format_ARGB); +} + void QSGTextMaskMaterial::init(QFontEngine::GlyphFormat glyphFormat) { Q_ASSERT(m_font.isValid()); setFlag(Blending, true); + updateCache(glyphFormat); +} + +void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat) +{ QOpenGLContext *ctx = const_cast(QOpenGLContext::currentContext()); Q_ASSERT(ctx != nullptr); @@ -437,20 +456,21 @@ void QSGTextMaskMaterial::init(QFontEngine::GlyphFormat glyphFormat) qreal devicePixelRatio = qsg_device_pixel_ratio(ctx); - QTransform glyphCacheTransform = QTransform::fromScale(devicePixelRatio, devicePixelRatio); if (!fontEngine->supportsTransformation(glyphCacheTransform)) glyphCacheTransform = QTransform(); - m_glyphCache = fontEngine->glyphCache(ctx, glyphFormat, glyphCacheTransform); + QColor color = glyphFormat == QFontEngine::Format_ARGB ? QColor::fromRgbF(m_color.x(), m_color.y(), m_color.z(), m_color.w()) : QColor(); + m_glyphCache = fontEngine->glyphCache(ctx, glyphFormat, glyphCacheTransform, color); if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) { - m_glyphCache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform); + m_glyphCache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform, color); fontEngine->setGlyphCache(ctx, m_glyphCache.data()); auto sg = QSGDefaultRenderContext::from(ctx); Q_ASSERT(sg); sg->registerFontengineForCleanup(fontEngine); } } + } void QSGTextMaskMaterial::populate(const QPointF &p, @@ -629,7 +649,7 @@ int QSGTextMaskMaterial::cacheTextureHeight() const QSGStyledTextMaterial::QSGStyledTextMaterial(const QRawFont &font) - : QSGTextMaskMaterial(font, QFontEngine::Format_A8) + : QSGTextMaskMaterial(QVector4D(), font, QFontEngine::Format_A8) { } diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h index b0a2788dd8..56084dea96 100644 --- a/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h +++ b/src/quick/scenegraph/qsgdefaultglyphnode_p_p.h @@ -68,15 +68,15 @@ class Geometry; class QSGTextMaskMaterial: public QSGMaterial { public: - QSGTextMaskMaterial(const QRawFont &font, QFontEngine::GlyphFormat glyphFormat = QFontEngine::Format_None); + QSGTextMaskMaterial(const QVector4D &color, const QRawFont &font, QFontEngine::GlyphFormat glyphFormat = QFontEngine::Format_None); virtual ~QSGTextMaskMaterial(); QSGMaterialType *type() const override; QSGMaterialShader *createShader() const override; int compare(const QSGMaterial *other) const override; - void setColor(const QColor &c) { m_color = QVector4D(c.redF(), c.greenF(), c.blueF(), c.alphaF()); } - void setColor(const QVector4D &color) { m_color = color; } + void setColor(const QColor &c) { setColor(QVector4D(c.redF(), c.greenF(), c.blueF(), c.alphaF())); } + void setColor(const QVector4D &color); const QVector4D &color() const { return m_color; } QSGTexture *texture() const { return m_texture; } @@ -94,6 +94,7 @@ public: private: void init(QFontEngine::GlyphFormat glyphFormat); + void updateCache(QFontEngine::GlyphFormat glyphFormat); QSGPlainTexture *m_texture; QExplicitlySharedDataPointer m_glyphCache; -- cgit v1.2.3 From f72db4de7f446c522db3f78b67c3b2bcc02b172a Mon Sep 17 00:00:00 2001 From: Nico Vertriest Date: Thu, 18 Jul 2019 15:15:23 +0200 Subject: Doc: Add comment on evaluation order bindings Task-number: QTBUG-73742 Change-Id: I42821823ee0315aa96da30798e35809cf0498f67 Reviewed-by: Mitch Curtis Reviewed-by: Paul Wicking --- src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc index a5ad6af4a2..e607666886 100644 --- a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc +++ b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc @@ -128,6 +128,9 @@ loops - it could indicate that the binding is being used for more than describin property relationships. Complex bindings can reduce code performance, readability, and maintainability. It may be a good idea to redesign components that have complex bindings, or at least factor the binding out into a separate function. +As a general rule, users should not rely on the evaluation order of bindings. + +\sa {Positioning with Anchors} \target qml-javascript-assignment -- cgit v1.2.3 From 982a397a520b5746dc8682b58bfe81270f75868f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Wed, 3 Jul 2019 17:25:55 +0200 Subject: Fix qqmlengine autotest for android Most of the fixes are due to the fact that qml files are embedded as resources Change-Id: Ief615fea608a839a4e0a09459cf60f500cbe8c72 Task-number: QTBUG-73512 Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmlengine/qqmlengine.pro | 2 ++ tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/auto/qml/qqmlengine/qqmlengine.pro b/tests/auto/qml/qqmlengine/qqmlengine.pro index d2eb92bfd5..8946201ea0 100644 --- a/tests/auto/qml/qqmlengine/qqmlengine.pro +++ b/tests/auto/qml/qqmlengine/qqmlengine.pro @@ -4,6 +4,8 @@ macx:CONFIG -= app_bundle include (../../shared/util.pri) +TESTDATA = data/* + SOURCES += tst_qqmlengine.cpp QT += core-private gui-private qml-private network testlib diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 66d50cfe39..77ebff313e 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -428,7 +428,7 @@ void tst_qqmlengine::trimComponentCache() engine.setIncubationController(&componentCache); QQmlComponent component(&engine, testFileUrl(file)); - QVERIFY(component.isReady()); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer object(component.create()); QVERIFY(object != nullptr); QCOMPARE(object->property("success").toBool(), true); @@ -742,13 +742,17 @@ public: CustomSelector(const QUrl &base):m_base(base){} virtual QUrl intercept(const QUrl &url, QQmlAbstractUrlInterceptor::DataType d) { - if (url.scheme() != QStringLiteral("file")) + if ((url.scheme() != QStringLiteral("file") && url.scheme() != QStringLiteral("qrc")) + || url.path().contains("QtQml")) return url; if (!m_interceptionPoints.contains(d)) return url; - if (url.path().endsWith("Test.2/qmldir"))//Special case - return QUrl::fromLocalFile(m_base.path() + "interception/module/intercepted/qmldir"); + if (url.path().endsWith("Test.2/qmldir")) {//Special case + QUrl url = m_base; + url.setPath(m_base.path() + "interception/module/intercepted/qmldir"); + return url; + } // Special case: with 5.10 we always add the implicit import, so we need to explicitly handle this case now if (url.path().endsWith("intercepted/qmldir")) return url; @@ -836,7 +840,7 @@ void tst_qqmlengine::urlInterceptor() QFETCH(QString, expectedAbsoluteUrl); QQmlEngine e; - e.addImportPath(testFileUrl("interception/imports").toLocalFile()); + e.addImportPath(testFileUrl("interception/imports").url()); CustomSelector cs(testFileUrl("")); cs.m_interceptionPoints = interceptionPoint; e.setUrlInterceptor(&cs); -- cgit v1.2.3 From 3db15da8e1109ef256a4a1a8a3cd6c643512d5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 18 Jul 2019 17:45:05 +0200 Subject: Fix qqmlecmascript failures for Android Some silly ignoreMessage failures on 32 bit systems.. QString::number((quint32)0xaabbccdd, 16) returns "0xaabbccdd" while QString::number((qint32)0xaabbccdd, 16) returns "0xffffffffaabbccdd" We therefore change to use quintptr (which will follow the quint32 codepath). Change-Id: I40530d8da83ee43862541f0e87684dc11ed07a53 Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index d603ca6907..00105c4225 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -2048,7 +2048,7 @@ void tst_qqmlecmascript::functionErrors() QObject *resource = qobject_cast(QQmlProperty::read(object, "a").value()); warning = url + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function"); - warning = warning.arg(QString::number((qintptr)resource, 16)); + warning = warning.arg(QString::number((quintptr)resource, 16)); QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); // we expect a meaningful warning to be printed. QMetaObject::invokeMethod(object, "retrieveScarceResource"); delete object; @@ -4573,7 +4573,7 @@ void tst_qqmlecmascript::scarceResources_other() eo = qobject_cast(QQmlProperty::read(object, "a").value()); QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. expectedWarning = varComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function"); - expectedWarning = expectedWarning.arg(QString::number((qintptr)eo, 16)); + expectedWarning = expectedWarning.arg(QString::number((quintptr)eo, 16)); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. QMetaObject::invokeMethod(object, "retrieveScarceResource"); QVERIFY(!object->property("scarceResourceCopy").isValid()); // due to exception, assignment will NOT have occurred. @@ -4647,7 +4647,7 @@ void tst_qqmlecmascript::scarceResources_other() eo = qobject_cast(QQmlProperty::read(object, "a").value()); QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. expectedWarning = variantComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function"); - expectedWarning = expectedWarning.arg(QString::number((qintptr)eo, 16)); + expectedWarning = expectedWarning.arg(QString::number((quintptr)eo, 16)); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. QMetaObject::invokeMethod(object, "retrieveScarceResource"); QVERIFY(!object->property("scarceResourceCopy").isValid()); // due to exception, assignment will NOT have occurred. -- cgit v1.2.3 From 55dbb7b90c32442c4fb9a16563990913ec0468f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 18 Jul 2019 15:22:49 +0200 Subject: Fix tst_qqmlapplicationengine failures for Android The tests doesn't make much sense on Android so we just skip them Change-Id: Ie93858f7041657d6e6d05229454f970a761efc0b Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp index a9c28a0911..8787a43884 100644 --- a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp +++ b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp @@ -96,6 +96,9 @@ void tst_qqmlapplicationengine::basicLoading() // will break. void tst_qqmlapplicationengine::testNonResolvedPath() { +#ifdef Q_OS_ANDROID + QSKIP("Android stores QML files in resources, and the path to a resource cannot be relative in this case"); +#endif { // NOTE NOTE NOTE! Missing testFileUrl is *WANTED* here! We want a // non-resolved URL. @@ -117,6 +120,9 @@ void tst_qqmlapplicationengine::testNonResolvedPath() void tst_qqmlapplicationengine::application_data() { +#ifdef Q_OS_ANDROID + QSKIP("Cannot launch external process on Android"); +#endif QTest::addColumn("qmlFile"); QTest::addColumn("expectedStdErr"); -- cgit v1.2.3 From 4173415250b932184a9ab8f32fc55038e592e2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 18 Jul 2019 16:03:19 +0200 Subject: Fix qqmldirparser test failure on Android Resources were missing from the binary Change-Id: I59dc692a034fe851505223ec0c1203ece3c520ba Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmldirparser/qqmldirparser.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/auto/qml/qqmldirparser/qqmldirparser.pro b/tests/auto/qml/qqmldirparser/qqmldirparser.pro index dda74b1ef9..b5373a6e8f 100644 --- a/tests/auto/qml/qqmldirparser/qqmldirparser.pro +++ b/tests/auto/qml/qqmldirparser/qqmldirparser.pro @@ -6,3 +6,5 @@ macx:CONFIG -= app_bundle SOURCES += tst_qqmldirparser.cpp include (../../shared/util.pri) + +TESTDATA = data/* -- cgit v1.2.3 From ce545a5bf291eec7c623e6f691842eca18b09ddc Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Fri, 14 Jun 2019 16:54:14 +0200 Subject: Introduce qmlRegisterSingletonInstance This method is intended as a replacement for common setContextProperty use cases, where the user is only using one single engine and has already created the object. Task-number: QTBUG-73064 Change-Id: Ib9ec023a0ad679aa22e90ebcb4a0c07622459c61 Reviewed-by: Simon Hausmann --- src/qml/doc/src/qmlfunctions.qdoc | 97 +++++++++++++++++++++++++++- src/qml/qml/qqml.cpp | 20 ++++++ src/qml/qml/qqml.h | 9 +++ src/qml/qml/qqmlprivate.h | 7 ++ tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 40 ++++++++++++ 5 files changed, 171 insertions(+), 2 deletions(-) diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index ab54b5fd1d..9c106558fd 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -487,8 +487,7 @@ A QObject singleton type may be referenced via the type name with which it was registered, and this typename may be used as the target in a \l Connections type or otherwise used as any other type id would. - One exception to this is that a QObject singleton type property may not be aliased (because the - singleton type name does not identify an object within the same component as any other item). + One exception to this is that a QObject singleton type property may not be aliased. \b{NOTE:} A QObject singleton type instance returned from a singleton type provider is owned by the QML engine unless the object has explicit QQmlEngine::CppOwnership flag set. @@ -618,6 +617,100 @@ files using the singleton. */ +/*! + \fn int qmlRegisterSingletonInstance(const char *uri, int versionMajor, int + versionMinor, const char *typeName, QObject* cppObject) + \relates QQmlEngine + \since 5.14 + + This function is used to register a singleton object \a cppObject, with a + particular \a uri and \a typeName. Its version is a combination of \a + versionMajor and \a versionMinor. + + Installing a singleton type into a URI allows you to provide arbitrary + functionality (methods and properties) to QML code without requiring + individual instances of the type to be instantiated by the client. + + Use this function to register an object of the given type T as a singleton + type. + + A QObject singleton type may be referenced via the type name with which it + was registered; in turn this type name may be used as the target in a \l + Connections type, or like any other type ID. However, there's one + exception: a QObject singleton type property can't be aliased because the + singleton type name does not identify an object within the same component + as any other item. + + \note \a cppObject must outlive the QML engine in which it is used. + Moreover, \cppObject must have the same thread affinity as the engine. If + you want separate singleton instances for multiple engines, you need to use + \l {qmlRegisterSingletonType}. See \l{Threads and QObjects} for more + information about thread safety. + + Usage: + \code + // First, define your QObject which provides the functionality. + class SingletonTypeExample : public QObject + { + Q_OBJECT + Q_PROPERTY(int someProperty READ someProperty WRITE setSomeProperty NOTIFY somePropertyChanged) + + public: + explicit SingletonTypeExample(QObject* parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE int doSomething() + { + setSomeProperty(5); + return m_someProperty; + } + + int someProperty() const { return m_someProperty; } + void setSomeProperty(int val) { + if (m_someProperty != val) { + m_someProperty = val; + emit somePropertyChanged(val); + } + } + + signals: + void somePropertyChanged(int newValue); + + private: + int m_someProperty = 0; + }; + \endcode + + \code + // Second, create an instance of the object + + // allocate example before the engine to ensure that it outlives it + QScopedPointer example(new SingletonTypeExample); + QQmlEngine engine; + + // Third, register the singleton type provider with QML by calling this + // function in an initialization function. + qmlRegisterSingletonInstance("Qt.example.qobjectSingleton", 1, 0, "MyApi", example.get()); + \endcode + + + In order to use the registered singleton type in QML, you must import the + URI with the corresponding version. + \qml + import QtQuick 2.0 + import Qt.example.qobjectSingleton 1.0 + Item { + id: root + property int someValue: MyApi.someProperty + + Component.onCompleted: { + console.log(MyApi.doSomething()) + } + } + \endqml + + \sa qmlRegisterSingletonType + */ + /*! \fn int qmlRegisterType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName); \relates QQmlEngine diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 613816e3f7..8a50b51b5d 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -76,6 +76,26 @@ int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *q return QQmlMetaType::typeId(uri, versionMajor, versionMinor, qmlName); } +// From qqmlprivate.h +QObject *QQmlPrivate::RegisterSingletonFunctor::operator()(QQmlEngine *qeng, QJSEngine *) +{ + if (qeng->thread() != m_object->thread()) { + QQmlError error; + error.setDescription(QLatin1String("Registered object must live in the same thread as the engine it was registered with")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + if (alreadyCalled) { + QQmlError error; + error.setDescription(QLatin1String("Singleton registered by registerSingletonInstance must only be accessed from one engine")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + alreadyCalled = true; + qeng->setObjectOwnership(m_object, QQmlEngine::CppOwnership); + return m_object; +}; + /* This method is "over generalized" to allow us to (potentially) register more types of things in the future without adding exported symbols. diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 4f3bfb76b0..bfd1c88b28 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -667,6 +667,15 @@ inline int qmlRegisterSingletonType(const char *uri, int versionMajor, int versi return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &api); } +template +inline auto qmlRegisterSingletonInstance(const char *uri, int versionMajor, int versionMinor, + const char *typeName, T *cppObject) -> typename std::enable_if::value, int>::type +{ + QQmlPrivate::RegisterSingletonFunctor registrationFunctor; + registrationFunctor.m_object = cppObject; + return qmlRegisterSingletonType(uri, versionMajor, versionMinor, typeName, registrationFunctor); +} + inline int qmlRegisterSingletonType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName) { if (url.isRelative()) { diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index eff78195d9..e6dd5e0b16 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -322,6 +322,13 @@ namespace QQmlPrivate int Q_QML_EXPORT qmlregister(RegistrationType, void *); void Q_QML_EXPORT qmlunregister(RegistrationType, quintptr); + struct Q_QML_EXPORT RegisterSingletonFunctor + { + QObject *operator()(QQmlEngine *, QJSEngine *); + + QObject *m_object; + bool alreadyCalled = false; + }; } QT_END_NAMESPACE diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 77ebff313e..aae42e9ebb 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -1037,6 +1037,46 @@ void tst_qqmlengine::singletonInstance() { qmlRegisterSingletonType("Qt.test",1,0,"NotAmbiguous", [](QQmlEngine* qeng, QJSEngine* jeng) -> QObject* {return CppSingleton::create(qeng, jeng);}); // test that overloads for qmlRegisterSingleton are not ambiguous } + { + // Register QObject* directly + CppSingleton single; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", + &single); + QQmlEngine engine2; + CppSingleton *singlePtr = engine2.singletonInstance(id); + QVERIFY(singlePtr); + QCOMPARE(&single, singlePtr); + QVERIFY(engine2.objectOwnership(singlePtr) == QQmlEngine::CppOwnership); + } + + { + CppSingleton single; + QQmlEngine engineA; + QQmlEngine engineB; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", &single); + auto singlePtr = engineA.singletonInstance(id); + QVERIFY(singlePtr); + singlePtr = engineA.singletonInstance(id); // accessing the singleton multiple times from the same engine is fine + QVERIFY(singlePtr); + QTest::ignoreMessage(QtMsgType::QtCriticalMsg, ": qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer."); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, ": Singleton registered by registerSingletonInstance must only be accessed from one engine"); + QCOMPARE(&single, singlePtr); + auto noSinglePtr = engineB.singletonInstance(id); + QVERIFY(!noSinglePtr); + } + + { + CppSingleton single; + QThread newThread {}; + single.moveToThread(&newThread); + QCOMPARE(single.thread(), &newThread); + QQmlEngine engineB; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", &single); + QTest::ignoreMessage(QtMsgType::QtCriticalMsg, ": qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer."); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, ": Registered object must live in the same thread as the engine it was registered with"); + auto noSinglePtr = engineB.singletonInstance(id); + QVERIFY(!noSinglePtr); + } { // Invalid types -- cgit v1.2.3 From a95f3795ae94616fb7b3d72ac71b0b38c6bba341 Mon Sep 17 00:00:00 2001 From: Paul Wicking Date: Fri, 19 Jul 2019 06:53:12 +0200 Subject: Doc: fix code snippet Task-number: QTBUG-77094 Change-Id: I9058bf7b65e8d390327af0624df611de4965f1e4 Reviewed-by: Mitch Curtis --- src/qml/doc/src/cppintegration/definetypes.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc index b7199393f7..2e8102bd65 100644 --- a/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -471,7 +471,7 @@ will be accessible to the \e attachee: class MessageBoardAttachedType : public QObject { Q_OBJECT - Q_PROPERTY(bool expired READ setExpired WRITE expired NOTIFY expiredChanged) + Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged) public: MessageBoardAttachedType(QObject *parent); bool expired() const; -- cgit v1.2.3 From 1dad2029a5bdbc40b244f72f995d70d27ed98e62 Mon Sep 17 00:00:00 2001 From: Dimitrios Apostolou Date: Fri, 19 Jul 2019 14:02:31 +0200 Subject: Always waitForWindow after centerOnScreen() Because centerOnScreen asks the window manager to move the window, but does not wait for it. This is applied in the same spirit as this change in qtquickcontrols2: https://codereview.qt-project.org/c/qt/qtquickcontrols2/+/268200 These tests appear slightly flaky on the Grafana dashboard, this commit might help. Change-Id: I30d3f4717aca435c94fb1a447c4b5c51021da3be Reviewed-by: Shawn Rutledge --- tests/auto/quick/qquickflickable/tst_qquickflickable.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index 65a08ce87f..2314b82e8c 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -1652,6 +1652,7 @@ void tst_qquickflickable::flickTwiceUsingTouches() QQuickViewTestUtil::moveMouseAway(window.data()); window->show(); QVERIFY(window->rootObject() != nullptr); + QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickFlickable *flickable = qobject_cast(window->rootObject()); QVERIFY(flickable != nullptr); @@ -1994,6 +1995,7 @@ void tst_qquickflickable::nestedMouseAreaUsingTouch() QQuickViewTestUtil::moveMouseAway(window.data()); window->show(); QVERIFY(window->rootObject() != nullptr); + QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickFlickable *flickable = qobject_cast(window->rootObject()); QVERIFY(flickable != nullptr); @@ -2489,6 +2491,7 @@ void tst_qquickflickable::synchronousDrag() QQuickViewTestUtil::moveMouseAway(window); window->show(); QVERIFY(window->rootObject() != nullptr); + QVERIFY(QTest::qWaitForWindowActive(window)); QQuickFlickable *flickable = qobject_cast(window->rootObject()); QVERIFY(flickable != nullptr); -- cgit v1.2.3 From b0e19e1d08e17a7fd76008447be0f788ebf73f1f Mon Sep 17 00:00:00 2001 From: Tasuku Suzuki Date: Tue, 16 Jul 2019 10:34:11 +0900 Subject: Fix label name of features.qml-object-model Change-Id: I8a35e51f87cfa49bdec834cca65ef169dd76b750 Reviewed-by: Edward Welbourne --- src/qmlmodels/configure.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qmlmodels/configure.json b/src/qmlmodels/configure.json index 2b4fa4f6e2..84eefed261 100644 --- a/src/qmlmodels/configure.json +++ b/src/qmlmodels/configure.json @@ -7,7 +7,7 @@ "features": { "qml-object-model" : { - "label": "QML list model", + "label": "QML object model", "purpose": "Provides the ObjectModel and Instantiator QML types.", "section": "QML", "output": [ "privateFeature" ] -- cgit v1.2.3 From da14688140550879e376e71cf273b16494e6c3c4 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Mon, 22 Jul 2019 13:41:11 +0200 Subject: Fix nullptr handling in binding Fixes: QTBUG-77027 Change-Id: I61d5a20329ffe95af810b89e338eee2bc10bfe04 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlproperty.cpp | 12 ++++++++++-- .../qml/qqmlproperty/data/nullPropertyBinding.qml | 22 ++++++++++++++++++++++ tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp | 17 +++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/auto/qml/qqmlproperty/data/nullPropertyBinding.qml diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index c8166695ba..5f57e0eca1 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1216,10 +1216,18 @@ bool QQmlPropertyPrivate::write(QObject *object, if (propertyType == variantType && !isUrl && propertyType != qMetaTypeId>() && !property.isQList()) { return property.writeProperty(object, const_cast(value.constData()), flags); } else if (property.isQObject()) { - QQmlMetaObject valMo = rawMetaObjectForType(enginePriv, variantType); + QVariant val = value; + int varType = variantType; + if (variantType == QMetaType::Nullptr) { + // This reflects the fact that you can assign a nullptr to a QObject pointer + // Without the change to QObjectStar, rawMetaObjectForType would not give us a QQmlMetaObject + varType = QMetaType::QObjectStar; + val = QVariant(QMetaType::QObjectStar, nullptr); + } + QQmlMetaObject valMo = rawMetaObjectForType(enginePriv, varType); if (valMo.isNull()) return false; - QObject *o = *static_cast(value.constData()); + QObject *o = *static_cast(val.constData()); QQmlMetaObject propMo = rawMetaObjectForType(enginePriv, propertyType); if (o) diff --git a/tests/auto/qml/qqmlproperty/data/nullPropertyBinding.qml b/tests/auto/qml/qqmlproperty/data/nullPropertyBinding.qml new file mode 100644 index 0000000000..4fffc7aead --- /dev/null +++ b/tests/auto/qml/qqmlproperty/data/nullPropertyBinding.qml @@ -0,0 +1,22 @@ +import QtQuick 2.12 + +Item { + id: root + + width: 640 + height: 480 + + property bool toggle: false + property Item bound + property string message: "defined" + + readonly property Item item: root.toggle ? root : null + + Binding { target: root; property: "bound"; value: item} + + function tog() { + console.info(root.bound ? root.bound.message: "undefined") + root.toggle = !root.toggle + return 42; + } +} diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 04c61ec11b..67da768f73 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -144,6 +144,7 @@ private slots: void deeplyNestedObject(); void readOnlyDynamicProperties(); void aliasToIdWithMatchingQmlFileNameOnCaseInsensitiveFileSystem(); + void nullPropertyBinding(); void floatToStringPrecision_data(); void floatToStringPrecision(); @@ -2059,6 +2060,22 @@ void tst_qqmlproperty::aliasToIdWithMatchingQmlFileNameOnCaseInsensitiveFileSyst QVERIFY(property.isValid()); } +// QTBUG-77027 +void tst_qqmlproperty::nullPropertyBinding() +{ + const QUrl url = testFileUrl("nullPropertyBinding.qml"); + QQmlEngine engine; + QQmlComponent component(&engine, url); + QScopedPointer root(component.create()); + QVERIFY(root); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "undefined"); + QMetaObject::invokeMethod(root.get(), "tog"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "defined"); + QMetaObject::invokeMethod(root.get(), "tog"); + QTest::ignoreMessage(QtMsgType::QtInfoMsg, "undefined"); + QMetaObject::invokeMethod(root.get(), "tog"); +} + void tst_qqmlproperty::floatToStringPrecision_data() { QTest::addColumn("propertyName"); -- cgit v1.2.3 From c0e0c755a1c927299607f0af83fadb4a0af6ce20 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Mon, 22 Jul 2019 09:02:58 +0200 Subject: Support top level generator functions Extends grammar to support generator functions in QML components and adjusts codegen accordingly The corresponding test case must be blacklisted in tst_qmlmin, as qmlmin cannot handle yield statements Fixes: QTBUG-77096 Change-Id: I47d45dd56289cdf073b41932a585259d3052de04 Reviewed-by: Qt CI Bot Reviewed-by: Simon Hausmann --- src/qml/compiler/qqmlirbuilder.cpp | 6 +++--- src/qml/compiler/qv4compilerscanfunctions_p.h | 2 +- src/qml/parser/qqmljs.g | 8 ++++++++ src/qml/parser/qqmljsast_p.h | 4 ++-- src/qml/qml/qqmlobjectcreator.cpp | 6 +++++- tests/auto/qml/qmlmin/tst_qmlmin.cpp | 2 ++ .../qml/qqmlecmascript/data/generatorFunction.qml | 22 +++++++++++++++++++++ .../auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 23 ++++++++++++++++++++++ 8 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tests/auto/qml/qqmlecmascript/data/generatorFunction.qml diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index aab3f8e9d6..171dc641d3 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -908,7 +908,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) { - if (QQmlJS::AST::FunctionDeclaration *funDecl = QQmlJS::AST::cast(node->sourceElement)) { + if (QQmlJS::AST::FunctionExpression *funDecl = node->sourceElement->asFunctionDefinition()) { CompiledFunctionOrExpression *foe = New(); foe->node = funDecl; foe->parentNode = funDecl; @@ -1770,7 +1770,7 @@ QVector JSCodeGen::generateJSCodeForFunctionsAndBindings(const QListprogram); Q_ASSERT(f.parentNode && f.parentNode != document->program); - QQmlJS::AST::FunctionDeclaration *function = QQmlJS::AST::cast(f.node); + auto function = f.node->asFunctionDefinition(); if (function) { scan.enterQmlFunction(function); @@ -1794,7 +1794,7 @@ QVector JSCodeGen::generateJSCodeForFunctionsAndBindings(const QListprogram); - QQmlJS::AST::FunctionDeclaration *function = QQmlJS::AST::cast(node); + QQmlJS::AST::FunctionExpression *function = node->asFunctionDefinition(); QString name; if (function) diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index f67db030a2..2de80eac44 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -88,7 +88,7 @@ public: const QString &name); void leaveEnvironment(); - void enterQmlFunction(QQmlJS::AST::FunctionDeclaration *ast) + void enterQmlFunction(QQmlJS::AST::FunctionExpression *ast) { enterFunction(ast, false); } protected: diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 82b040232a..6c9760e472 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -1396,6 +1396,14 @@ UiObjectMember: FunctionDeclarationWithTypes; } break; ./ +UiObjectMember: GeneratorExpression; +/. + case $rule_number: { + auto node = new (pool) AST::UiSourceElement(sym(1).Node); + sym(1).Node = node; + } break; +./ + UiObjectMember: VariableStatement; /. case $rule_number: { diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 1502298d14..39194068bf 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -3351,7 +3351,7 @@ public: SourceLocation firstSourceLocation() const override { - if (FunctionDeclaration *funDecl = cast(sourceElement)) + if (FunctionExpression *funDecl = sourceElement->asFunctionDefinition()) return funDecl->firstSourceLocation(); else if (VariableStatement *varStmt = cast(sourceElement)) return varStmt->firstSourceLocation(); @@ -3361,7 +3361,7 @@ public: SourceLocation lastSourceLocation() const override { - if (FunctionDeclaration *funDecl = cast(sourceElement)) + if (FunctionExpression *funDecl = sourceElement->asFunctionDefinition()) return funDecl->lastSourceLocation(); else if (VariableStatement *varStmt = cast(sourceElement)) return varStmt->lastSourceLocation(); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index d5681b3449..f89608cd5d 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include @@ -1135,7 +1136,10 @@ void QQmlObjectCreator::setupFunctions() if (!property->isVMEFunction()) continue; - function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); + if (runtimeFunction->isGenerator()) + function = QV4::GeneratorFunction::create(qmlContext, runtimeFunction); + else + function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); _vmeMetaObject->setVmeMethod(property->coreIndex(), function); } } diff --git a/tests/auto/qml/qmlmin/tst_qmlmin.cpp b/tests/auto/qml/qmlmin/tst_qmlmin.cpp index 696ec66246..cae833cd60 100644 --- a/tests/auto/qml/qmlmin/tst_qmlmin.cpp +++ b/tests/auto/qml/qmlmin/tst_qmlmin.cpp @@ -130,6 +130,8 @@ void tst_qmlmin::initTestCase() invalidFiles << "tests/auto/qml/qjsengine/script/com/trolltech/syntaxerror/__init__.js"; invalidFiles << "tests/auto/qml/debugger/qqmlpreview/data/broken.qml"; invalidFiles << "tests/auto/qml/qqmllanguage/data/fuzzed.2.qml"; + // generatorFunction.qml is not invalid per se, but the minifier cannot handle yield statements + invalidFiles << "tests/auto/qml/qqmlecmascript/data/generatorFunction.qml"; #endif } diff --git a/tests/auto/qml/qqmlecmascript/data/generatorFunction.qml b/tests/auto/qml/qqmlecmascript/data/generatorFunction.qml new file mode 100644 index 0000000000..66bb642f34 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/generatorFunction.qml @@ -0,0 +1,22 @@ +import QtQml 2.12 + +QtObject { + id: root + property bool test1: false; + property bool test2: false; + property bool test3: false; + property bool done: false; + function *gen() { + yield 1 + yield 2 + yield 3 + } + + Component.onCompleted: { + let it = root.gen(); + root.test1 = (it.next().value == 1); + root.test2 = (it.next().value == 2); + root.test3 = (it.next().value == 3); + root.done = it.next().done; + } +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 00105c4225..b44fe9766c 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -233,6 +233,7 @@ private slots: void functionAssignment_afterBinding(); void eval(); void function(); + void topLevelGeneratorFunction(); void qtbug_10696(); void qtbug_11606(); void qtbug_11600(); @@ -6348,6 +6349,28 @@ void tst_qqmlecmascript::function() delete o; } +// QTBUG-77096 +void tst_qqmlecmascript::topLevelGeneratorFunction() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("generatorFunction.qml")); + + QScopedPointer o {component.create()}; + QVERIFY(o != nullptr); + + // check that generator works correctly in QML + QCOMPARE(o->property("test1").toBool(), true); + QCOMPARE(o->property("test2").toBool(), true); + QCOMPARE(o->property("test3").toBool(), true); + QCOMPARE(o->property("done").toBool(), true); + + // check that generator is accessible from C++ + QVariant returnedValue; + QMetaObject::invokeMethod(o.get(), "gen", Q_RETURN_ARG(QVariant, returnedValue)); + auto it = returnedValue.value(); + QCOMPARE(it.property("next").callWithInstance(it).property("value").toInt(), 1); +} + // Test the "Qt.include" method void tst_qqmlecmascript::include() { -- cgit v1.2.3 From d94dd247ecd6753150dc2a7ef6be4ccb482e9423 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 18 Jul 2019 16:06:26 +0200 Subject: Do not search for Singletons from more recent versions This would break importing older versions of a module, as we would try to locate a singleton which does not exist in this version. Fixes: QTBUG-77102 Change-Id: I78be1ec111d2be26a14b2a94bbf743cf6238cddd Reviewed-by: Qt CI Bot Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlimport.cpp | 13 ++++++- .../imports/MyPlugin/MyComponent_0_9.qml | 14 +++++++ .../imports/MyPlugin/MyComponent_1_0.qml | 15 ++++++++ .../imports/MyPlugin/MySettings_1_0.qml | 7 ++++ .../data/QTBUG-77102/imports/MyPlugin/qmldir | 6 +++ .../qqmlimport/data/QTBUG-77102/main.0.9.fail.qml | 11 ++++++ .../qml/qqmlimport/data/QTBUG-77102/main.0.9.qml | 11 ++++++ .../qml/qqmlimport/data/QTBUG-77102/main.1.0.qml | 11 ++++++ .../qqmlimport/data/QTBUG-77102/main.nonumber.qml | 11 ++++++ tests/auto/qml/qqmlimport/tst_qqmlimport.cpp | 45 ++++++++++++++++++++++ 10 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_0_9.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_1_0.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MySettings_1_0.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/qmldir create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.fail.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/main.1.0.qml create mode 100644 tests/auto/qml/qqmlimport/data/QTBUG-77102/main.nonumber.qml diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 5a1364473e..ba8dce4b6e 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -465,9 +465,18 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QListqmlDirComponents; + const int importMajorVersion = import->majversion; + const int importMinorVersion = import->minversion; + auto shouldSkipSingleton = [importMajorVersion, importMinorVersion](int singletonMajorVersion, int singletonMinorVersion) -> bool { + return importMajorVersion != -1 && + (singletonMajorVersion > importMajorVersion || (singletonMajorVersion == importMajorVersion && singletonMinorVersion > importMinorVersion)); + }; + ConstIterator cend = components.constEnd(); for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) { + if (shouldSkipSingleton(cit->majorVersion, cit->minorVersion)) + continue; QQmlImports::CompositeSingletonReference ref; ref.typeName = cit->typeName; ref.prefix = set.prefix; @@ -478,7 +487,9 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QListuri, import->majversion)) { - module->walkCompositeSingletons([&resultList, &set](const QQmlType &singleton) { + module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) { + if (shouldSkipSingleton(singleton.majorVersion(), singleton.minorVersion())) + return; QQmlImports::CompositeSingletonReference ref; ref.typeName = singleton.elementName(); ref.prefix = set.prefix; diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_0_9.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_0_9.qml new file mode 100644 index 0000000000..a63e2d121c --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_0_9.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 + +Rectangle { + width: 50 + height: 50 + + color: "yellow" + + Text { + anchors.centerIn: parent + text: "0.9" + + } +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_1_0.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_1_0.qml new file mode 100644 index 0000000000..d9cff582bb --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MyComponent_1_0.qml @@ -0,0 +1,15 @@ + +import QtQuick 2.0 + +Rectangle { + width: 50 + height: 50 + + color: "orange" + + Text { + anchors.centerIn: parent + text: "1.0" + + } +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MySettings_1_0.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MySettings_1_0.qml new file mode 100644 index 0000000000..63ad6ba1a5 --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/MySettings_1_0.qml @@ -0,0 +1,7 @@ +pragma Singleton + +import QtQuick 2.0 + +Item { + property int baseWidth: 50 +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/qmldir b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/qmldir new file mode 100644 index 0000000000..57b8c55f81 --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/imports/MyPlugin/qmldir @@ -0,0 +1,6 @@ +module MyPlugin + +MyComponent 0.9 MyComponent_0_9.qml + +MyComponent 1.0 MyComponent_1_0.qml +singleton MySettings 1.0 MySettings_1_0.qml diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.fail.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.fail.qml new file mode 100644 index 0000000000..0fbcec607a --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.fail.qml @@ -0,0 +1,11 @@ +import QtQuick 2.12 +import MyPlugin 0.9 + +Item { + width: MySettings.baseWidth + height: 100 + + MyComponent { + anchors.centerIn: parent + } +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.qml new file mode 100644 index 0000000000..53fc25699c --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.0.9.qml @@ -0,0 +1,11 @@ +import QtQuick 2.12 +import MyPlugin 0.9 + +Item { + width: 100 + height: 100 + + MyComponent { + anchors.centerIn: parent + } +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.1.0.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.1.0.qml new file mode 100644 index 0000000000..1f6244068b --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.1.0.qml @@ -0,0 +1,11 @@ +import QtQuick 2.12 +import MyPlugin 1.0 + +Item { + width: MySettings.baseWidth + height: 100 + + MyComponent { + anchors.centerIn: parent + } +} diff --git a/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.nonumber.qml b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.nonumber.qml new file mode 100644 index 0000000000..dea00c0163 --- /dev/null +++ b/tests/auto/qml/qqmlimport/data/QTBUG-77102/main.nonumber.qml @@ -0,0 +1,11 @@ +import QtQuick 2.12 +import "imports/MyPlugin" + +Item { + width: MySettings.baseWidth + height: 100 + + MyComponent { + anchors.centerIn: parent + } +} diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index a3cb68fdcb..a9657eba1d 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -45,6 +45,7 @@ private slots: void completeQmldirPaths_data(); void completeQmldirPaths(); void interceptQmldir(); + void singletonVersionResolution(); void cleanup(); }; @@ -212,6 +213,50 @@ void tst_QQmlImport::interceptQmldir() QVERIFY(!obj.isNull()); } +// QTBUG-77102 +void tst_QQmlImport::singletonVersionResolution() +{ + QQmlEngine engine; + engine.addImportPath(testFile("QTBUG-77102/imports")); + { + // Singleton with higher version is simply ignored when importing lower version of plugin + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("QTBUG-77102/main.0.9.qml")); + QVERIFY(component.isReady()); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + { + // but the singleton is not accessible + QQmlComponent component(&engine); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression {".*ReferenceError: MySettings is not defined$"} ); + component.loadUrl(testFileUrl("QTBUG-77102/main.0.9.fail.qml")); + QVERIFY(component.isReady()); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + { + // unless a version which is high enough is imported + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("QTBUG-77102/main.1.0.qml")); + QVERIFY(component.isReady()); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + auto item = qobject_cast(obj.get()); + QCOMPARE(item->width(), 50); + } + { + // or when there is no number because we are importing from a path + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("QTBUG-77102/main.nonumber.qml")); + QVERIFY(component.isReady()); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + auto item = qobject_cast(obj.get()); + QCOMPARE(item->width(), 50); + } +} + QTEST_MAIN(tst_QQmlImport) -- cgit v1.2.3 From 53e7927fdf56617cfcd3b0e6c9f6db6e0b3d6483 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Wed, 24 Jul 2019 15:43:49 +0200 Subject: qmllint: Improve signal handler recommendations - Fix the case where multiple unqualified accesses would be mapped to the same signal (wrong in all but one cases), as the event parameter has the same name and we were using a QHash. Fixed by using QMultiHash and searching for the matching signal handler (by location) - Recommend arrow functions for single line event handlers Change-Id: I3cbb85fe0e49b454908ca03b4c86318ef02e364c Reviewed-by: Simon Hausmann --- tests/auto/qml/qmllint/data/SignalHandler.qml | 10 +++++++++- tests/auto/qml/qmllint/main.cpp | 5 ++++- tools/qmllint/findunqualified.cpp | 4 +++- tools/qmllint/scopetree.cpp | 25 +++++++++++++++++++------ tools/qmllint/scopetree.h | 5 +++-- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/tests/auto/qml/qmllint/data/SignalHandler.qml b/tests/auto/qml/qmllint/data/SignalHandler.qml index bdf503e3dc..865277cedb 100644 --- a/tests/auto/qml/qmllint/data/SignalHandler.qml +++ b/tests/auto/qml/qmllint/data/SignalHandler.qml @@ -1,5 +1,13 @@ import QtQuick 2.0 MouseArea { - onDoubleClicked: console.log(mouse); + onDoubleClicked: { + console.log(mouse); + // do further things + } + onClicked: console.info(mouse) + onPositionChanged: { + console.log(mouse) + } + onPressAndHold: console.warn(mouse) } diff --git a/tests/auto/qml/qmllint/main.cpp b/tests/auto/qml/qmllint/main.cpp index 038826790b..1069aa5a33 100644 --- a/tests/auto/qml/qmllint/main.cpp +++ b/tests/auto/qml/qmllint/main.cpp @@ -110,7 +110,10 @@ void TestQmllint::testUnqualified_data() QTest::newRow("FromRootDirectParentDirect") << QStringLiteral("FromRootDirectParent.qml") << QStringLiteral("x: parent.unqualified") << 8 << 12; QTest::newRow("FromRootDirectParentAccess") << QStringLiteral("FromRootDirectParent.qml") << QStringLiteral("property int check: parent.x") << 12 << 29; // access injected name from signal - QTest::newRow("SignalHandler") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onDoubleClicked: function(mouse) {...") << 4 << 34; + QTest::newRow("SignalHandler1") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onDoubleClicked: function(mouse) {...") << 5 << 21; + QTest::newRow("SignalHandler2") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onPositionChanged: function(mouse) {...") << 10 << 21; + QTest::newRow("SignalHandlerShort1") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onClicked: (mouse) => {...") << 8 << 29; + QTest::newRow("SignalHandlerShort2") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onPressAndHold: (mouse) => {...") << 12 << 34; } diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 3abdfcfa53..e6a72df44c 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -503,7 +503,9 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) } else { auto method = m_currentScope->methods()[signal]; for (auto const ¶m : method.parameterNames()) { - m_currentScope->insertSignalIdentifier(param, method, uisb->statement->firstSourceLocation()); + auto firstSourceLocation = uisb->statement->firstSourceLocation(); + bool hasMultilineStatementBody = uisb->statement->lastSourceLocation().startLine > firstSourceLocation.startLine; + m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, hasMultilineStatementBody); } } return true; diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index b064117dd4..f58fde061e 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -30,6 +30,8 @@ #include "qcoloroutput_p.h" +#include + #include ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) @@ -66,10 +68,10 @@ void ScopeTree::insertQMLIdentifier(QString id) m_currentScopeQMLIdentifiers.insert(id); } -void ScopeTree::insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc) +void ScopeTree::insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody) { Q_ASSERT(m_scopeType == ScopeType::QMLScope); - m_injectedSignalIdentifiers.insert(id, {method, loc}); + m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody}); } void ScopeTree::insertPropertyIdentifier(QString id) @@ -164,13 +166,24 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHashisIdInjectedFromSignal(idLocationPair.first)) { auto qmlScope = currentScope->getCurrentQMLScope(); - MethodUsage methodUsage = qmlScope->m_injectedSignalIdentifiers[idLocationPair.first]; + auto methodUsages = qmlScope->m_injectedSignalIdentifiers.values(idLocationPair.first); + auto location = idLocationPair.second; + // sort the list of signal handlers by their occurrence in the source code + // then, we select the first one whose location is after the unqualified id + // and go one step backwards to get the one which we actually need + std::sort(methodUsages.begin(), methodUsages.end(), [](const MethodUsage m1, const MethodUsage m2) { + return m1.loc.startLine < m2.loc.startLine || (m1.loc.startLine == m2.loc.startLine && m1.loc.startColumn < m2.loc.startColumn); + }); + auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), [&location](MethodUsage methodUsage) { + return location.startLine < methodUsage.loc.startLine || (location.startLine == methodUsage.loc.startLine && location.startColumn < methodUsage.loc.startColumn); + }); + auto methodUsage = *(--oneBehindIt); colorOut.write("Note:", Info); colorOut.write(idLocationPair.first + QString::asprintf(" is accessible in this scope because you are handling a signal at %d:%d\n", methodUsage.loc.startLine, methodUsage.loc.startColumn), Normal); colorOut.write("Consider using a function instead\n", Normal); IssueLocationWithContext context {code, methodUsage.loc}; colorOut.write(context.beforeText + QLatin1Char(' ')); - colorOut.write("function(", Hint); + colorOut.write(methodUsage.hasMultilineHandlerBody ? "function(" : "(", Hint); const auto parameters = methodUsage.method.parameterNames(); for (int numParams = parameters.size(); numParams > 0; --numParams) { colorOut.write(parameters.at(parameters.size() - numParams), Hint); @@ -178,10 +191,10 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHash ", Hint); colorOut.write(" {...", Normal); } - colorOut.write("\n", Normal); + colorOut.write("\n\n\n", Normal); } for (auto const& childScope: currentScope->m_childScopes) { workQueue.enqueue(childScope); diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index cb495584c1..a65ed30d61 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -56,6 +56,7 @@ struct MethodUsage { LanguageUtils::FakeMetaMethod method; QQmlJS::AST::SourceLocation loc; + bool hasMultilineHandlerBody; }; class ColorOutput; @@ -70,7 +71,7 @@ public: void insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope); void insertQMLIdentifier(QString id); - void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc); + void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody); void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding bool isIdInCurrentScope(QString const &id) const; @@ -88,7 +89,7 @@ public: private: QSet m_currentScopeJSIdentifiers; QSet m_currentScopeQMLIdentifiers; - QHash m_injectedSignalIdentifiers; // need iteration in insertion order for hints, rarely more than 4 parameters needed + QMultiHash m_injectedSignalIdentifiers; QMap m_methods; QVector> m_accessedIdentifiers; QVector m_childScopes; -- cgit v1.2.3 From 66368ffdd93e63a182aa6961f821ca14290ecf80 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Tue, 23 Jul 2019 16:10:24 +0200 Subject: qmllint: Improve parent handling - Do not warn about parent access in unknown components This avoids false positive warnings when an imported component could not be found (or when it actually was not imported). We still warn about the component which could not be found, so the user is still informed that something is not right. We also still emit a warning when we know the properties of a component, and parent is not one of them. - Do not recommend the use of parent to address the root components properties. For this to work, we would need to know whether the root component reparents its children or not. Moreover, id lookups are actually faster than parent lookups. Change-Id: I83d0e71e4bf20d34a3e6d836c2b123b2bf0d416e Reviewed-by: Simon Hausmann --- .../auto/qml/qmllint/data/FromRootDirectParent.qml | 14 --------- .../qml/qmllint/data/nonSpuriousParentWarning.qml | 8 +++++ .../qml/qmllint/data/spuriousParentWarning.qml | 7 +++++ tests/auto/qml/qmllint/main.cpp | 34 +++++++++++++++++++--- tools/qmllint/findunqualified.cpp | 6 ++-- tools/qmllint/findunqualified.h | 1 + tools/qmllint/scopetree.cpp | 25 ++++++++-------- tools/qmllint/scopetree.h | 2 +- 8 files changed, 64 insertions(+), 33 deletions(-) delete mode 100644 tests/auto/qml/qmllint/data/FromRootDirectParent.qml create mode 100644 tests/auto/qml/qmllint/data/nonSpuriousParentWarning.qml create mode 100644 tests/auto/qml/qmllint/data/spuriousParentWarning.qml diff --git a/tests/auto/qml/qmllint/data/FromRootDirectParent.qml b/tests/auto/qml/qmllint/data/FromRootDirectParent.qml deleted file mode 100644 index c0bfd0f26b..0000000000 --- a/tests/auto/qml/qmllint/data/FromRootDirectParent.qml +++ /dev/null @@ -1,14 +0,0 @@ -import QtQuick 2.0 - -Item { - id: root - property int unqualified: 42 - - Item { - x: unqualified // user defined property from root - } - - QtObject { - property int check: x // existing property from root - } -} diff --git a/tests/auto/qml/qmllint/data/nonSpuriousParentWarning.qml b/tests/auto/qml/qmllint/data/nonSpuriousParentWarning.qml new file mode 100644 index 0000000000..a59b736929 --- /dev/null +++ b/tests/auto/qml/qmllint/data/nonSpuriousParentWarning.qml @@ -0,0 +1,8 @@ +import QtQuick 2.12 +import QtQml 2.12 + +Item { + QtObject { + property int x: parent.x + } +} diff --git a/tests/auto/qml/qmllint/data/spuriousParentWarning.qml b/tests/auto/qml/qmllint/data/spuriousParentWarning.qml new file mode 100644 index 0000000000..1323593031 --- /dev/null +++ b/tests/auto/qml/qmllint/data/spuriousParentWarning.qml @@ -0,0 +1,7 @@ +import QtQuick 2.12 + +Item { + Unknown { + property int x: parent.x + } +} diff --git a/tests/auto/qml/qmllint/main.cpp b/tests/auto/qml/qmllint/main.cpp index 1069aa5a33..928575bc82 100644 --- a/tests/auto/qml/qmllint/main.cpp +++ b/tests/auto/qml/qmllint/main.cpp @@ -40,6 +40,7 @@ private Q_SLOTS: void test_data(); void testUnqualified(); void testUnqualified_data(); + void testUnqualifiedNoSpuriousParentWarning(); private: QString m_qmllintPath; }; @@ -73,13 +74,14 @@ void TestQmllint::test_data() void TestQmllint::testUnqualified() { + auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); QFETCH(QString, filename); QFETCH(QString, warningMessage); QFETCH(int, warningLine); QFETCH(int, warningColumn); filename.prepend(QStringLiteral("data/")); QStringList args; - args << QStringLiteral("-U") << filename; + args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; QProcess process; process.start(m_qmllintPath, args); @@ -106,9 +108,6 @@ void TestQmllint::testUnqualified_data() // access property of root object QTest::newRow("FromRootDirect") << QStringLiteral("FromRoot.qml") << QStringLiteral("x: root.unqualified") << 9 << 16; // new property QTest::newRow("FromRootAccess") << QStringLiteral("FromRoot.qml") << QStringLiteral("property int check: root.x") << 13 << 33; // builtin property - // access property of root object from direct child - QTest::newRow("FromRootDirectParentDirect") << QStringLiteral("FromRootDirectParent.qml") << QStringLiteral("x: parent.unqualified") << 8 << 12; - QTest::newRow("FromRootDirectParentAccess") << QStringLiteral("FromRootDirectParent.qml") << QStringLiteral("property int check: parent.x") << 12 << 29; // access injected name from signal QTest::newRow("SignalHandler1") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onDoubleClicked: function(mouse) {...") << 5 << 21; QTest::newRow("SignalHandler2") << QStringLiteral("SignalHandler.qml") << QStringLiteral("onPositionChanged: function(mouse) {...") << 10 << 21; @@ -117,6 +116,33 @@ void TestQmllint::testUnqualified_data() } +void TestQmllint::testUnqualifiedNoSpuriousParentWarning() +{ + auto qmlImportDir = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); + { + QString filename = QLatin1String("spuriousParentWarning.qml"); + filename.prepend(QStringLiteral("data/")); + QStringList args; + args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; + QProcess process; + process.start(m_qmllintPath, args); + QVERIFY(process.waitForFinished()); + QVERIFY(process.exitStatus() == QProcess::NormalExit); + QVERIFY(process.exitCode() == 0); + } + { + QString filename = QLatin1String("nonSpuriousParentWarning.qml"); + filename.prepend(QStringLiteral("data/")); + QStringList args; + args << QStringLiteral("-U") << filename << QStringLiteral("-I") << qmlImportDir; + QProcess process; + process.start(m_qmllintPath, args); + QVERIFY(process.waitForFinished()); + QVERIFY(process.exitStatus() == QProcess::NormalExit); + QVERIFY(process.exitCode()); + } +} + void TestQmllint::test() { QFETCH(QString, filename); diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index e6a72df44c..69e1473975 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -325,7 +325,9 @@ void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString na break; } } else { - qDebug() << name << "not found"; + m_colorOut.write(QLatin1String("warning: "), Warning); + m_colorOut.write(name + QLatin1String(" was not found. Did you add all import paths?\n")); + m_unknownImports.insert(name); break; } } @@ -526,7 +528,7 @@ bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) auto name = idexp->name; if (!m_exportedName2MetaObject.contains(name.toString())) { m_currentScope->addIdToAccssedIfNotInParentScopes( - { name.toString(), idexp->firstSourceLocation() }); + { name.toString(), idexp->firstSourceLocation() }, m_unknownImports); } return true; } diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h index 937194d142..8fc8257bef 100644 --- a/tools/qmllint/findunqualified.h +++ b/tools/qmllint/findunqualified.h @@ -57,6 +57,7 @@ private: QString m_rootId; QString m_filePath; QSet> m_alreadySeenImports; + QSet m_unknownImports; ColorOutput m_colorOut; struct OutstandingConnection {QString targetName; ScopeTree *scope; QQmlJS::AST::UiObjectDefinition *uiod;}; diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp index f58fde061e..2eff3fa319 100644 --- a/tools/qmllint/scopetree.cpp +++ b/tools/qmllint/scopetree.cpp @@ -86,8 +86,14 @@ bool ScopeTree::isIdInCurrentScope(const QString &id) const return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); } -void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair &id_loc_pair) { - if (!isIdInCurrentScope(id_loc_pair.first)) { +void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair &id_loc_pair, const QSet& unknownImports) { + // also do not add id if it is parent + // parent is almost always defined valid in QML, and if we could not find a definition for the current QML component + // not skipping "parent" will lead to many false positives + // Moreover, if the top level item is Item or inherits from it, it will have a parent property to which we would point the user + // which makes for a very nonsensical warning + auto qmlScope = getCurrentQMLScope(); + if (!isIdInCurrentScope(id_loc_pair.first) && !(id_loc_pair.first == QLatin1String("parent") && qmlScope && unknownImports.contains(qmlScope->name()))) { m_accessedIdentifiers.push_back(id_loc_pair); } } @@ -148,20 +154,15 @@ bool ScopeTree::recheckIdentifiers(const QString& code, const QHashscopeType() != ScopeType::QMLScope) { parentScope = parentScope->m_parentScope; } - bool directChild = parentScope && parentScope->isVisualRootScope(); colorOut.write("Note: ", Info); colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); - if (directChild) { - colorOut.write(QLatin1String(" As the current element is a direct child of the root element,\n you can qualify the access with parent to avoid this warning:\n"), Normal); - } else { - colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); - if (rootId == QLatin1String("")) { - colorOut.write("Note: ", Warning); - colorOut.write(("You first have to give the root element an id\n")); - } + colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); + if (rootId == QLatin1String("")) { + colorOut.write("Note: ", Warning); + colorOut.write(("You first have to give the root element an id\n")); } colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); - colorOut.write( (directChild ? QLatin1String("parent") : rootId) + QLatin1Char('.'), Hint); + colorOut.write(rootId + QLatin1Char('.'), Hint); colorOut.write(issueLocationWithContext.issueText.toString(), Normal); colorOut.write(issueLocationWithContext.afterText + QLatin1Char('\n'), Normal); } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) { diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h index a65ed30d61..872a509123 100644 --- a/tools/qmllint/scopetree.h +++ b/tools/qmllint/scopetree.h @@ -75,7 +75,7 @@ public: void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding bool isIdInCurrentScope(QString const &id) const; - void addIdToAccssedIfNotInParentScopes(QPair const& id_loc_pair); + void addIdToAccssedIfNotInParentScopes(QPair const& id_loc_pair, const QSet& unknownImports); bool isVisualRootScope() const; QString name() const; -- cgit v1.2.3 From cbe445dd915760d81a467b5285bacd208e44e365 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Wed, 24 Jul 2019 08:42:17 +0200 Subject: linter: Cover all supported JavaScript functions + qDebug/commented out code cleanup Change-Id: Id6a4410391eed2fbf6ff0dd7aaffa1de4d5b892c Reviewed-by: Simon Hausmann --- tools/qmllint/findunqualified.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp index 69e1473975..0d1cbf5823 100644 --- a/tools/qmllint/findunqualified.cpp +++ b/tools/qmllint/findunqualified.cpp @@ -38,6 +38,7 @@ #include #include #include +#include QDebug &operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc); @@ -225,8 +226,7 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) break; } case UiObjectMember::Kind_UiEnumDeclaration: { - auto enumDeclaration = static_cast(initMembers->member); - qDebug() << "enumdecl" << enumDeclaration->name; + // nothing to do break; } case UiObjectMember::Kind_UiObjectBinding: { @@ -235,8 +235,6 @@ FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) } case UiObjectMember::Kind_UiObjectDefinition: { // creates nothing accessible - /*auto objectDefinition = static_cast(initMembers->member); - qDebug() << "objdef" << objectDefinition->qualifiedTypeNameId->name;*/ break; } case UiObjectMember::Kind_UiPublicMember: { @@ -549,15 +547,19 @@ FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDir m_colorOut.insertColorMapping(Normal, ColorOutput::DefaultColor); m_colorOut.insertColorMapping(Hint, ColorOutput::GreenForeground); QLatin1String jsGlobVars[] = { - QLatin1String ("Array"), QLatin1String("Boolean"), QLatin1String("Date"), QLatin1String("Function"), QLatin1String("Math"), QLatin1String("Number"), QLatin1String("Object"), QLatin1String("RegExp"), QLatin1String("String"), - QLatin1String("Error"), QLatin1String("EvalError"), QLatin1String("RangeError"), QLatin1String("ReferenceError"), QLatin1String("SyntaxError"), QLatin1String("TypeError"), QLatin1String("URIError"), - QLatin1String("encodeURI"), QLatin1String("encodeURIComponent"), QLatin1String("decodeURI"), QLatin1String("decodeURIComponent"), QLatin1String("escape"), QLatin1String("unescape"), - QLatin1String("isFinite"), QLatin1String("isNanN"), QLatin1String("parseFloat"), QLatin1String("parseInt"), - QLatin1String("eval"), QLatin1String("console"), QLatin1String("print"), QLatin1String("gc"), + /* Not listed on the MDN page; browser and QML extensions: */ + // console/debug api + QLatin1String("console"), QLatin1String("print"), + // garbage collector + QLatin1String("gc"), + // i18n QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"), - QLatin1String("XMLHttpRequest"), QLatin1String("JSON"), QLatin1String("Promise"), - QLatin1String("undefined") + // XMLHttpRequest + QLatin1String("XMLHttpRequest") }; + for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr; ++globalName) { + m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), QQmlJS::AST::VariableScope::Const); + } for (const auto& jsGlobVar: jsGlobVars) m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const); } @@ -597,7 +599,6 @@ void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::Functi m_currentScope->insertJSIdentifier(name, VariableScope::Const); } } - // qDebug() << fexpr->firstSourceLocation() << "function expression" << fexpr->name; QString name = fexpr->name.toString(); if (name.isEmpty()) name = ""; -- cgit v1.2.3 From ad4960b40461c8b83599fe71540086182adecb10 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 26 Jul 2019 10:40:22 +0200 Subject: Restore compatibility with Qt 5 for VisualDataModel/DataModelGroup This change amends commit af3438cf29925bfcf4ff1a8fa04276580b66ce37 by fixing the visibility of not only Package but also VisualDataModel and VisualDataModelGroup. This is a prospective fix for various failures in qtlocation. Change-Id: I831422fa5d87c4cbaf45a5d766a77ba150d932da Reviewed-by: Alexandru Croitor --- src/qmlmodels/qqmlmodelsmodule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qmlmodels/qqmlmodelsmodule.cpp b/src/qmlmodels/qqmlmodelsmodule.cpp index 5195f9baad..884b7f78b2 100644 --- a/src/qmlmodels/qqmlmodelsmodule.cpp +++ b/src/qmlmodels/qqmlmodelsmodule.cpp @@ -71,6 +71,8 @@ void QQmlModelsModule::defineModule() qmlRegisterCustomType(uri, 2, 0, "ListModel", new QQmlListModelParser); #endif #if QT_CONFIG(qml_delegate_model) + qmlRegisterType(uri, 2, 0, "VisualDataModel"); + qmlRegisterType(uri, 2, 0, "VisualDataGroup"); qmlRegisterType(uri, 2, 1, "DelegateModel"); qmlRegisterType(uri, 2, 1, "DelegateModelGroup"); qmlRegisterType(uri, 2, 0, "Package"); -- cgit v1.2.3 From 10bbc7095f9ae6980269dac403b4741ff30caa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Fri, 19 Jul 2019 15:08:08 +0200 Subject: Fix qqmlimport failures for Android Task-number: QTBUG-73512 Change-Id: I50f69f2f071dca2adde1134991963b5c9220219c Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmlimport/qqmlimport.pro | 5 +++++ tests/auto/qml/qqmlimport/tst_qqmlimport.cpp | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/auto/qml/qqmlimport/qqmlimport.pro b/tests/auto/qml/qqmlimport/qqmlimport.pro index c8b0f68d9f..ba80547f4e 100644 --- a/tests/auto/qml/qqmlimport/qqmlimport.pro +++ b/tests/auto/qml/qqmlimport/qqmlimport.pro @@ -6,3 +6,8 @@ osx:CONFIG -= app_bundle SOURCES += tst_qqmlimport.cpp include (../../shared/util.pri) + +TESTDATA = data/* \ + MyPluginSupported/* \ + MyPluginUnsupported/* \ + FormFromQmlDir/* diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index a9657eba1d..ca1e52ad2c 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -78,7 +78,7 @@ void tst_QQmlImport::testDesignerSupported() QVERIFY(window->errors().isEmpty()); QString warningString("%1:30:1: module does not support the designer \"MyPluginUnsupported\" \n import MyPluginUnsupported 1.0\r \n ^ "); -#ifndef Q_OS_WIN +#if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) warningString.remove('\r'); #endif warningString = warningString.arg(testFileUrl("testfile_unsupported.qml").toString()); @@ -130,6 +130,9 @@ void tst_QQmlImport::uiFormatLoading() void tst_QQmlImport::importPathOrder() { +#ifdef Q_OS_ANDROID + QSKIP("QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath) returns bogus path on Android, but its nevertheless unusable."); +#endif QStringList expectedImportPaths; QString appDirPath = QCoreApplication::applicationDirPath(); QString qml2Imports = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); -- cgit v1.2.3 From 1d40ed9d22d0d1bd525932bd41875cbaf1d577d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Fri, 19 Jul 2019 14:10:06 +0200 Subject: Fix qqmlexpression failures for Android Should load from resource on Android... Task-number: QTBUG-73512 Change-Id: I29f74e896000aeb2b42e27dd739c505d7838b605 Reviewed-by: Simon Hausmann --- tests/auto/qml/qqmlexpression/tst_qqmlexpression.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/auto/qml/qqmlexpression/tst_qqmlexpression.cpp b/tests/auto/qml/qqmlexpression/tst_qqmlexpression.cpp index 1decc04ad2..0ba29d6b6a 100644 --- a/tests/auto/qml/qqmlexpression/tst_qqmlexpression.cpp +++ b/tests/auto/qml/qqmlexpression/tst_qqmlexpression.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -125,10 +126,12 @@ void tst_qqmlexpression::expressionFromDataComponent() QQmlEngine engine; QQmlComponent c(&engine); - QUrl url = testFileUrl("expressionFromDataComponent.qml"); + const QString fn(QLatin1String("expressionFromDataComponent.qml")); + QUrl url = testFileUrl(fn); + QString path = testFile(fn); { - QFile f(url.toLocalFile()); + QFile f(path); QVERIFY(f.open(QIODevice::ReadOnly)); c.setData(f.readAll(), url); } -- cgit v1.2.3 From f1d213b4039e38bb9ceebaca9167fcb722a0c16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Fri, 26 Jul 2019 18:17:27 +0200 Subject: Fix bug in QQmlEngine::setImportPathList() when it had a resource path The bug is really in QQmlImportDatabase::setImportPathList(). It was missing the same conversions that was done in QQmlImportDatabase::addImportPath(), so it failed to use a resource path as a import path because it did not convert ":/foo" to "qrc:/foo". We therefore just use addImportPath() to ensure the paths are converted properly. Before this, several autotests in tests/auto/qml/qqmllanguage failed on Android, since they were calling QQmlEngine::setImportPathList() where the list had resource paths. Task-number: QTBUG-73512 Change-Id: Idc64f5ad20ec665df7cb57ea1c346bc0975c3b0d Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlimport.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 5102fb111c..7c9c0da01a 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1958,7 +1958,9 @@ void QQmlImportDatabase::setImportPathList(const QStringList &paths) if (qmlImportTrace()) qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths; - fileImportPath = paths; + fileImportPath.clear(); + for (auto it = paths.crbegin(); it != paths.crend(); ++it) + addImportPath(*it); // Our existing cached paths may have been invalidated clearDirCache(); -- cgit v1.2.3 From 42872e720b2d25501587b0ed95f183d2dd9c8441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Fri, 19 Jul 2019 12:12:05 +0200 Subject: Fix debug output for QQmlError when url is a resource It failed to show the content of the file that had an error if the file was embedded as a resource. Change-Id: Ib8e0627919fa1f7956588ac80179adb8539df0e3 Reviewed-by: Simon Hausmann --- src/qml/qml/qqmlerror.cpp | 5 +++-- tests/auto/qml/qqmlerror/tst_qqmlerror.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/qml/qml/qqmlerror.cpp b/src/qml/qml/qqmlerror.cpp index 739d5ce4cd..0b94ed3b49 100644 --- a/src/qml/qml/qqmlerror.cpp +++ b/src/qml/qml/qqmlerror.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qqmlerror.h" +#include "qqmlfile.h" #include "qqmlsourcecoordinate_p.h" #include @@ -307,8 +308,8 @@ QDebug operator<<(QDebug debug, const QQmlError &error) QUrl url = error.url(); - if (error.line() > 0 && url.scheme() == QLatin1String("file")) { - QString file = url.toLocalFile(); + if (error.line() > 0 && (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc"))) { + QString file = QQmlFile::urlToLocalFileOrQrc(url); QFile f(file); if (f.open(QIODevice::ReadOnly)) { QByteArray data = f.readAll(); diff --git a/tests/auto/qml/qqmlerror/tst_qqmlerror.cpp b/tests/auto/qml/qqmlerror/tst_qqmlerror.cpp index 7a7185e909..f282e60417 100644 --- a/tests/auto/qml/qqmlerror/tst_qqmlerror.cpp +++ b/tests/auto/qml/qqmlerror/tst_qqmlerror.cpp @@ -195,7 +195,7 @@ void tst_qqmlerror::debug() } { - QUrl url(dataDirectoryUrl().resolved(QUrl("test.txt"))); + QUrl url = testFileUrl("test.txt"); QQmlError error; error.setUrl(url); error.setDescription("An Error"); -- cgit v1.2.3 From 65d9d4b930387ff67ebaba8e81c08cb8a417d035 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 29 Jul 2019 11:29:13 +0200 Subject: Re-enable threaded render loop with Vulkan on Linux Change-Id: I05440c54b99ddb6aac9a47e8b33a00be41c13055 Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgrenderloop.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index d38b5649c7..7545896972 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -249,14 +249,6 @@ QSGRenderLoop *QSGRenderLoop::instance() loopType = BasicRenderLoop; switch (rhiSupport->rhiBackend()) { - case QRhi::Vulkan: -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - // ### to be investigated (Mesa/Gnome deadlocks on - // resize with threaded+Vulkan (but not threaded+GL)) - loopType = BasicRenderLoop; -#endif - break; - case QRhi::D3D11: // D3D11 is forced to 'basic' always for now. The threaded loop's model may // not be suitable for DXGI due to the possibility of having the main -- cgit v1.2.3 From 25c61c5de86d8ca4debd9a2956702fdb58f42981 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 29 Jul 2019 11:25:48 +0200 Subject: Avoid locking up on resize with threaded loop and the rhi This was visible on X11 only because there beginFrame() happened to fail once or twice with out-of-date swapchain when there were a lot of resizes in a row. Handle this case correctly by waking up the main thread as appropriate. Change-Id: I67dc18522e1c05070267fd355095324f48259276 Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgthreadedrenderloop.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index db8e17a8e6..98a493f6ae 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -686,10 +686,11 @@ void QSGRenderThread::syncAndRender(QImage *grabImage) syncResultedInChanges = false; QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); - bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage; - bool syncRequested = (pendingUpdate & SyncRequest) || grabImage; - bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; + const bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage; + const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage; + const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest; pendingUpdate = 0; + const bool grabRequested = grabImage != nullptr; QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); // Begin the frame before syncing -> sync is where we may invoke @@ -732,13 +733,26 @@ void QSGRenderThread::syncAndRender(QImage *grabImage) // try again later if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate) QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest))); + + // Before returning we need to ensure the same wake up logic that + // would have happened if beginFrame() had suceeded. + if (exposeRequested) { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui"); + waitCondition.wakeOne(); + mutex.unlock(); + } else if (syncRequested && !grabRequested) { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui like sync would do"); + mutex.lock(); + waitCondition.wakeOne(); + mutex.unlock(); + } return; } } if (syncRequested) { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync"); - sync(exposeRequested, grabImage != nullptr); + sync(exposeRequested, grabRequested); } #ifndef QSG_NO_RENDER_TIMING if (profileFrames) -- cgit v1.2.3 From 595a90f99117bb9b588065a4492a6cc7ac9e1e34 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 26 Jul 2019 15:56:39 +0200 Subject: Have an autorelease pool for each frame on the render thread Required with Metal. Drawables and various system resources get autoreleased and there is no pool to handle these on the SG render thread by default. This may present a slight perf hit in debug builds due to QMacAutoReleasePool doing smart but - for us - unnecessary stuff every time (so in our case, every frame) but will do for now. This complements the QRhiMetal change for not holding onto the drawable when skipping the present. The pool is essential then to prevent nextDrawable from starving and so blocking. Task-number: QTBUG-76953 Change-Id: Iaf803a0e20504d6b349d3564eda1677868fb29ef Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgthreadedrenderloop.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 98a493f6ae..93853f57d4 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -902,6 +902,10 @@ void QSGRenderThread::run() QQuickProfiler::registerAnimationCallback(); while (active) { +#ifdef Q_OS_DARWIN + QMacAutoReleasePool frameReleasePool; +#endif + if (window) { if (enableRhi) { if (!rhi) { -- cgit v1.2.3 From 3cff8b2718ad182e39047a551ccdb8a72b2b2060 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 29 Jul 2019 13:56:32 +0200 Subject: Make openvg compile The plugin is not tested in any form in CI, and some of the internal changes in the RHI patch has some consequences here as well. Adapt as necessary. Have not tested the plugin in practice but it compiles now. Change-Id: I482fa34802d0bd4d44570f852298c1227cf0bb71 Reviewed-by: Andy Nichols --- src/plugins/scenegraph/openvg/qsgopenvgcontext.cpp | 12 ++++++++---- src/plugins/scenegraph/openvg/qsgopenvgcontext_p.h | 10 ++++++++-- src/plugins/scenegraph/openvg/qsgopenvglayer.cpp | 8 +++++++- src/plugins/scenegraph/openvg/qsgopenvglayer.h | 10 ++++++++++ src/plugins/scenegraph/openvg/qsgopenvgpainternode.cpp | 1 + src/plugins/scenegraph/openvg/qsgopenvgrenderloop.cpp | 4 +++- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/plugins/scenegraph/openvg/qsgopenvgcontext.cpp b/src/plugins/scenegraph/openvg/qsgopenvgcontext.cpp index 76ebb7c4ee..1736a2098e 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvgcontext.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvgcontext.cpp @@ -68,10 +68,14 @@ QSGOpenVGRenderContext::QSGOpenVGRenderContext(QSGContext *context) } -void QSGOpenVGRenderContext::initialize(void *context) +void QSGOpenVGRenderContext::initialize(const QSGRenderContext::InitParams *params) { - m_vgContext = static_cast(context); - QSGRenderContext::initialize(context); + const InitParams *vgparams = static_cast(params); + if (vgparams->sType != INIT_PARAMS_MAGIC) + qFatal("Invalid OpenVG render context parameters"); + + m_vgContext = vgparams->context; + QSGRenderContext::initialize(params); } void QSGOpenVGRenderContext::invalidate() @@ -160,7 +164,7 @@ QSGInternalRectangleNode *QSGOpenVGContext::createInternalRectangleNode() return new QSGOpenVGInternalRectangleNode(); } -QSGInternalImageNode *QSGOpenVGContext::createInternalImageNode() +QSGInternalImageNode *QSGOpenVGContext::createInternalImageNode(QSGRenderContext *) { return new QSGOpenVGInternalImageNode(); } diff --git a/src/plugins/scenegraph/openvg/qsgopenvgcontext_p.h b/src/plugins/scenegraph/openvg/qsgopenvgcontext_p.h index 31a1e8643f..15d0b3f344 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvgcontext_p.h +++ b/src/plugins/scenegraph/openvg/qsgopenvgcontext_p.h @@ -57,7 +57,13 @@ class QSGOpenVGRenderContext : public QSGRenderContext, public QSGRendererInterf public: QSGOpenVGRenderContext(QSGContext *context); - void initialize(void *context) override; + static const int INIT_PARAMS_MAGIC = 0x51E; + struct InitParams : public QSGRenderContext::InitParams { + int sType = INIT_PARAMS_MAGIC; + QOpenVGContext *context = nullptr; + }; + + void initialize(const QSGRenderContext::InitParams *params) override; void invalidate() override; void renderNextFrame(QSGRenderer *renderer, uint fboId) override; QSGTexture *createTexture(const QImage &image, uint flags) const override; @@ -94,7 +100,7 @@ public: QSGLayer *createLayer(QSGRenderContext *renderContext) override; QSurfaceFormat defaultSurfaceFormat() const override; QSGInternalRectangleNode *createInternalRectangleNode() override; - QSGInternalImageNode *createInternalImageNode() override; + QSGInternalImageNode *createInternalImageNode(QSGRenderContext *renderContext) override; #if QT_CONFIG(quick_sprite) QSGSpriteNode *createSpriteNode() override; #endif diff --git a/src/plugins/scenegraph/openvg/qsgopenvglayer.cpp b/src/plugins/scenegraph/openvg/qsgopenvglayer.cpp index 047539d431..b03168b334 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvglayer.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvglayer.cpp @@ -44,7 +44,8 @@ QT_BEGIN_NAMESPACE QSGOpenVGLayer::QSGOpenVGLayer(QSGRenderContext *renderContext) - : m_item(nullptr) + : QSGLayer(*(new QSGOpenVGLayerPrivate)) + , m_item(nullptr) , m_renderer(nullptr) , m_device_pixel_ratio(1) , m_mirrorHorizontal(false) @@ -312,4 +313,9 @@ void QSGOpenVGLayer::grab() markDirtyTexture(); // Continuously update if 'live' and 'recursive'. } +int QSGOpenVGLayerPrivate::comparisonKey() const +{ + return 0; +} + QT_END_NAMESPACE diff --git a/src/plugins/scenegraph/openvg/qsgopenvglayer.h b/src/plugins/scenegraph/openvg/qsgopenvglayer.h index 8deedc3347..f2763463cd 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvglayer.h +++ b/src/plugins/scenegraph/openvg/qsgopenvglayer.h @@ -42,6 +42,7 @@ #include #include +#include #include "qopenvgcontext_p.h" #include "qopenvgoffscreensurface.h" @@ -50,9 +51,11 @@ QT_BEGIN_NAMESPACE class QSGOpenVGRenderer; class QSGOpenVGRenderContext; +class QSGOpenVGLayerPrivate; class QSGOpenVGLayer : public QSGLayer { + Q_DECLARE_PRIVATE(QSGOpenVGLayer) public: QSGOpenVGLayer(QSGRenderContext *renderContext); ~QSGOpenVGLayer(); @@ -109,6 +112,13 @@ private: QOpenVGOffscreenSurface *m_secondaryOffscreenSurface; }; +class QSGOpenVGLayerPrivate : public QSGTexturePrivate +{ + Q_DECLARE_PUBLIC(QSGOpenVGLayer) +public: + int comparisonKey() const override; +}; + QT_END_NAMESPACE #endif // QSGOPENVGLAYER_H diff --git a/src/plugins/scenegraph/openvg/qsgopenvgpainternode.cpp b/src/plugins/scenegraph/openvg/qsgopenvgpainternode.cpp index fb68ebf2bc..74f30f8189 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvgpainternode.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvgpainternode.cpp @@ -39,6 +39,7 @@ #include "qsgopenvgpainternode.h" #include "qsgopenvgtexture.h" +#include #include #include diff --git a/src/plugins/scenegraph/openvg/qsgopenvgrenderloop.cpp b/src/plugins/scenegraph/openvg/qsgopenvgrenderloop.cpp index c41dfd7400..94f7f76036 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvgrenderloop.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvgrenderloop.cpp @@ -176,7 +176,9 @@ void QSGOpenVGRenderLoop::renderWindow(QQuickWindow *window) if (vg == nullptr) { vg = new QOpenVGContext(window); vg->makeCurrent(); - cd->context->initialize(vg); + QSGOpenVGRenderContext::InitParams params; + params.context = vg; + cd->context->initialize(¶ms); } else { vg->makeCurrent(); } -- cgit v1.2.3 From 013a1ae4e7e5df36e03ccf71f02ad6644473a3e8 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 31 Jul 2019 13:20:29 +0200 Subject: Fix build with Qt 6 The #warning directive is non-standard and doesn't compile with non-GCC. Meanwhile, a setter was added for this property and it appears to be used from public QQuickPaintedItem API. Change-Id: Id4150f6a8045d1e9e9245ebc3213f5e9b9f4bb59 Reviewed-by: Alexandru Croitor --- src/quick/scenegraph/util/qsgdefaultpainternode_p.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h index a86f7397be..dc103648ff 100644 --- a/src/quick/scenegraph/util/qsgdefaultpainternode_p.h +++ b/src/quick/scenegraph/util/qsgdefaultpainternode_p.h @@ -159,9 +159,6 @@ private: QSize m_textureSize; QRect m_dirtyRect; QColor m_fillColor; -#if QT_VERSION >= 0x060000 -#warning "Remove m_contentsScale and assume 1 everywhere" -#endif qreal m_contentsScale; bool m_dirtyContents : 1; -- cgit v1.2.3 From 1d9645576eab6cc316c7772b4f4b2c05f37e6ec2 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 29 Jul 2019 09:50:09 +0200 Subject: Re-enable colors with both gl and rhi glyph caches Follow up to the 5.13->dev merge where the lack of the qt5 submodule update made it impossible to use the new color argument. Also implements the color argument for the rhi variant of the glyph cache. Change-Id: Ie6c6ba3d647335eb6173d0c9f7fbe3a4ed6b1f24 Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgdefaultglyphnode_p.cpp | 8 ++++---- src/quick/scenegraph/qsgrhitextureglyphcache.cpp | 5 +++-- src/quick/scenegraph/qsgrhitextureglyphcache_p.h | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp index 5a66f1dfef..db889c3102 100644 --- a/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp +++ b/src/quick/scenegraph/qsgdefaultglyphnode_p.cpp @@ -764,13 +764,13 @@ void QSGTextMaskMaterial::updateCache(QFontEngine::GlyphFormat glyphFormat) if (!fontEngine->supportsTransformation(glyphCacheTransform)) glyphCacheTransform = QTransform(); - //QColor color = glyphFormat == QFontEngine::Format_ARGB ? QColor::fromRgbF(m_color.x(), m_color.y(), m_color.z(), m_color.w()) : QColor(); - m_glyphCache = fontEngine->glyphCache(cacheKey, glyphFormat, glyphCacheTransform); // ### restore color arg when merges are done + QColor color = glyphFormat == QFontEngine::Format_ARGB ? QColor::fromRgbF(m_color.x(), m_color.y(), m_color.z(), m_color.w()) : QColor(); + m_glyphCache = fontEngine->glyphCache(cacheKey, glyphFormat, glyphCacheTransform, color); if (!m_glyphCache || int(m_glyphCache->glyphFormat()) != glyphFormat) { if (m_rhi) - m_glyphCache = new QSGRhiTextureGlyphCache(m_rhi, glyphFormat, glyphCacheTransform); // ### color + m_glyphCache = new QSGRhiTextureGlyphCache(m_rhi, glyphFormat, glyphCacheTransform, color); else - m_glyphCache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform); // ### restore color arg when merges are done + m_glyphCache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform, color); fontEngine->setGlyphCache(cacheKey, m_glyphCache.data()); m_rc->registerFontengineForCleanup(fontEngine); diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp index f181d101c6..99761302e2 100644 --- a/src/quick/scenegraph/qsgrhitextureglyphcache.cpp +++ b/src/quick/scenegraph/qsgrhitextureglyphcache.cpp @@ -43,8 +43,9 @@ QT_BEGIN_NAMESPACE -QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix) - : QImageTextureGlyphCache(format, matrix), +QSGRhiTextureGlyphCache::QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix, + const QColor &color) + : QImageTextureGlyphCache(format, matrix, color), m_rhi(rhi) { // Some OpenGL implementations, for instance macOS, have issues with diff --git a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h index 1533beb162..75d82de90d 100644 --- a/src/quick/scenegraph/qsgrhitextureglyphcache_p.h +++ b/src/quick/scenegraph/qsgrhitextureglyphcache_p.h @@ -59,7 +59,8 @@ QT_BEGIN_NAMESPACE class QSGRhiTextureGlyphCache : public QImageTextureGlyphCache { public: - QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix); + QSGRhiTextureGlyphCache(QRhi *rhi, QFontEngine::GlyphFormat format, const QTransform &matrix, + const QColor &color = QColor()); ~QSGRhiTextureGlyphCache(); void createTextureData(int width, int height) override; -- cgit v1.2.3 From 4bac72aa13e6818460f6b71127d3af5bd7e00ca5 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Fri, 28 Jun 2019 10:58:17 +0200 Subject: Port from QMutex::Recursive to QRecursiveMutex Change-Id: I5bf128b4479971e87d377707f2ebf267ccba1f1d Reviewed-by: Fabian Kosmale Reviewed-by: Ulf Hermann --- src/qml/debugger/qqmlconfigurabledebugservice_p.h | 4 ++-- src/qml/jsapi/qjsengine_p.h | 4 ++-- src/qml/jsruntime/qv4executableallocator.cpp | 4 +--- src/qml/jsruntime/qv4executableallocator_p.h | 2 +- src/qml/qml/qqmlmetatype.cpp | 4 ++-- src/qml/qml/qqmlmetatype_p.h | 4 ++-- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/qml/debugger/qqmlconfigurabledebugservice_p.h b/src/qml/debugger/qqmlconfigurabledebugservice_p.h index 96ec46f475..38f41047c0 100644 --- a/src/qml/debugger/qqmlconfigurabledebugservice_p.h +++ b/src/qml/debugger/qqmlconfigurabledebugservice_p.h @@ -64,7 +64,7 @@ class QQmlConfigurableDebugService : public Base { protected: QQmlConfigurableDebugService(float version, QObject *parent = nullptr) : - Base(version, parent), m_configMutex(QMutex::Recursive) + Base(version, parent) { init(); } @@ -103,7 +103,7 @@ protected: emit Base::attachedToEngine(engine); } - QMutex m_configMutex; + QRecursiveMutex m_configMutex; QList m_waitingEngines; bool m_waitingForConfiguration; }; diff --git a/src/qml/jsapi/qjsengine_p.h b/src/qml/jsapi/qjsengine_p.h index a77d710cff..164a70d000 100644 --- a/src/qml/jsapi/qjsengine_p.h +++ b/src/qml/jsapi/qjsengine_p.h @@ -74,7 +74,7 @@ public: static const QJSEnginePrivate* get(const QJSEngine*e) { return e->d_func(); } static QJSEnginePrivate* get(QV4::ExecutionEngine *e); - QJSEnginePrivate() : mutex(QMutex::Recursive) {} + QJSEnginePrivate() = default; ~QJSEnginePrivate() override; static void addToDebugServer(QJSEngine *q); @@ -105,7 +105,7 @@ public: }; // Shared by QQmlEngine - mutable QMutex mutex; + mutable QRecursiveMutex mutex; // These methods may be called from the QML loader thread diff --git a/src/qml/jsruntime/qv4executableallocator.cpp b/src/qml/jsruntime/qv4executableallocator.cpp index c836d121e3..7ee6f39aa2 100644 --- a/src/qml/jsruntime/qv4executableallocator.cpp +++ b/src/qml/jsruntime/qv4executableallocator.cpp @@ -147,9 +147,7 @@ bool ExecutableAllocator::ChunkOfPages::contains(Allocation *alloc) const } ExecutableAllocator::ExecutableAllocator() - : mutex(QMutex::NonRecursive) -{ -} + = default; ExecutableAllocator::~ExecutableAllocator() { diff --git a/src/qml/jsruntime/qv4executableallocator_p.h b/src/qml/jsruntime/qv4executableallocator_p.h index 013c6d7120..f98f2c7d33 100644 --- a/src/qml/jsruntime/qv4executableallocator_p.h +++ b/src/qml/jsruntime/qv4executableallocator_p.h @@ -130,7 +130,7 @@ public: private: QMultiMap freeAllocations; QMap chunks; - mutable QMutex mutex; + mutable QRecursiveMutex mutex; }; } diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index f21427ff69..2c641d3845 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -60,7 +60,7 @@ struct LockedData : private QQmlMetaTypeData }; Q_GLOBAL_STATIC(LockedData, metaTypeData) -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, metaTypeDataLock, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, metaTypeDataLock) class QQmlMetaTypeDataPtr { @@ -804,7 +804,7 @@ QQmlType QQmlMetaType::typeForUrl(const QString &urlString, return QQmlType(); } -QMutex *QQmlMetaType::typeRegistrationLock() +QRecursiveMutex *QQmlMetaType::typeRegistrationLock() { return metaTypeDataLock(); } diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index c2535a7fd5..6c2b0bb2a6 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -58,7 +58,7 @@ QT_BEGIN_NAMESPACE class QQmlTypeModule; -class QMutex; +class QRecursiveMutex; class QQmlError; namespace QV4 { class ExecutableCompilationUnit; } @@ -160,7 +160,7 @@ public: static void prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); static void removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); - static QMutex *typeRegistrationLock(); + static QRecursiveMutex *typeRegistrationLock(); static QString prettyTypeName(const QObject *object); -- cgit v1.2.3 From ebd287fd035dda9d4fea9b73d0ae5af2b2e16e06 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 31 Jul 2019 18:33:20 +0200 Subject: Fix Qt6 build Amends 013a1ae4e7e5df36e03ccf71f02ad6644473a3e8 Change-Id: I4b8ed9cf80f8715424346fd4b81fb4bc5e54bfbc Reviewed-by: Simon Hausmann --- src/quick/items/qquickpainteditem.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/quick/items/qquickpainteditem.cpp b/src/quick/items/qquickpainteditem.cpp index 57848919f3..cee73ac2e8 100644 --- a/src/quick/items/qquickpainteditem.cpp +++ b/src/quick/items/qquickpainteditem.cpp @@ -369,10 +369,6 @@ void QQuickPaintedItem::setTextureSize(const QSize &size) emit textureSizeChanged(); } -#if QT_VERSION >= 0x060000 -#warning "Remove: QQuickPaintedItem::contentsBoundingRect, contentsScale, contentsSize. Also remove them from qsgadaptationlayer_p.h and qsgdefaultpainternode.h/cpp." -#endif - /*! \obsolete -- cgit v1.2.3 From 55a0fe4539e2a9ac934bb02cdb935389a26f74ad Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Tue, 30 Jul 2019 09:39:00 +0200 Subject: Remove usages of deprecated QSignalMapper The code using QSignalMapper relies on dynamic property inspection, and therefore can't use the lambda syntax for capturing the int value. Since there is only one sender object, replace QSignalMapper with a simpler mapper class, which emits the mapped signal by passing the saved member value. Task-number: QTBUG-76491 Change-Id: I6e8e527b1a92d39a1711d85c203df704c293d294 Reviewed-by: Simon Hausmann Reviewed-by: Marc Mutz --- src/quick/items/qquickgenericshadereffect.cpp | 31 +++++++++++++++++++-------- src/quick/items/qquickgenericshadereffect_p.h | 4 +--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/quick/items/qquickgenericshadereffect.cpp b/src/quick/items/qquickgenericshadereffect.cpp index 1d71555849..df61ee853d 100644 --- a/src/quick/items/qquickgenericshadereffect.cpp +++ b/src/quick/items/qquickgenericshadereffect.cpp @@ -40,10 +40,27 @@ #include #include #include -#include QT_BEGIN_NAMESPACE +namespace { +class IntSignalMapper : public QObject +{ + Q_OBJECT + + int value; +public: + explicit IntSignalMapper(int v) + : QObject(nullptr), value(v) {} + +public Q_SLOTS: + void map() { emit mapped(value); } + +Q_SIGNALS: + void mapped(int); +}; +} // unnamed namespace + // The generic shader effect is used whenever on the RHI code path, or when the // scenegraph backend indicates SupportsShaderEffectNode. This, unlike the // monolithic and interconnected (e.g. with particles) OpenGL variant, passes @@ -547,15 +564,10 @@ void QQuickGenericShaderEffect::updateShaderVars(Shader shaderType) if (!mp.hasNotifySignal()) qWarning("ShaderEffect: property '%s' does not have notification method", v.name.constData()); - // Have a QSignalMapper that emits mapped() with an index+type on each property change notify signal. + // Have a IntSignalMapper that emits mapped() with an index+type on each property change notify signal. auto &sm(m_signalMappers[shaderType][i]); - if (!sm.mapper) { -QT_WARNING_PUSH -QT_WARNING_DISABLE_DEPRECATED - sm.mapper = new QSignalMapper; -QT_WARNING_POP - sm.mapper->setMapping(m_item, i | (shaderType << 16)); - } + if (!sm.mapper) + sm.mapper = new IntSignalMapper(i | (shaderType << 16)); sm.active = true; const QByteArray signalName = '2' + mp.notifySignal().methodSignature(); QObject::connect(m_item, signalName, sm.mapper, SLOT(map())); @@ -665,3 +677,4 @@ void QQuickGenericShaderEffect::markGeometryDirtyAndUpdateIfSupportsAtlas() QT_END_NAMESPACE #include "moc_qquickgenericshadereffect_p.cpp" +#include "qquickgenericshadereffect.moc" diff --git a/src/quick/items/qquickgenericshadereffect_p.h b/src/quick/items/qquickgenericshadereffect_p.h index 3f6f92921b..368c32d2f8 100644 --- a/src/quick/items/qquickgenericshadereffect_p.h +++ b/src/quick/items/qquickgenericshadereffect_p.h @@ -59,8 +59,6 @@ QT_BEGIN_NAMESPACE -class QSignalMapper; - class Q_QUICK_PRIVATE_EXPORT QQuickGenericShaderEffect : public QObject { Q_OBJECT @@ -142,7 +140,7 @@ private: struct SignalMapper { SignalMapper() : mapper(nullptr), active(false) { } - QSignalMapper *mapper; + QObject *mapper; bool active; }; QVector m_signalMappers[NShader]; -- cgit v1.2.3 From a22b44266b402c3b0693e82847b7505cdfcc2ca9 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 31 Jul 2019 13:18:48 +0200 Subject: Change rhi-related debug prints to categorized logging Change-Id: I4e8d3111a2f3b77e984756cc9eef49d42f0b647c Reviewed-by: Andy Nichols --- src/quick/scenegraph/qsgrenderloop.cpp | 6 +++--- src/quick/scenegraph/qsgrhisupport.cpp | 11 ++++++----- src/quick/scenegraph/qsgthreadedrenderloop.cpp | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index 7545896972..ec835fe3bd 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -564,7 +564,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) QRhiRenderBuffer::UsedWithSwapChainOnly); cd->swapchain->setWindow(window); cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); - qDebug("MSAA sample count for the swapchain is %d", rhiSampleCount); + qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d", rhiSampleCount); cd->swapchain->setSampleCount(rhiSampleCount); cd->swapchain->setFlags(flags); cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor(); @@ -629,7 +629,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) const QSize previousOutputSize = cd->swapchain->currentPixelSize(); if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) { if (cd->swapchainJustBecameRenderable) - qDebug("just became exposed"); + qCDebug(QSG_LOG_RENDERLOOP, "just became exposed"); cd->swapchainJustBecameRenderable = false; cd->depthStencilForSwapchain->setPixelSize(effectiveOutputSize); @@ -640,7 +640,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) if (!cd->hasActiveSwapchain) qWarning("Failed to build or resize swapchain"); else - qDebug() << "rhi swapchain size" << effectiveOutputSize; + qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize; } Q_ASSERT(rhi == cd->rhi); diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp index 2ca66f23d5..8bae24dc76 100644 --- a/src/quick/scenegraph/qsgrhisupport.cpp +++ b/src/quick/scenegraph/qsgrhisupport.cpp @@ -192,8 +192,9 @@ void QSGRhiSupport::applySettings() default: break; } - qDebug("Using QRhi with backend %s\n graphics API debug/validation layers: %d\n QRhi profiling and debug markers: %d", - backendName, m_debugLayer, m_profile); + qCDebug(QSG_LOG_INFO, + "Using QRhi with backend %s\n graphics API debug/validation layers: %d\n QRhi profiling and debug markers: %d", + backendName, m_debugLayer, m_profile); } QSGRhiSupport *QSGRhiSupport::staticInst() @@ -527,11 +528,11 @@ void QSGRhiProfileConnection::initialize(QRhi *rhi) int profPort = qEnvironmentVariableIntValue("QSG_RHI_PROFILE_PORT"); if (!profPort) profPort = 30667; - qDebug("Sending RHI profiling output to %s:%d", qPrintable(profHost), profPort); + qCDebug(QSG_LOG_INFO, "Sending RHI profiling output to %s:%d", qPrintable(profHost), profPort); m_profConn.reset(new QTcpSocket); QObject::connect(m_profConn.data(), QOverload::of(&QAbstractSocket::error), m_profConn.data(), - [this](QAbstractSocket::SocketError socketError) { qDebug(" RHI profiler error: %d (%s)", - socketError, qPrintable(m_profConn->errorString())); }); + [this](QAbstractSocket::SocketError socketError) { qWarning(" RHI profiler error: %d (%s)", + socketError, qPrintable(m_profConn->errorString())); }); m_profConn->connectToHost(profHost, profPort); m_profConn->waitForConnected(); // blocking wait because we want to send stuff already from the init below rhi->profiler()->setDevice(m_profConn.data()); diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index 93853f57d4..d1258cf903 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -709,7 +709,7 @@ void QSGRenderThread::syncAndRender(QImage *grabImage) const QSize previousOutputSize = cd->swapchain->currentPixelSize(); if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) { if (cd->swapchainJustBecameRenderable) - qDebug("just became exposed"); + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "just became exposed"); cd->swapchainJustBecameRenderable = false; cd->depthStencilForSwapchain->setPixelSize(effectiveOutputSize); @@ -720,7 +720,7 @@ void QSGRenderThread::syncAndRender(QImage *grabImage) if (!cd->hasActiveSwapchain) qWarning("Failed to build or resize swapchain"); else - qDebug() << "rhi swapchain size" << effectiveOutputSize; + qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize; } Q_ASSERT(rhi == cd->rhi); @@ -940,7 +940,7 @@ void QSGRenderThread::run() QRhiRenderBuffer::UsedWithSwapChainOnly); cd->swapchain->setWindow(window); cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain); - qDebug("MSAA sample count for the swapchain is %d", rhiSampleCount); + qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d", rhiSampleCount); cd->swapchain->setSampleCount(rhiSampleCount); cd->swapchain->setFlags(flags); cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor(); -- cgit v1.2.3 From 924beea1617e67faf234d304a0592d71490d41bd Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 1 Aug 2019 22:02:06 +0200 Subject: Fix 3+ level stencil clips on the rhi path Change-Id: Id1e0b904ba7273e63fb63ea53c513bde20dc9759 Reviewed-by: Andy Nichols --- src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp | 10 +++++----- tests/manual/nodetypes_ng/MultiClipRects.qml | 20 +++++++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp index 14ab7f9393..9203c67201 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -2844,17 +2844,17 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch) // RHI drawCall.vbufOffset = aligned(vOffset, 4); const int vertexByteSize = g->sizeOfVertex() * g->vertexCount(); - vOffset += vertexByteSize; + vOffset = drawCall.vbufOffset + vertexByteSize; int indexByteSize = 0; if (g->indexCount()) { drawCall.ibufOffset = aligned(iOffset, 4); indexByteSize = g->sizeOfIndex() * g->indexCount(); - iOffset += indexByteSize; + iOffset = drawCall.ibufOffset + indexByteSize; } drawCall.ubufOffset = aligned(uOffset, m_ubufAlignment); - uOffset += StencilClipUbufSize; + uOffset = drawCall.ubufOffset + StencilClipUbufSize; QMatrix4x4 matrixYUpNDC = m_current_projection_matrix; if (clip->matrix()) @@ -2919,13 +2919,13 @@ void Renderer::enqueueStencilDraw(const Batch *batch) // RHI only QRhiCommandBuffer::DynamicOffset ubufOffset(0, drawCall.ubufOffset); if (i == 0) { cb->setGraphicsPipeline(m_stencilClipCommon.replacePs); - cb->setShaderResources(srb, 1, &ubufOffset); cb->setViewport(m_pstate.viewport); } else if (i == 1) { cb->setGraphicsPipeline(m_stencilClipCommon.incrPs); - cb->setShaderResources(srb, 1, &ubufOffset); cb->setViewport(m_pstate.viewport); } + // else incrPs is already bound + cb->setShaderResources(srb, 1, &ubufOffset); cb->setStencilRef(drawCall.stencilRef); const QRhiCommandBuffer::VertexInput vbufBinding(batch->stencilClipState.vbuf, drawCall.vbufOffset); if (drawCall.indexCount) { diff --git a/tests/manual/nodetypes_ng/MultiClipRects.qml b/tests/manual/nodetypes_ng/MultiClipRects.qml index 793ebeae93..2d3804af21 100644 --- a/tests/manual/nodetypes_ng/MultiClipRects.qml +++ b/tests/manual/nodetypes_ng/MultiClipRects.qml @@ -61,8 +61,8 @@ Item { GradientStop { position: 1; color: "black" } } - // Clip using scissor, 2 levels. - // This means that the lightGreen-yellow-blue batch's clip list will have two clips. + // Clip using scissor, up to 2 levels. This means that the + // lightGreen-yellow-blue batch's clip list will have two clips. Row { spacing: 10 Repeater { @@ -103,9 +103,9 @@ Item { } } - // Clip using stencil, 2 levels. - // This means that the lightGreen-yellow batch's clip list will have two clips - // and so two stencil draw calls before drawing the actual content. + // Clip using stencil, up to 3 levels. This means that the + // lightGreen-yellow-blue batch's clip list will have three clips and + // so two stencil draw calls before drawing the actual content. Row { spacing: 10 Repeater { @@ -133,6 +133,16 @@ Item { x: 75 y: 75 NumberAnimation on rotation { from: 360; to: 0; duration: 5000; loops: Animation.Infinite; } + clip: true + + Rectangle { + color: "blue" + width: 50 + height: 50 + x: 0 + y: 0 + NumberAnimation on rotation { from: 360; to: 0; duration: 5000; loops: Animation.Infinite; } + } } } } -- cgit v1.2.3 From 5f530c252b3ca0a358c3f6e46add0f83ab8ebf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Tue, 30 Jul 2019 15:14:30 +0200 Subject: Skip the test function that uses QProcess on Android See also c3059391fea326b7115cf038ecdad8e820a0c2d5 in qtbase. Change-Id: I30d4e7349fa7d7604f9ef90692cd7749cd6728bc Task-number: QTBUG-73512 Reviewed-by: Liang Qi --- tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp b/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp index a5332c8860..de762d66c5 100644 --- a/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp +++ b/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp @@ -342,6 +342,9 @@ void tst_qqmlnotifier::lotsOfBindings() void tst_qqmlnotifier::deleteFromHandler() { +#ifdef Q_OS_ANDROID + QSKIP("Android seems to have problems with QProcess"); +#endif #if !QT_CONFIG(process) QSKIP("Need QProcess support to test qFatal."); #else -- cgit v1.2.3 From c273175ffec925a4164de41a79c21d785a1761a7 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 6 Aug 2019 15:00:45 +0200 Subject: Fix tests in qtquickcontrols 1 and 2 This patch partially reverts the 5.15 related #ifdefs from commit f38e071f5b353cbf9ce6c6c104bd82099ae0aa14. Those were introduced without any test coverage and our own modules (qtquickcontrols 1 and 2) were not tested with it or adapted. The change of behavior breaks our own existing code. Task-number: QTBUG-33444 Change-Id: Ie9823638c1a02281798b725f745b15e622f837c5 Reviewed-by: Alexandru Croitor Reviewed-by: Fabian Kosmale --- src/qml/types/qqmlbind.cpp | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp index 5067b6a02e..4b60108597 100644 --- a/src/qml/types/qqmlbind.cpp +++ b/src/qml/types/qqmlbind.cpp @@ -70,12 +70,8 @@ public: , delayed(false) , pendingEval(false) , restoreBinding(true) -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) , restoreValue(false) , restoreModeExplicit(false) -#else - , restoreValue(true) -#endif {} ~QQmlBindPrivate() { } @@ -93,9 +89,7 @@ public: bool pendingEval:1; bool restoreBinding:1; bool restoreValue:1; -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) bool restoreModeExplicit:1; -#endif void validate(QObject *binding) const; void clearPrev(); @@ -365,8 +359,7 @@ void QQmlBind::setDelayed(bool delayed) \li Binding.RestoreBindingOrValue The original value is always restored. \list - \warning The default value is Binding.RestoreBinding. This will change in - Qt 5.15 to Binding.RestoreBindingOrValue. + The default value is Binding.RestoreBinding. If you rely on any specific behavior regarding the restoration of plain values when bindings get disabled you should migrate to explicitly set the @@ -392,9 +385,7 @@ void QQmlBind::setRestoreMode(RestorationMode newMode) if (newMode != restoreMode()) { d->restoreValue = (newMode & RestoreValue); d->restoreBinding = (newMode & RestoreBinding); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) d->restoreModeExplicit = true; -#endif emit restoreModeChanged(); } } @@ -465,27 +456,11 @@ void QQmlBind::eval() Q_ASSERT(vmemo); vmemo->setVMEProperty(propPriv->core.coreIndex(), *d->v4Value.valueRef()); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } else if (d->prevIsVariant) { if (d->restoreValue) { d->prop.write(d->prevValue); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } return; -- cgit v1.2.3 From 88cc9d692fc7e2a1bcb647d96bcd820714a29a01 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 31 Jul 2019 15:17:52 +0200 Subject: Implement QSG_VISUALIZE for RHI Change-Id: I6343f316e2ecff4e4d7454fb450a1bd0c5a917b8 Reviewed-by: Andy Nichols --- src/quick/items/qquickwindow.cpp | 7 + src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp | 397 ++------- src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h | 49 +- .../scenegraph/coreapi/qsgopenglvisualizer.cpp | 365 ++++++++ .../scenegraph/coreapi/qsgopenglvisualizer_p.h | 91 ++ src/quick/scenegraph/coreapi/qsgrenderer_p.h | 1 + src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp | 929 +++++++++++++++++++++ src/quick/scenegraph/coreapi/qsgrhivisualizer_p.h | 233 ++++++ src/quick/scenegraph/scenegraph.pri | 6 +- src/quick/scenegraph/scenegraph.qrc | 3 + src/quick/scenegraph/shaders_ng/compile.bat | 2 + src/quick/scenegraph/shaders_ng/visualization.frag | 19 + .../scenegraph/shaders_ng/visualization.frag.qsb | Bin 0 -> 1877 bytes src/quick/scenegraph/shaders_ng/visualization.vert | 28 + .../scenegraph/shaders_ng/visualization.vert.qsb | Bin 0 -> 2030 bytes 15 files changed, 1789 insertions(+), 341 deletions(-) create mode 100644 src/quick/scenegraph/coreapi/qsgopenglvisualizer.cpp create mode 100644 src/quick/scenegraph/coreapi/qsgopenglvisualizer_p.h create mode 100644 src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp create mode 100644 src/quick/scenegraph/coreapi/qsgrhivisualizer_p.h create mode 100644 src/quick/scenegraph/shaders_ng/visualization.frag create mode 100644 src/quick/scenegraph/shaders_ng/visualization.frag.qsb create mode 100644 src/quick/scenegraph/shaders_ng/visualization.vert create mode 100644 src/quick/scenegraph/shaders_ng/visualization.vert.qsb diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index b55c578b12..acd5c4c077 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -536,6 +536,13 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfa context->endNextRhiFrame(renderer); else context->endNextFrame(renderer); + + if (renderer->hasCustomRenderModeWithContinuousUpdate()) { + // For the overdraw visualizer. This update is not urgent so avoid a + // direct update() call, this is only here to keep the overdraw + // visualization box rotating even when the scene is static. + QCoreApplication::postEvent(q, new QEvent(QEvent::Type(FullUpdateRequest))); + } } QQuickWindowPrivate::QQuickWindowPrivate() diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp index 9203c67201..3d8e3291b2 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp @@ -59,6 +59,9 @@ #include #include "qsgmaterialrhishader_p.h" +#include "qsgopenglvisualizer_p.h" +#include "qsgrhivisualizer_p.h" + #include #ifndef GL_DOUBLE @@ -145,7 +148,7 @@ static inline uint aligned(uint v, uint byteAlign) return (v + byteAlign - 1) & ~(byteAlign - 1); } -static inline QRhiVertexInputAttribute::Format vertexInputFormat(const QSGGeometry::Attribute &a) +QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a) { switch (a.type) { case QSGGeometry::FloatType: @@ -193,7 +196,7 @@ static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialRhiShad qWarning("Vertex input %d is present in material but not in shader. This is wrong.", a.position); } - inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, vertexInputFormat(a), offset)); + inputAttributes.append(QRhiVertexInputAttribute(VERTEX_BUFFER_BINDING, a.position, qsg_vertexInputFormat(a), offset)); offset += a.tupleSize * size_of_type(a.type); } if (batchable) { @@ -215,7 +218,7 @@ static QRhiVertexInputLayout calculateVertexInputLayout(const QSGMaterialRhiShad return inputLayout; } -static inline QRhiCommandBuffer::IndexFormat indexFormat(const QSGGeometry *geometry) +QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry) { switch (geometry->indexType()) { case QSGGeometry::UnsignedShortType: @@ -230,7 +233,7 @@ static inline QRhiCommandBuffer::IndexFormat indexFormat(const QSGGeometry *geom } } -static inline QRhiGraphicsPipeline::Topology gpTopology(int geomDrawMode) +QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode) { QRhiGraphicsPipeline::Topology topology = QRhiGraphicsPipeline::Triangles; switch (geomDrawMode) { @@ -501,8 +504,8 @@ void Updater::updateStates(QSGNode *n) qDebug(" - forceupdate"); } - if (Q_UNLIKELY(renderer->m_visualizeMode == Renderer::VisualizeChanges)) - renderer->visualizeChangesPrepare(sn); + if (Q_UNLIKELY(renderer->m_visualizer->mode() == Visualizer::VisualizeChanges)) + renderer->m_visualizer->visualizeChangesPrepare(sn); visitNode(sn); } @@ -975,7 +978,6 @@ Renderer::Renderer(QSGDefaultRenderContext *ctx) , m_vertexUploadPool(256) , m_indexUploadPool(64) , m_vao(nullptr) - , m_visualizeMode(VisualizeNothing) { m_rhi = m_context->rhi(); if (m_rhi) { @@ -983,9 +985,11 @@ Renderer::Renderer(QSGDefaultRenderContext *ctx) m_uint32IndexForRhi = !m_rhi->isFeatureSupported(QRhi::NonFourAlignedEffectiveIndexBufferOffset); if (qEnvironmentVariableIntValue("QSG_RHI_UINT32_INDEX")) m_uint32IndexForRhi = true; + m_visualizer = new RhiVisualizer(this); } else { initializeOpenGLFunctions(); m_uint32IndexForRhi = false; + m_visualizer = new OpenGLVisualizer(this); } setNodeUpdater(new Updater(this)); @@ -1088,6 +1092,8 @@ Renderer::~Renderer() } destroyGraphicsResources(); + + delete m_visualizer; } void Renderer::destroyGraphicsResources() @@ -1104,6 +1110,8 @@ void Renderer::destroyGraphicsResources() m_stencilClipCommon.reset(); delete m_dummyTexture; + + m_visualizer->releaseResources(); } void Renderer::releaseCachedResources() @@ -1138,7 +1146,7 @@ void Renderer::invalidateAndRecycleBatch(Batch *b) */ void Renderer::map(Buffer *buffer, int byteSize, bool isIndexBuf) { - if (!m_context->hasBrokenIndexBufferObjects() && m_visualizeMode == VisualizeNothing) { + if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing) { // Common case, use a shared memory pool for uploading vertex data to avoid // excessive reevaluation QDataBuffer &pool = m_context->separateIndexBuffer() && isIndexBuf @@ -1194,7 +1202,7 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf) m_resourceUpdates->updateDynamicBuffer(buffer->buf, 0, buffer->size, QByteArray::fromRawData(buffer->data, buffer->size)); } - if (m_visualizeMode == VisualizeNothing) + if (m_visualizer->mode() == Visualizer::VisualizeNothing) buffer->data = nullptr; } else { if (buffer->id == 0) @@ -1202,7 +1210,7 @@ void Renderer::unmap(Buffer *buffer, bool isIndexBuf) GLenum target = isIndexBuf ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER; glBindBuffer(target, buffer->id); glBufferData(target, buffer->size, buffer->data, m_bufferStrategy); - if (!m_context->hasBrokenIndexBufferObjects() && m_visualizeMode == VisualizeNothing) + if (!m_context->hasBrokenIndexBufferObjects() && m_visualizer->mode() == Visualizer::VisualizeNothing) buffer->data = nullptr; } } @@ -2096,7 +2104,7 @@ void Renderer::uploadMergedElement(Element *e, int vaOffset, char **vertexData, *indexCount += iCount; } -static QMatrix4x4 qsg_matrixForRoot(Node *node) +QMatrix4x4 qsg_matrixForRoot(Node *node) { if (node->type() == QSGNode::TransformNodeType) return static_cast(node->sgNode)->combinedMatrix(); @@ -2830,14 +2838,14 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch) // RHI if (firstStencilClipInBatch) { m_stencilClipCommon.inputLayout.setBindings({ QRhiVertexInputBinding(g->sizeOfVertex()) }); - m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, vertexInputFormat(*a), 0) }); - m_stencilClipCommon.topology = gpTopology(g->drawingMode()); + m_stencilClipCommon.inputLayout.setAttributes({ QRhiVertexInputAttribute(0, 0, qsg_vertexInputFormat(*a), 0) }); + m_stencilClipCommon.topology = qsg_topology(g->drawingMode()); } #ifndef QT_NO_DEBUG else { - if (gpTopology(g->drawingMode()) != m_stencilClipCommon.topology) + if (qsg_topology(g->drawingMode()) != m_stencilClipCommon.topology) qWarning("updateClipState: Clip list entries have different primitive topologies, this is not currently supported."); - if (vertexInputFormat(*a) != m_stencilClipCommon.inputLayout.attributes().first().format()) + if (qsg_vertexInputFormat(*a) != m_stencilClipCommon.inputLayout.attributes().first().format()) qWarning("updateClipState: Clip list entries have different vertex input layouts, this is must not happen."); } #endif @@ -2874,7 +2882,7 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch) // RHI drawCall.vertexCount = g->vertexCount(); drawCall.indexCount = g->indexCount(); - drawCall.indexFormat = indexFormat(g); + drawCall.indexFormat = qsg_indexFormat(g); batch->stencilClipState.drawCalls.add(drawCall); } @@ -3255,7 +3263,7 @@ bool Renderer::ensurePipelineState(Element *e, const ShaderManager::Shader *sms) flags |= QRhiGraphicsPipeline::UsesStencilRef; ps->setFlags(flags); - ps->setTopology(gpTopology(m_gstate.drawMode)); + ps->setTopology(qsg_topology(m_gstate.drawMode)); ps->setCullMode(m_gstate.cullMode); QRhiGraphicsPipeline::TargetBlend blend; @@ -4089,6 +4097,9 @@ void Renderer::renderBatches() } } + if (m_visualizer->mode() != Visualizer::VisualizeNothing) + m_visualizer->prepareVisualize(); + QRhiCommandBuffer *cb = commandBuffer(); cb->beginPass(renderTarget(), m_pstate.clearColor, m_pstate.dsClear, m_resourceUpdates); m_resourceUpdates = nullptr; @@ -4120,7 +4131,8 @@ void Renderer::renderBatches() if (m_renderPassRecordingCallbacks.end) m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData); - cb->endPass(); + if (m_visualizer->mode() == Visualizer::VisualizeNothing) + cb->endPass(); } } @@ -4314,13 +4326,16 @@ void Renderer::render() m_renderOrderRebuildLower = -1; m_renderOrderRebuildUpper = -1; - if (!m_rhi) { - if (m_visualizeMode != VisualizeNothing) - visualize(); + if (m_visualizer->mode() != Visualizer::VisualizeNothing) + m_visualizer->visualize(); + if (!m_rhi) { if (m_vao) m_vao->release(); } else { + if (m_visualizer->mode() != Visualizer::VisualizeNothing) + commandBuffer()->endPass(); + if (m_resourceUpdates) { m_resourceUpdates->release(); m_resourceUpdates = nullptr; @@ -4460,311 +4475,23 @@ void Renderer::renderRenderNode(Batch *batch) glBindFramebuffer(GL_FRAMEBUFFER, prevFbo); } -class VisualizeShader : public QOpenGLShaderProgram -{ -public: - int color; - int matrix; - int rotation; - int pattern; - int projection; -}; - -void Renderer::visualizeDrawGeometry(const QSGGeometry *g) -{ - if (g->attributeCount() < 1) - return; - const QSGGeometry::Attribute *a = g->attributes(); - glVertexAttribPointer(0, a->tupleSize, a->type, false, g->sizeOfVertex(), g->vertexData()); - if (g->indexCount()) - glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); - else - glDrawArrays(g->drawingMode(), 0, g->vertexCount()); - -} - -void Renderer::visualizeBatch(Batch *b) -{ - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - - if (b->positionAttribute != 0) - return; - - QSGGeometryNode *gn = b->first->node; - QSGGeometry *g = gn->geometry(); - const QSGGeometry::Attribute &a = g->attributes()[b->positionAttribute]; - - glBindBuffer(GL_ARRAY_BUFFER, b->vbo.id); - - QMatrix4x4 matrix(m_current_projection_matrix); - if (b->root) - matrix = matrix * qsg_matrixForRoot(b->root); - - shader->setUniformValue(shader->pattern, float(b->merged ? 0 : 1)); - - QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0); - float cr = color.redF(); - float cg = color.greenF(); - float cb = color.blueF(); - shader->setUniformValue(shader->color, cr, cg, cb, 1.0); - - if (b->merged) { - shader->setUniformValue(shader->matrix, matrix); - const char *dataStart = m_context->separateIndexBuffer() ? b->ibo.data : b->vbo.data; - for (int ds=0; dsdrawSets.size(); ++ds) { - const DrawSet &set = b->drawSets.at(ds); - glVertexAttribPointer(a.position, 2, a.type, false, g->sizeOfVertex(), (void *) (qintptr) (set.vertices)); - glDrawElements(g->drawingMode(), set.indexCount, GL_UNSIGNED_SHORT, - (void *)(qintptr)(dataStart + set.indices)); - } - } else { - Element *e = b->first; - int offset = 0; - while (e) { - gn = e->node; - g = gn->geometry(); - shader->setUniformValue(shader->matrix, matrix * *gn->matrix()); - glVertexAttribPointer(a.position, a.tupleSize, a.type, false, g->sizeOfVertex(), (void *) (qintptr) offset); - if (g->indexCount()) - glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); - else - glDrawArrays(g->drawingMode(), 0, g->vertexCount()); - offset += g->sizeOfVertex() * g->vertexCount(); - e = e->nextInBatch; - } - } -} - - - - -void Renderer::visualizeClipping(QSGNode *node) -{ - if (node->type() == QSGNode::ClipNodeType) { - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - QSGClipNode *clipNode = static_cast(node); - QMatrix4x4 matrix = m_current_projection_matrix; - if (clipNode->matrix()) - matrix = matrix * *clipNode->matrix(); - shader->setUniformValue(shader->matrix, matrix); - visualizeDrawGeometry(clipNode->geometry()); - } - - QSGNODE_TRAVERSE(node) { - visualizeClipping(child); - } -} - -#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \ - | QSGNode::DirtyOpacity \ - | QSGNode::DirtyMatrix \ - | QSGNode::DirtyNodeRemoved) - -void Renderer::visualizeChangesPrepare(Node *n, uint parentChanges) -{ - uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT; - uint selfDirty = n->dirtyState | parentChanges; - if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0) - m_visualizeChanceSet.insert(n, selfDirty); - SHADOWNODE_TRAVERSE(n) { - visualizeChangesPrepare(child, childDirty); - } -} - -void Renderer::visualizeChanges(Node *n) -{ - - if (n->type() == QSGNode::GeometryNodeType && n->element()->batch && m_visualizeChanceSet.contains(n)) { - uint dirty = m_visualizeChanceSet.value(n); - bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0; - - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 0.3, 1.0); - float ca = 0.5; - float cr = color.redF() * ca; - float cg = color.greenF() * ca; - float cb = color.blueF() * ca; - shader->setUniformValue(shader->color, cr, cg, cb, ca); - shader->setUniformValue(shader->pattern, float(tinted ? 0.5 : 0)); - - QSGGeometryNode *gn = static_cast(n->sgNode); - - QMatrix4x4 matrix = m_current_projection_matrix; - if (n->element()->batch->root) - matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); - matrix = matrix * *gn->matrix(); - shader->setUniformValue(shader->matrix, matrix); - visualizeDrawGeometry(gn->geometry()); - - // This is because many changes don't propegate their dirty state to the - // parent so the node updater will not unset these states. They are - // not used for anything so, unsetting it should have no side effects. - n->dirtyState = nullptr; - } - - SHADOWNODE_TRAVERSE(n) { - visualizeChanges(child); - } -} - -void Renderer::visualizeOverdraw_helper(Node *node) -{ - if (node->type() == QSGNode::GeometryNodeType && node->element()->batch) { - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - QSGGeometryNode *gn = static_cast(node->sgNode); - - QMatrix4x4 matrix = m_current_projection_matrix; - matrix(2, 2) = m_zRange; - matrix(2, 3) = 1.0f - node->element()->order * m_zRange; - - if (node->element()->batch->root) - matrix = matrix * qsg_matrixForRoot(node->element()->batch->root); - matrix = matrix * *gn->matrix(); - shader->setUniformValue(shader->matrix, matrix); - - QColor color = node->element()->batch->isOpaque ? QColor::fromRgbF(0.3, 1.0, 0.3) : QColor::fromRgbF(1.0, 0.3, 0.3); - float ca = 0.33f; - shader->setUniformValue(shader->color, color.redF() * ca, color.greenF() * ca, color.blueF() * ca, ca); - - visualizeDrawGeometry(gn->geometry()); - } - - SHADOWNODE_TRAVERSE(node) { - visualizeOverdraw_helper(child); - } -} - -void Renderer::visualizeOverdraw() -{ - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - shader->setUniformValue(shader->color, 0.5f, 0.5f, 1.0f, 1.0f); - shader->setUniformValue(shader->projection, 1); - - glBlendFunc(GL_ONE, GL_ONE); - - static float step = 0; - step += static_cast(M_PI * 2 / 1000.); - if (step > M_PI * 2) - step = 0; - float angle = 80.0 * std::sin(step); - - QMatrix4x4 xrot; xrot.rotate(20, 1, 0, 0); - QMatrix4x4 zrot; zrot.rotate(angle, 0, 0, 1); - QMatrix4x4 tx; tx.translate(0, 0, 1); - - QMatrix4x4 m; - -// m.rotate(180, 0, 1, 0); - - m.translate(0, 0.5, 4); - m.scale(2, 2, 1); - - m.rotate(-30, 1, 0, 0); - m.rotate(angle, 0, 1, 0); - m.translate(0, 0, -1); - - shader->setUniformValue(shader->rotation, m); - - float box[] = { - // lower - -1, 1, 0, 1, 1, 0, - -1, 1, 0, -1, -1, 0, - 1, 1, 0, 1, -1, 0, - -1, -1, 0, 1, -1, 0, - - // upper - -1, 1, 1, 1, 1, 1, - -1, 1, 1, -1, -1, 1, - 1, 1, 1, 1, -1, 1, - -1, -1, 1, 1, -1, 1, - - // sides - -1, -1, 0, -1, -1, 1, - 1, -1, 0, 1, -1, 1, - -1, 1, 0, -1, 1, 1, - 1, 1, 0, 1, 1, 1 - }; - glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, box); - glLineWidth(2); - glDrawArrays(GL_LINES, 0, 24); - - visualizeOverdraw_helper(m_nodes.value(rootNode())); - - // Animate the view... - QSurface *surface = m_context->openglContext()->surface(); - if (surface->surfaceClass() == QSurface::Window) - if (QQuickWindow *window = qobject_cast(static_cast(surface))) - window->update(); -} - void Renderer::setCustomRenderMode(const QByteArray &mode) { - if (mode.isEmpty()) m_visualizeMode = VisualizeNothing; - else if (mode == "clip") m_visualizeMode = VisualizeClipping; - else if (mode == "overdraw") m_visualizeMode = VisualizeOverdraw; - else if (mode == "batches") m_visualizeMode = VisualizeBatches; - else if (mode == "changes") m_visualizeMode = VisualizeChanges; + if (mode.isEmpty()) + m_visualizer->setMode(Visualizer::VisualizeNothing); + else if (mode == "clip") + m_visualizer->setMode(Visualizer::VisualizeClipping); + else if (mode == "overdraw") + m_visualizer->setMode(Visualizer::VisualizeOverdraw); + else if (mode == "batches") + m_visualizer->setMode(Visualizer::VisualizeBatches); + else if (mode == "changes") + m_visualizer->setMode(Visualizer::VisualizeChanges); } -void Renderer::visualize() +bool Renderer::hasCustomRenderModeWithContinuousUpdate() const { - if (!m_shaderManager->visualizeProgram) { - VisualizeShader *prog = new VisualizeShader(); - QSGShaderSourceBuilder::initializeProgramFromFiles( - prog, - QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.vert"), - QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.frag")); - prog->bindAttributeLocation("v", 0); - prog->link(); - prog->bind(); - prog->color = prog->uniformLocation("color"); - prog->pattern = prog->uniformLocation("pattern"); - prog->projection = prog->uniformLocation("projection"); - prog->matrix = prog->uniformLocation("matrix"); - prog->rotation = prog->uniformLocation("rotation"); - m_shaderManager->visualizeProgram = prog; - } else { - m_shaderManager->visualizeProgram->bind(); - } - VisualizeShader *shader = static_cast(m_shaderManager->visualizeProgram); - - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glEnableVertexAttribArray(0); - - // Blacken out the actual rendered content... - float bgOpacity = 0.8f; - if (m_visualizeMode == VisualizeBatches) - bgOpacity = 1.0; - float v[] = { -1, 1, 1, 1, -1, -1, 1, -1 }; - shader->setUniformValue(shader->color, 0.0f, 0.0f, 0.0f, bgOpacity); - shader->setUniformValue(shader->matrix, QMatrix4x4()); - shader->setUniformValue(shader->rotation, QMatrix4x4()); - shader->setUniformValue(shader->pattern, 0.0f); - shader->setUniformValue(shader->projection, false); - glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, v); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - if (m_visualizeMode == VisualizeBatches) { - srand(0); // To force random colors to be roughly the same every time.. - for (int i=0; isetUniformValue(shader->pattern, 0.5f); - shader->setUniformValue(shader->color, 0.2f, 0.0f, 0.0f, 0.2f); - visualizeClipping(rootNode()); - } else if (m_visualizeMode == VisualizeChanges) { - visualizeChanges(m_nodes.value(rootNode())); - m_visualizeChanceSet.clear(); - } else if (m_visualizeMode == VisualizeOverdraw) { - visualizeOverdraw(); - } - - // Reset state back to defaults.. - glDisable(GL_BLEND); - glDisableVertexAttribArray(0); - shader->release(); + return m_visualizer->mode() == Visualizer::VisualizeOverdraw; } bool operator==(const GraphicsState &a, const GraphicsState &b) Q_DECL_NOTHROW @@ -4822,8 +4549,34 @@ uint qHash(const GraphicsPipelineStateKey &k, uint seed) Q_DECL_NOTHROW return qHash(k.state, seed) + qHash(k.sms->programRhi.program, seed) + qHash(k.rpDesc, seed); } -QT_END_NAMESPACE +Visualizer::Visualizer(Renderer *renderer) + : m_renderer(renderer), + m_visualizeMode(VisualizeNothing) +{ +} + +Visualizer::~Visualizer() +{ +} + +#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \ + | QSGNode::DirtyOpacity \ + | QSGNode::DirtyMatrix \ + | QSGNode::DirtyNodeRemoved) +void Visualizer::visualizeChangesPrepare(Node *n, uint parentChanges) +{ + uint childDirty = (parentChanges | n->dirtyState) & QSGNODE_DIRTY_PARENT; + uint selfDirty = n->dirtyState | parentChanges; + if (n->type() == QSGNode::GeometryNodeType && selfDirty != 0) + m_visualizeChangeSet.insert(n, selfDirty); + SHADOWNODE_TRAVERSE(n) { + visualizeChangesPrepare(child, childDirty); + } } +} // namespace QSGBatchRenderer + +QT_END_NAMESPACE + #include "moc_qsgbatchrenderer_p.cpp" diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h index 9dec203e73..ea9dab244f 100644 --- a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h +++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h @@ -648,7 +648,7 @@ public: float lastOpacity; }; - ShaderManager(QSGDefaultRenderContext *ctx) : visualizeProgram(nullptr), blitProgram(nullptr), context(ctx) { } + ShaderManager(QSGDefaultRenderContext *ctx) : blitProgram(nullptr), context(ctx) { } ~ShaderManager() { qDeleteAll(rewrittenShaders); qDeleteAll(stockShaders); @@ -665,8 +665,6 @@ public: Shader *prepareMaterial(QSGMaterial *material, bool enableRhiShaders = false, const QSGGeometry *geometry = nullptr); Shader *prepareMaterialNoRewrite(QSGMaterial *material, bool enableRhiShaders = false, const QSGGeometry *geometry = nullptr); - QOpenGLShaderProgram *visualizeProgram; - private: QHash rewrittenShaders; QHash stockShaders; @@ -719,12 +717,9 @@ struct RenderPassState bool scissorSet; }; -class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer, public QOpenGLFunctions +class Visualizer { public: - Renderer(QSGDefaultRenderContext *); - ~Renderer(); - enum VisualizeMode { VisualizeNothing, VisualizeBatches, @@ -733,6 +728,30 @@ public: VisualizeOverdraw }; + Visualizer(Renderer *renderer); + virtual ~Visualizer(); + + VisualizeMode mode() const { return m_visualizeMode; } + void setMode(VisualizeMode mode) { m_visualizeMode = mode; } + + virtual void visualizeChangesPrepare(Node *n, uint parentChanges = 0); + virtual void prepareVisualize() = 0; + virtual void visualize() = 0; + + virtual void releaseResources() = 0; + +protected: + Renderer *m_renderer; + VisualizeMode m_visualizeMode; + QHash m_visualizeChangeSet; +}; + +class Q_QUICK_PRIVATE_EXPORT Renderer : public QSGRenderer, public QOpenGLFunctions +{ +public: + Renderer(QSGDefaultRenderContext *); + ~Renderer(); + protected: void nodeChanged(QSGNode *node, QSGNode::DirtyState state) override; void render() override; @@ -747,6 +766,8 @@ private: }; friend class Updater; + friend class OpenGLVisualizer; + friend class RhiVisualizer; void destroyGraphicsResources(); void map(Buffer *buffer, int size, bool isIndexBuf = false); @@ -813,15 +834,8 @@ private: inline Batch *newBatch(); void invalidateAndRecycleBatch(Batch *b); - void visualize(); - void visualizeBatch(Batch *b); - void visualizeClipping(QSGNode *node); - void visualizeChangesPrepare(Node *n, uint parentChanges = 0); - void visualizeChanges(Node *n); - void visualizeOverdraw(); - void visualizeOverdraw_helper(Node *node); - void visualizeDrawGeometry(const QSGGeometry *g); void setCustomRenderMode(const QByteArray &mode) override; + bool hasCustomRenderModeWithContinuousUpdate() const override; QSGDefaultRenderContext *m_context; QSet m_taggedRoots; @@ -852,6 +866,8 @@ private: int m_batchNodeThreshold; int m_batchVertexThreshold; + Visualizer *m_visualizer; + // Stuff used during rendering only... ShaderManager *m_shaderManager; // per rendercontext, shared QSGMaterial *m_currentMaterial; @@ -874,9 +890,6 @@ private: // For minimal OpenGL core profile support QOpenGLVertexArrayObject *m_vao; - QHash m_visualizeChanceSet; - VisualizeMode m_visualizeMode; - Allocator m_nodeAllocator; Allocator m_elementAllocator; diff --git a/src/quick/scenegraph/coreapi/qsgopenglvisualizer.cpp b/src/quick/scenegraph/coreapi/qsgopenglvisualizer.cpp new file mode 100644 index 0000000000..6c2ff0b176 --- /dev/null +++ b/src/quick/scenegraph/coreapi/qsgopenglvisualizer.cpp @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2016 Jolla Ltd, author: +** Copyright (C) 2016 Robin Burchell +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgopenglvisualizer_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QSGBatchRenderer +{ + +#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling()) +#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling()) +#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \ + | QSGNode::DirtyOpacity \ + | QSGNode::DirtyMatrix \ + | QSGNode::DirtyNodeRemoved) + +QMatrix4x4 qsg_matrixForRoot(Node *node); + +class VisualizeShader : public QOpenGLShaderProgram +{ +public: + int color; + int matrix; + int rotation; + int pattern; + int projection; +}; + +OpenGLVisualizer::OpenGLVisualizer(Renderer *renderer) + : Visualizer(renderer), + m_funcs(QOpenGLContext::currentContext()->functions()), + m_visualizeProgram(nullptr) +{ +} + +OpenGLVisualizer::~OpenGLVisualizer() +{ + releaseResources(); +} + +void OpenGLVisualizer::releaseResources() +{ + delete m_visualizeProgram; + m_visualizeProgram = nullptr; +} + +void OpenGLVisualizer::prepareVisualize() +{ + // nothing to do here +} + +void OpenGLVisualizer::visualizeDrawGeometry(const QSGGeometry *g) +{ + if (g->attributeCount() < 1) + return; + const QSGGeometry::Attribute *a = g->attributes(); + m_funcs->glVertexAttribPointer(0, a->tupleSize, a->type, false, g->sizeOfVertex(), g->vertexData()); + if (g->indexCount()) + m_funcs->glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); + else + m_funcs->glDrawArrays(g->drawingMode(), 0, g->vertexCount()); + +} + +void OpenGLVisualizer::visualizeBatch(Batch *b) +{ + VisualizeShader *shader = static_cast(m_visualizeProgram); + + if (b->positionAttribute != 0) + return; + + QSGGeometryNode *gn = b->first->node; + QSGGeometry *g = gn->geometry(); + const QSGGeometry::Attribute &a = g->attributes()[b->positionAttribute]; + + m_funcs->glBindBuffer(GL_ARRAY_BUFFER, b->vbo.id); + + QMatrix4x4 matrix(m_renderer->m_current_projection_matrix); + if (b->root) + matrix = matrix * qsg_matrixForRoot(b->root); + + shader->setUniformValue(shader->pattern, float(b->merged ? 0 : 1)); + + QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0); + float cr = color.redF(); + float cg = color.greenF(); + float cb = color.blueF(); + shader->setUniformValue(shader->color, cr, cg, cb, 1.0); + + if (b->merged) { + shader->setUniformValue(shader->matrix, matrix); + const char *dataStart = m_renderer->m_context->separateIndexBuffer() ? b->ibo.data : b->vbo.data; + for (int ds=0; dsdrawSets.size(); ++ds) { + const DrawSet &set = b->drawSets.at(ds); + m_funcs->glVertexAttribPointer(a.position, 2, a.type, false, g->sizeOfVertex(), + (void *) (qintptr) (set.vertices)); + m_funcs->glDrawElements(g->drawingMode(), set.indexCount, GL_UNSIGNED_SHORT, + (void *)(qintptr)(dataStart + set.indices)); + } + } else { + Element *e = b->first; + int offset = 0; + while (e) { + gn = e->node; + g = gn->geometry(); + shader->setUniformValue(shader->matrix, matrix * *gn->matrix()); + m_funcs->glVertexAttribPointer(a.position, a.tupleSize, a.type, false, g->sizeOfVertex(), + (void *) (qintptr) offset); + if (g->indexCount()) + m_funcs->glDrawElements(g->drawingMode(), g->indexCount(), g->indexType(), g->indexData()); + else + m_funcs->glDrawArrays(g->drawingMode(), 0, g->vertexCount()); + offset += g->sizeOfVertex() * g->vertexCount(); + e = e->nextInBatch; + } + } +} + +void OpenGLVisualizer::visualizeClipping(QSGNode *node) +{ + if (node->type() == QSGNode::ClipNodeType) { + VisualizeShader *shader = static_cast(m_visualizeProgram); + QSGClipNode *clipNode = static_cast(node); + QMatrix4x4 matrix = m_renderer->m_current_projection_matrix; + if (clipNode->matrix()) + matrix = matrix * *clipNode->matrix(); + shader->setUniformValue(shader->matrix, matrix); + visualizeDrawGeometry(clipNode->geometry()); + } + + QSGNODE_TRAVERSE(node) { + visualizeClipping(child); + } +} + +void OpenGLVisualizer::visualizeChanges(Node *n) +{ + + if (n->type() == QSGNode::GeometryNodeType && n->element()->batch && m_visualizeChangeSet.contains(n)) { + uint dirty = m_visualizeChangeSet.value(n); + bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0; + + VisualizeShader *shader = static_cast(m_visualizeProgram); + QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 0.3, 1.0); + float ca = 0.5; + float cr = color.redF() * ca; + float cg = color.greenF() * ca; + float cb = color.blueF() * ca; + shader->setUniformValue(shader->color, cr, cg, cb, ca); + shader->setUniformValue(shader->pattern, float(tinted ? 0.5 : 0)); + + QSGGeometryNode *gn = static_cast(n->sgNode); + + QMatrix4x4 matrix = m_renderer->m_current_projection_matrix; + if (n->element()->batch->root) + matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); + matrix = matrix * *gn->matrix(); + shader->setUniformValue(shader->matrix, matrix); + visualizeDrawGeometry(gn->geometry()); + + // This is because many changes don't propegate their dirty state to the + // parent so the node updater will not unset these states. They are + // not used for anything so, unsetting it should have no side effects. + n->dirtyState = nullptr; + } + + SHADOWNODE_TRAVERSE(n) { + visualizeChanges(child); + } +} + +void OpenGLVisualizer::visualizeOverdraw_helper(Node *node) +{ + if (node->type() == QSGNode::GeometryNodeType && node->element()->batch) { + VisualizeShader *shader = static_cast(m_visualizeProgram); + QSGGeometryNode *gn = static_cast(node->sgNode); + + QMatrix4x4 matrix = m_renderer->m_current_projection_matrix; + matrix(2, 2) = m_renderer->m_zRange; + matrix(2, 3) = 1.0f - node->element()->order * m_renderer->m_zRange; + + if (node->element()->batch->root) + matrix = matrix * qsg_matrixForRoot(node->element()->batch->root); + matrix = matrix * *gn->matrix(); + shader->setUniformValue(shader->matrix, matrix); + + QColor color = node->element()->batch->isOpaque ? QColor::fromRgbF(0.3, 1.0, 0.3) : QColor::fromRgbF(1.0, 0.3, 0.3); + float ca = 0.33f; + shader->setUniformValue(shader->color, color.redF() * ca, color.greenF() * ca, color.blueF() * ca, ca); + + visualizeDrawGeometry(gn->geometry()); + } + + SHADOWNODE_TRAVERSE(node) { + visualizeOverdraw_helper(child); + } +} + +void OpenGLVisualizer::visualizeOverdraw() +{ + VisualizeShader *shader = static_cast(m_visualizeProgram); + shader->setUniformValue(shader->color, 0.5f, 0.5f, 1.0f, 1.0f); + shader->setUniformValue(shader->projection, 1); + + m_funcs->glBlendFunc(GL_ONE, GL_ONE); + + static float step = 0; + step += static_cast(M_PI * 2 / 1000.); + if (step > M_PI * 2) + step = 0; + float angle = 80.0 * std::sin(step); + + QMatrix4x4 xrot; xrot.rotate(20, 1, 0, 0); + QMatrix4x4 zrot; zrot.rotate(angle, 0, 0, 1); + QMatrix4x4 tx; tx.translate(0, 0, 1); + + QMatrix4x4 m; + +// m.rotate(180, 0, 1, 0); + + m.translate(0, 0.5, 4); + m.scale(2, 2, 1); + + m.rotate(-30, 1, 0, 0); + m.rotate(angle, 0, 1, 0); + m.translate(0, 0, -1); + + shader->setUniformValue(shader->rotation, m); + + float box[] = { + // lower + -1, 1, 0, 1, 1, 0, + -1, 1, 0, -1, -1, 0, + 1, 1, 0, 1, -1, 0, + -1, -1, 0, 1, -1, 0, + + // upper + -1, 1, 1, 1, 1, 1, + -1, 1, 1, -1, -1, 1, + 1, 1, 1, 1, -1, 1, + -1, -1, 1, 1, -1, 1, + + // sides + -1, -1, 0, -1, -1, 1, + 1, -1, 0, 1, -1, 1, + -1, 1, 0, -1, 1, 1, + 1, 1, 0, 1, 1, 1 + }; + m_funcs->glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, box); + m_funcs->glLineWidth(2); + m_funcs->glDrawArrays(GL_LINES, 0, 24); + + visualizeOverdraw_helper(m_renderer->m_nodes.value(m_renderer->rootNode())); +} + +void OpenGLVisualizer::visualize() +{ + if (m_visualizeMode == VisualizeNothing) + return; + + if (!m_visualizeProgram) { + VisualizeShader *prog = new VisualizeShader(); + QSGShaderSourceBuilder::initializeProgramFromFiles( + prog, + QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.vert"), + QStringLiteral(":/qt-project.org/scenegraph/shaders/visualization.frag")); + prog->bindAttributeLocation("v", 0); + prog->link(); + prog->bind(); + prog->color = prog->uniformLocation("color"); + prog->pattern = prog->uniformLocation("pattern"); + prog->projection = prog->uniformLocation("projection"); + prog->matrix = prog->uniformLocation("matrix"); + prog->rotation = prog->uniformLocation("rotation"); + m_visualizeProgram = prog; + } else { + m_visualizeProgram->bind(); + } + VisualizeShader *shader = static_cast(m_visualizeProgram); + + m_funcs->glDisable(GL_DEPTH_TEST); + m_funcs->glEnable(GL_BLEND); + m_funcs->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + m_funcs->glEnableVertexAttribArray(0); + + // Blacken out the actual rendered content... + float bgOpacity = 0.8f; + if (m_visualizeMode == VisualizeBatches) + bgOpacity = 1.0; + float v[] = { -1, 1, 1, 1, -1, -1, 1, -1 }; + shader->setUniformValue(shader->color, 0.0f, 0.0f, 0.0f, bgOpacity); + shader->setUniformValue(shader->matrix, QMatrix4x4()); + shader->setUniformValue(shader->rotation, QMatrix4x4()); + shader->setUniformValue(shader->pattern, 0.0f); + shader->setUniformValue(shader->projection, false); + m_funcs->glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, v); + m_funcs->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + if (m_visualizeMode == VisualizeBatches) { + srand(0); // To force random colors to be roughly the same every time.. + for (int i = 0; i < m_renderer->m_opaqueBatches.size(); ++i) + visualizeBatch(m_renderer->m_opaqueBatches.at(i)); + for (int i = 0; i < m_renderer->m_alphaBatches.size(); ++i) + visualizeBatch(m_renderer->m_alphaBatches.at(i)); + } else if (m_visualizeMode == VisualizeClipping) { + shader->setUniformValue(shader->pattern, 0.5f); + shader->setUniformValue(shader->color, 0.2f, 0.0f, 0.0f, 0.2f); + visualizeClipping(m_renderer->rootNode()); + } else if (m_visualizeMode == VisualizeChanges) { + visualizeChanges(m_renderer->m_nodes.value(m_renderer->rootNode())); + m_visualizeChangeSet.clear(); + } else if (m_visualizeMode == VisualizeOverdraw) { + visualizeOverdraw(); + } + + // Reset state back to defaults.. + m_funcs->glDisable(GL_BLEND); + m_funcs->glDisableVertexAttribArray(0); + shader->release(); +} + +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/coreapi/qsgopenglvisualizer_p.h b/src/quick/scenegraph/coreapi/qsgopenglvisualizer_p.h new file mode 100644 index 0000000000..0b21694351 --- /dev/null +++ b/src/quick/scenegraph/coreapi/qsgopenglvisualizer_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Jolla Ltd, author: +** Copyright (C) 2016 Robin Burchell +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGOPENGLVISUALIZER_P_H +#define QSGOPENGLVISUALIZER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgbatchrenderer_p.h" + +QT_BEGIN_NAMESPACE + +namespace QSGBatchRenderer +{ + +// ### Qt 6: remove +class OpenGLVisualizer : public Visualizer +{ +public: + OpenGLVisualizer(Renderer *renderer); + ~OpenGLVisualizer(); + + void prepareVisualize() override; + void visualize() override; + + void releaseResources() override; + +private: + void visualizeBatch(Batch *b); + void visualizeClipping(QSGNode *node); + void visualizeChanges(Node *n); + void visualizeOverdraw(); + void visualizeOverdraw_helper(Node *node); + void visualizeDrawGeometry(const QSGGeometry *g); + + QOpenGLFunctions *m_funcs; + QOpenGLShaderProgram *m_visualizeProgram; +}; + +} // namespace QSGBatchRenderer + +QT_END_NAMESPACE + +#endif // QSGOPENGLVISUALIZER_P_H diff --git a/src/quick/scenegraph/coreapi/qsgrenderer_p.h b/src/quick/scenegraph/coreapi/qsgrenderer_p.h index 9c83ddf111..c4ed0072f6 100644 --- a/src/quick/scenegraph/coreapi/qsgrenderer_p.h +++ b/src/quick/scenegraph/coreapi/qsgrenderer_p.h @@ -97,6 +97,7 @@ public: inline QSGMaterialShader::RenderState state(QSGMaterialShader::RenderState::DirtyStates dirty) const; inline QSGMaterialRhiShader::RenderState rhiState(QSGMaterialRhiShader::RenderState::DirtyStates dirty) const; virtual void setCustomRenderMode(const QByteArray &) { } + virtual bool hasCustomRenderModeWithContinuousUpdate() const { return false; } virtual void releaseCachedResources() { } void clearChangedFlag() { m_changed_emitted = false; } diff --git a/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp new file mode 100644 index 0000000000..38d4c4440f --- /dev/null +++ b/src/quick/scenegraph/coreapi/qsgrhivisualizer.cpp @@ -0,0 +1,929 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2016 Jolla Ltd, author: +** Copyright (C) 2016 Robin Burchell +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgrhivisualizer_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QSGBatchRenderer +{ + +#define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling()) +#define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling()) +#define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \ + | QSGNode::DirtyOpacity \ + | QSGNode::DirtyMatrix \ + | QSGNode::DirtyNodeRemoved) + +QMatrix4x4 qsg_matrixForRoot(Node *node); +QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a); +QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry); +QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode); + +RhiVisualizer::RhiVisualizer(Renderer *renderer) + : Visualizer(renderer) +{ +} + +RhiVisualizer::~RhiVisualizer() +{ + releaseResources(); +} + +void RhiVisualizer::releaseResources() +{ + m_pipelines.releaseResources(); + + m_fade.releaseResources(); + + m_changeVis.releaseResources(); + m_batchVis.releaseResources(); + m_clipVis.releaseResources(); + m_overdrawVis.releaseResources(); +} + +void RhiVisualizer::prepareVisualize() +{ + // Called before the render pass has begun (but after preparing the + // batches). Now is the time to put resource updates to the renderer's + // current m_resourceUpdates instance. + + if (m_visualizeMode == VisualizeNothing) + return; + + if (!m_vs.isValid()) { + m_vs = QSGMaterialRhiShaderPrivate::loadShader( + QLatin1String(":/qt-project.org/scenegraph/shaders_ng/visualization.vert.qsb")); + m_fs = QSGMaterialRhiShaderPrivate::loadShader( + QLatin1String(":/qt-project.org/scenegraph/shaders_ng/visualization.frag.qsb")); + } + + m_fade.prepare(this, m_renderer->m_rhi, m_renderer->m_resourceUpdates, m_renderer->renderPassDescriptor()); + + const bool forceUintIndex = m_renderer->m_uint32IndexForRhi; + + switch (m_visualizeMode) { + case VisualizeBatches: + m_batchVis.prepare(m_renderer->m_opaqueBatches, m_renderer->m_alphaBatches, + this, + m_renderer->m_rhi, m_renderer->m_resourceUpdates, + forceUintIndex); + break; + case VisualizeClipping: + m_clipVis.prepare(m_renderer->rootNode(), this, + m_renderer->m_rhi, m_renderer->m_resourceUpdates); + break; + case VisualizeChanges: + m_changeVis.prepare(m_renderer->m_nodes.value(m_renderer->rootNode()), + this, + m_renderer->m_rhi, m_renderer->m_resourceUpdates); + m_visualizeChangeSet.clear(); + break; + case VisualizeOverdraw: + m_overdrawVis.prepare(m_renderer->m_nodes.value(m_renderer->rootNode()), + this, + m_renderer->m_rhi, m_renderer->m_resourceUpdates); + break; + default: + Q_UNREACHABLE(); + break; + } +} + +void RhiVisualizer::visualize() +{ + if (m_visualizeMode == VisualizeNothing) + return; + + QRhiCommandBuffer *cb = m_renderer->commandBuffer(); + m_fade.render(cb); + + switch (m_visualizeMode) { + case VisualizeBatches: + m_batchVis.render(cb); + break; + case VisualizeClipping: + m_clipVis.render(cb); + break; + case VisualizeChanges: + m_changeVis.render(cb); + break; + case VisualizeOverdraw: + m_overdrawVis.render(cb); + break; + default: + Q_UNREACHABLE(); + break; + } +} + +void RhiVisualizer::recordDrawCalls(const QVector &drawCalls, + QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + bool blendOneOne) +{ + for (const DrawCall &dc : drawCalls) { + QRhiGraphicsPipeline *ps = m_pipelines.pipeline(this, m_renderer->m_rhi, srb, m_renderer->renderPassDescriptor(), + dc.vertex.topology, dc.vertex.format, dc.vertex.stride, + blendOneOne); + if (!ps) + continue; + cb->setGraphicsPipeline(ps); // no-op if same as the last one + QRhiCommandBuffer::DynamicOffset dynofs(0, dc.buf.ubufOffset); + cb->setShaderResources(srb, 1, &dynofs); + QRhiCommandBuffer::VertexInput vb(dc.buf.vbuf, dc.buf.vbufOffset); + if (dc.index.count) { + cb->setVertexInput(0, 1, &vb, dc.buf.ibuf, dc.buf.ibufOffset, dc.index.format); + cb->drawIndexed(dc.index.count); + } else { + cb->setVertexInput(0, 1, &vb); + cb->draw(dc.vertex.count); + } + } +} + +const QRhiShaderResourceBinding::StageFlags ubufVisibility = + QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + +void RhiVisualizer::Fade::prepare(RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u, QRhiRenderPassDescriptor *rpDesc) +{ + this->visualizer = visualizer; + + if (!vbuf) { + float v[] = { -1, 1, 1, 1, -1, -1, 1, -1 }; + vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(v)); + if (!vbuf->build()) + return; + u->uploadStaticBuffer(vbuf, v); + } + + if (!ubuf) { + ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE); + if (!ubuf->build()) + return; + float bgOpacity = 0.8f; + if (visualizer->m_visualizeMode == Visualizer::VisualizeBatches) + bgOpacity = 1.0; + QMatrix4x4 ident; + u->updateDynamicBuffer(ubuf, 0, 64, ident.constData()); // matrix + u->updateDynamicBuffer(ubuf, 64, 64, ident.constData()); // rotation + float color[4] = { 0.0f, 0.0f, 0.0f, bgOpacity }; + u->updateDynamicBuffer(ubuf, 128, 16, color); + float pattern = 0.0f; + u->updateDynamicBuffer(ubuf, 144, 4, &pattern); + qint32 projection = 0; + u->updateDynamicBuffer(ubuf, 148, 4, &projection); + } + + if (!srb) { + srb = rhi->newShaderResourceBindings(); + srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, ubufVisibility, ubuf) }); + if (!srb->build()) + return; + } + + if (!ps) { + ps = rhi->newGraphicsPipeline(); + ps->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QRhiGraphicsPipeline::TargetBlend blend; // defaults to premul alpha, just what we need + blend.enable = true; + ps->setTargetBlends({ blend }); + ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, + { QRhiShaderStage::Fragment, visualizer->m_fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb); + ps->setRenderPassDescriptor(rpDesc); + if (!ps->build()) + return; + } +} + +void RhiVisualizer::Fade::releaseResources() +{ + delete ps; + ps = nullptr; + + delete srb; + srb = nullptr; + + delete ubuf; + ubuf = nullptr; + + delete vbuf; + vbuf = nullptr; +} + +void RhiVisualizer::Fade::render(QRhiCommandBuffer *cb) +{ + cb->setGraphicsPipeline(ps); + cb->setViewport(visualizer->m_renderer->m_pstate.viewport); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vb(vbuf, 0); + cb->setVertexInput(0, 1, &vb); + cb->draw(4); +} + +static void fillVertexIndex(RhiVisualizer::DrawCall *dc, QSGGeometry *g, bool withData, bool forceUintIndex) +{ + dc->vertex.topology = qsg_topology(g->drawingMode()); + dc->vertex.format = qsg_vertexInputFormat(g->attributes()[0]); + dc->vertex.count = g->vertexCount(); + dc->vertex.stride = g->sizeOfVertex(); + if (withData) + dc->vertex.data = g->vertexData(); + + dc->index.format = forceUintIndex ? QRhiCommandBuffer::IndexUInt32 : qsg_indexFormat(g); + dc->index.count = g->indexCount(); + dc->index.stride = forceUintIndex ? sizeof(quint32) : g->sizeOfIndex(); + if (withData && g->indexCount()) + dc->index.data = g->indexData(); +} + +static inline uint aligned(uint v, uint byteAlign) +{ + return (v + byteAlign - 1) & ~(byteAlign - 1); +} + +static bool ensureBuffer(QRhi *rhi, QRhiBuffer **buf, QRhiBuffer::UsageFlags usage, int newSize) +{ + if (!*buf) { + *buf = rhi->newBuffer(QRhiBuffer::Dynamic, usage, newSize); + if (!(*buf)->build()) + return false; + } else if ((*buf)->size() < newSize) { + (*buf)->setSize(newSize); + if (!(*buf)->build()) + return false; + } + return true; +} + +QRhiGraphicsPipeline *RhiVisualizer::PipelineCache::pipeline(RhiVisualizer *visualizer, + QRhi *rhi, + QRhiShaderResourceBindings *srb, + QRhiRenderPassDescriptor *rpDesc, + QRhiGraphicsPipeline::Topology topology, + QRhiVertexInputAttribute::Format vertexFormat, + quint32 vertexStride, + bool blendOneOne) +{ + for (int i = 0, ie = pipelines.count(); i != ie; ++i) { + const Pipeline &p(pipelines.at(i)); + if (p.topology == topology && p.format == vertexFormat && p.stride == vertexStride) + return p.ps; + } + + QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline(); + ps->setTopology(topology); + QRhiGraphicsPipeline::TargetBlend blend; // premul alpha + blend.enable = true; + if (blendOneOne) { + // only for visualizing overdraw, other modes use premul alpha + blend.srcColor = QRhiGraphicsPipeline::One; + blend.dstColor = QRhiGraphicsPipeline::One; + blend.srcAlpha = QRhiGraphicsPipeline::One; + blend.dstAlpha = QRhiGraphicsPipeline::One; + } + ps->setTargetBlends({ blend }); + ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, + { QRhiShaderStage::Fragment, visualizer->m_fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { vertexStride } }); + inputLayout.setAttributes({ { 0, 0, vertexFormat, 0 } }); + ps->setVertexInputLayout(inputLayout); + ps->setShaderResourceBindings(srb); + ps->setRenderPassDescriptor(rpDesc); + if (!ps->build()) + return nullptr; + + Pipeline p; + p.topology = topology; + p.format = vertexFormat; + p.stride = vertexStride; + p.ps = ps; + pipelines.append(p); + + return ps; +} + +void RhiVisualizer::PipelineCache::releaseResources() +{ + for (int i = 0, ie = pipelines.count(); i != ie; ++i) + delete pipelines.at(i).ps; + + pipelines.clear(); +} + +void RhiVisualizer::ChangeVis::gather(Node *n) +{ + if (n->type() == QSGNode::GeometryNodeType && n->element()->batch && visualizer->m_visualizeChangeSet.contains(n)) { + const uint dirty = visualizer->m_visualizeChangeSet.value(n); + const bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0; + const QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0f, 0.3f, 1.0f); + const float alpha = 0.5f; + + QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; + if (n->element()->batch->root) + matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); + + QSGGeometryNode *gn = static_cast(n->sgNode); + matrix = matrix * *gn->matrix(); + + QSGGeometry *g = gn->geometry(); + if (g->attributeCount() >= 1) { + DrawCall dc; + memcpy(dc.uniforms.data, matrix.constData(), 64); + QMatrix4x4 rotation; + memcpy(dc.uniforms.data + 64, rotation.constData(), 64); + float c[4] = { + float(color.redF()) * alpha, + float(color.greenF()) * alpha, + float(color.blueF()) * alpha, + alpha + }; + memcpy(dc.uniforms.data + 128, c, 16); + float pattern = tinted ? 0.5f : 0.0f; + memcpy(dc.uniforms.data + 144, &pattern, 4); + qint32 projection = 0; + memcpy(dc.uniforms.data + 148, &projection, 4); + + fillVertexIndex(&dc, g, true, false); + drawCalls.append(dc); + } + + // This is because many changes don't propegate their dirty state to the + // parent so the node updater will not unset these states. They are + // not used for anything so, unsetting it should have no side effects. + n->dirtyState = nullptr; + } + + SHADOWNODE_TRAVERSE(n) { + gather(child); + } +} + +void RhiVisualizer::ChangeVis::prepare(Node *n, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u) +{ + this->visualizer = visualizer; + + drawCalls.clear(); + gather(n); + + if (drawCalls.isEmpty()) + return; + + const int ubufAlign = rhi->ubufAlignment(); + int vbufOffset = 0; + int ibufOffset = 0; + int ubufOffset = 0; + for (RhiVisualizer::DrawCall &dc : drawCalls) { + dc.buf.vbufOffset = aligned(vbufOffset, 4); + vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; + + dc.buf.ibufOffset = aligned(ibufOffset, 4); + ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; + + dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); + ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; + } + + ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); + if (ibufOffset) + ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); + const int ubufSize = ubufOffset; + ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); + + for (RhiVisualizer::DrawCall &dc : drawCalls) { + u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); + dc.buf.vbuf = vbuf; + if (dc.index.count) { + u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); + dc.buf.ibuf = ibuf; + } + u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); + } + + if (!srb) { + srb = rhi->newShaderResourceBindings(); + srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); + if (!srb->build()) + return; + } +} + +void RhiVisualizer::ChangeVis::releaseResources() +{ + delete srb; + srb = nullptr; + + delete ubuf; + ubuf = nullptr; + + delete ibuf; + ibuf = nullptr; + + delete vbuf; + vbuf = nullptr; +} + +void RhiVisualizer::ChangeVis::render(QRhiCommandBuffer *cb) +{ + visualizer->recordDrawCalls(drawCalls, cb, srb); +} + +void RhiVisualizer::BatchVis::gather(Batch *b) +{ + if (b->positionAttribute != 0) + return; + + QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix); + if (b->root) + matrix = matrix * qsg_matrixForRoot(b->root); + + DrawCall dc; + + QMatrix4x4 rotation; + memcpy(dc.uniforms.data + 64, rotation.constData(), 64); + + QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0); + + float c[4] = { + float(color.redF()), + float(color.greenF()), + float(color.blueF()), + 1.0f + }; + memcpy(dc.uniforms.data + 128, c, 16); + + float pattern = b->merged ? 0.0f : 1.0f; + memcpy(dc.uniforms.data + 144, &pattern, 4); + + qint32 projection = 0; + memcpy(dc.uniforms.data + 148, &projection, 4); + + if (b->merged) { + memcpy(dc.uniforms.data, matrix.constData(), 64); + + QSGGeometryNode *gn = b->first->node; + QSGGeometry *g = gn->geometry(); + + fillVertexIndex(&dc, g, false, forceUintIndex); + + for (int ds = 0; ds < b->drawSets.size(); ++ds) { + const DrawSet &set = b->drawSets.at(ds); + dc.buf.vbuf = b->vbo.buf; + dc.buf.vbufOffset = set.vertices; + dc.buf.ibuf = b->ibo.buf; + dc.buf.ibufOffset = set.indices; + dc.index.count = set.indexCount; + drawCalls.append(dc); + } + } else { + Element *e = b->first; + int vOffset = 0; + int iOffset = 0; + + while (e) { + QSGGeometryNode *gn = e->node; + QSGGeometry *g = gn->geometry(); + + QMatrix4x4 m = matrix * *gn->matrix(); + memcpy(dc.uniforms.data, m.constData(), 64); + + fillVertexIndex(&dc, g, false, forceUintIndex); + + dc.buf.vbuf = b->vbo.buf; + dc.buf.vbufOffset = vOffset; + if (g->indexCount()) { + dc.buf.ibuf = b->ibo.buf; + dc.buf.ibufOffset = iOffset; + } + + drawCalls.append(dc); + + vOffset += dc.vertex.count * dc.vertex.stride; + iOffset += dc.index.count * dc.index.stride; + + e = e->nextInBatch; + } + } +} + +void RhiVisualizer::BatchVis::prepare(const QDataBuffer &opaqueBatches, const QDataBuffer &alphaBatches, + RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u, + bool forceUintIndex) +{ + this->visualizer = visualizer; + this->forceUintIndex = forceUintIndex; + + drawCalls.clear(); + + srand(0); // To force random colors to be roughly the same every time.. + for (int i = 0; i < opaqueBatches.size(); ++i) + gather(opaqueBatches.at(i)); + for (int i = 0; i < alphaBatches.size(); ++i) + gather(alphaBatches.at(i)); + + if (drawCalls.isEmpty()) + return; + + const int ubufAlign = rhi->ubufAlignment(); + int ubufOffset = 0; + for (RhiVisualizer::DrawCall &dc : drawCalls) { + dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); + ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; + } + + const int ubufSize = ubufOffset; + ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); + + for (RhiVisualizer::DrawCall &dc : drawCalls) + u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); + + if (!srb) { + srb = rhi->newShaderResourceBindings(); + srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); + if (!srb->build()) + return; + } +} + +void RhiVisualizer::BatchVis::releaseResources() +{ + delete srb; + srb = nullptr; + + delete ubuf; + ubuf = nullptr; +} + +void RhiVisualizer::BatchVis::render(QRhiCommandBuffer *cb) +{ + visualizer->recordDrawCalls(drawCalls, cb, srb); +} + +void RhiVisualizer::ClipVis::gather(QSGNode *node) +{ + if (node->type() == QSGNode::ClipNodeType) { + QSGClipNode *clipNode = static_cast(node); + QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; + if (clipNode->matrix()) + matrix = matrix * *clipNode->matrix(); + + QSGGeometry *g = clipNode->geometry(); + if (g->attributeCount() >= 1) { + DrawCall dc; + memcpy(dc.uniforms.data, matrix.constData(), 64); + QMatrix4x4 rotation; + memcpy(dc.uniforms.data + 64, rotation.constData(), 64); + float c[4] = { 0.2f, 0.0f, 0.0f, 0.2f }; + memcpy(dc.uniforms.data + 128, c, 16); + float pattern = 0.5f; + memcpy(dc.uniforms.data + 144, &pattern, 4); + qint32 projection = 0; + memcpy(dc.uniforms.data + 148, &projection, 4); + fillVertexIndex(&dc, g, true, false); + drawCalls.append(dc); + } + } + + QSGNODE_TRAVERSE(node) { + gather(child); + } +} + +void RhiVisualizer::ClipVis::prepare(QSGNode *node, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u) +{ + this->visualizer = visualizer; + + drawCalls.clear(); + gather(node); + + if (drawCalls.isEmpty()) + return; + + const int ubufAlign = rhi->ubufAlignment(); + int vbufOffset = 0; + int ibufOffset = 0; + int ubufOffset = 0; + for (RhiVisualizer::DrawCall &dc : drawCalls) { + dc.buf.vbufOffset = aligned(vbufOffset, 4); + vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; + + dc.buf.ibufOffset = aligned(ibufOffset, 4); + ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; + + dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); + ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; + } + + ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); + if (ibufOffset) + ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); + const int ubufSize = ubufOffset; + ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); + + for (RhiVisualizer::DrawCall &dc : drawCalls) { + u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); + dc.buf.vbuf = vbuf; + if (dc.index.count) { + u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); + dc.buf.ibuf = ibuf; + } + u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); + } + + if (!srb) { + srb = rhi->newShaderResourceBindings(); + srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); + if (!srb->build()) + return; + } +} + +void RhiVisualizer::ClipVis::releaseResources() +{ + delete srb; + srb = nullptr; + + delete ubuf; + ubuf = nullptr; + + delete ibuf; + ibuf = nullptr; + + delete vbuf; + vbuf = nullptr; +} + +void RhiVisualizer::ClipVis::render(QRhiCommandBuffer *cb) +{ + visualizer->recordDrawCalls(drawCalls, cb, srb); +} + +void RhiVisualizer::OverdrawVis::gather(Node *n) +{ + if (n->type() == QSGNode::GeometryNodeType && n->element()->batch) { + QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; + matrix(2, 2) = visualizer->m_renderer->m_zRange; + matrix(2, 3) = 1.0f - n->element()->order * visualizer->m_renderer->m_zRange; + + if (n->element()->batch->root) + matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); + + QSGGeometryNode *gn = static_cast(n->sgNode); + matrix = matrix * *gn->matrix(); + + QSGGeometry *g = gn->geometry(); + if (g->attributeCount() >= 1) { + DrawCall dc; + memcpy(dc.uniforms.data, matrix.constData(), 64); + memcpy(dc.uniforms.data + 64, rotation.constData(), 64); + + float c[4]; + const float ca = 0.33f; + if (n->element()->batch->isOpaque) { + c[0] = ca * 0.3f; c[1] = ca * 1.0f; c[2] = ca * 0.3f; c[3] = ca; + } else { + c[0] = ca * 1.0f; c[1] = ca * 0.3f; c[2] = ca * 0.3f; c[3] = ca; + } + memcpy(dc.uniforms.data + 128, c, 16); + float pattern = 0.0f; + memcpy(dc.uniforms.data + 144, &pattern, 4); + qint32 projection = 1; + memcpy(dc.uniforms.data + 148, &projection, 4); + + fillVertexIndex(&dc, g, true, false); + drawCalls.append(dc); + } + } + + SHADOWNODE_TRAVERSE(n) { + gather(child); + } +} + +void RhiVisualizer::OverdrawVis::prepare(Node *n, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u) +{ + this->visualizer = visualizer; + + step += float(M_PI * 2 / 1000.0); + if (step > float(M_PI * 2)) + step = 0.0f; + + const float yfix = rhi->isYUpInNDC() ? 1.0f : -1.0f; + rotation.setToIdentity(); + rotation.translate(0.0f, 0.5f * yfix, 4.0f); + rotation.scale(2.0f, 2.0f, 1.0f); + rotation.rotate(-30.0f * yfix, 1.0f, 0.0f, 0.0f); + rotation.rotate(80.0f * std::sin(step), 0.0f, 1.0f, 0.0f); + rotation.translate(0.0f, 0.0f, -1.0f); + + drawCalls.clear(); + gather(n); + + if (!box.vbuf) { + const float v[] = { + // lower + -1, 1, 0, 1, 1, 0, + -1, 1, 0, -1, -1, 0, + 1, 1, 0, 1, -1, 0, + -1, -1, 0, 1, -1, 0, + + // upper + -1, 1, 1, 1, 1, 1, + -1, 1, 1, -1, -1, 1, + 1, 1, 1, 1, -1, 1, + -1, -1, 1, 1, -1, 1, + + // sides + -1, -1, 0, -1, -1, 1, + 1, -1, 0, 1, -1, 1, + -1, 1, 0, -1, 1, 1, + 1, 1, 0, 1, 1, 1 + }; + box.vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(v)); + if (!box.vbuf->build()) + return; + u->uploadStaticBuffer(box.vbuf, v); + } + + if (!box.ubuf) { + box.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE); + if (!box.ubuf->build()) + return; + QMatrix4x4 ident; + u->updateDynamicBuffer(box.ubuf, 0, 64, ident.constData()); + float color[4] = { 0.5f, 0.5f, 1.0f, 1.0f }; + u->updateDynamicBuffer(box.ubuf, 128, 16, color); + float pattern = 0.0f; + u->updateDynamicBuffer(box.ubuf, 144, 4, &pattern); + qint32 projection = 1; + u->updateDynamicBuffer(box.ubuf, 148, 4, &projection); + } + + u->updateDynamicBuffer(box.ubuf, 64, 64, rotation.constData()); + + if (!box.srb) { + box.srb = rhi->newShaderResourceBindings(); + box.srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, ubufVisibility, box.ubuf) }); + if (!box.srb->build()) + return; + } + + if (!box.ps) { + box.ps = rhi->newGraphicsPipeline(); + box.ps->setTopology(QRhiGraphicsPipeline::Lines); + box.ps->setLineWidth(2); // may be be ignored (D3D, Metal), but may be used on GL and Vulkan + QRhiGraphicsPipeline::TargetBlend blend; + blend.enable = true; + blend.srcColor = QRhiGraphicsPipeline::One; + blend.dstColor = QRhiGraphicsPipeline::One; + blend.srcAlpha = QRhiGraphicsPipeline::One; + blend.dstAlpha = QRhiGraphicsPipeline::One; + box.ps->setTargetBlends({ blend }); + box.ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, + { QRhiShaderStage::Fragment, visualizer->m_fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 3 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } }); + box.ps->setVertexInputLayout(inputLayout); + box.ps->setShaderResourceBindings(box.srb); + box.ps->setRenderPassDescriptor(visualizer->m_renderer->renderPassDescriptor()); + if (!box.ps->build()) + return; + } + + if (drawCalls.isEmpty()) + return; + + const int ubufAlign = rhi->ubufAlignment(); + int vbufOffset = 0; + int ibufOffset = 0; + int ubufOffset = 0; + for (RhiVisualizer::DrawCall &dc : drawCalls) { + dc.buf.vbufOffset = aligned(vbufOffset, 4); + vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; + + dc.buf.ibufOffset = aligned(ibufOffset, 4); + ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; + + dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); + ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; + } + + ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); + if (ibufOffset) + ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); + const int ubufSize = ubufOffset; + ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); + + for (RhiVisualizer::DrawCall &dc : drawCalls) { + u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); + dc.buf.vbuf = vbuf; + if (dc.index.count) { + u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); + dc.buf.ibuf = ibuf; + } + u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); + } + + if (!srb) { + srb = rhi->newShaderResourceBindings(); + srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); + if (!srb->build()) + return; + } +} + +void RhiVisualizer::OverdrawVis::releaseResources() +{ + delete srb; + srb = nullptr; + + delete ubuf; + ubuf = nullptr; + + delete ibuf; + ibuf = nullptr; + + delete vbuf; + vbuf = nullptr; + + delete box.ps; + box.ps = nullptr; + + delete box.srb; + box.srb = nullptr; + + delete box.ubuf; + box.ubuf = nullptr; + + delete box.vbuf; + box.vbuf = nullptr; +} + +void RhiVisualizer::OverdrawVis::render(QRhiCommandBuffer *cb) +{ + cb->setGraphicsPipeline(box.ps); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vb(box.vbuf, 0); + cb->setVertexInput(0, 1, &vb); + cb->draw(24); + + visualizer->recordDrawCalls(drawCalls, cb, srb, true); +} + +} + +QT_END_NAMESPACE diff --git a/src/quick/scenegraph/coreapi/qsgrhivisualizer_p.h b/src/quick/scenegraph/coreapi/qsgrhivisualizer_p.h new file mode 100644 index 0000000000..1ce52a81f1 --- /dev/null +++ b/src/quick/scenegraph/coreapi/qsgrhivisualizer_p.h @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Jolla Ltd, author: +** Copyright (C) 2016 Robin Burchell +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGRHIVISUALIZER_P_H +#define QSGRHIVISUALIZER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgbatchrenderer_p.h" + +QT_BEGIN_NAMESPACE + +namespace QSGBatchRenderer +{ + +class RhiVisualizer : public Visualizer +{ +public: + RhiVisualizer(Renderer *renderer); + ~RhiVisualizer(); + + void prepareVisualize() override; + void visualize() override; + + void releaseResources() override; + + struct DrawCall + { + static const int UBUF_SIZE = 152; // visualization.vert/frag + struct { + char data[UBUF_SIZE]; // matrix, rotation, color, pattern, projection + } uniforms; + struct { + QRhiGraphicsPipeline::Topology topology; + QRhiVertexInputAttribute::Format format; + int count; + int stride; + const void *data; // only when using own vbuf + } vertex; + struct { + QRhiCommandBuffer::IndexFormat format; + int count; + int stride; + const void *data; // only when using own ibuf + } index; + struct { + QRhiBuffer *vbuf; // either same for all draw calls and owned by the *Vis, or points to a Batch.Buffer.vbo.buf + int vbufOffset; + QRhiBuffer *ibuf; // same, but for index + int ibufOffset; + int ubufOffset; + } buf; + }; + +private: + QShader m_vs; + QShader m_fs; + + void recordDrawCalls(const QVector &drawCalls, + QRhiCommandBuffer *cb, + QRhiShaderResourceBindings *srb, + bool blendOneOne = false); + + class PipelineCache { + public: + QRhiGraphicsPipeline *pipeline(RhiVisualizer *visualizer, + QRhi *rhi, + QRhiShaderResourceBindings *srb, + QRhiRenderPassDescriptor *rpDesc, + QRhiGraphicsPipeline::Topology topology, + QRhiVertexInputAttribute::Format vertexFormat, + quint32 vertexStride, + bool blendOneOne); + void releaseResources(); + private: + struct Pipeline { + QRhiGraphicsPipeline::Topology topology; + QRhiVertexInputAttribute::Format format; + quint32 stride; + QRhiGraphicsPipeline *ps; + }; + QVarLengthArray pipelines; + }; + + PipelineCache m_pipelines; + + class Fade { + public: + void prepare(RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u, QRhiRenderPassDescriptor *rpDesc); + void releaseResources(); + void render(QRhiCommandBuffer *cb); + private: + RhiVisualizer *visualizer; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + } m_fade; + + class ChangeVis { + public: + void prepare(Node *n, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u); + void releaseResources(); + void render(QRhiCommandBuffer *cb); + private: + void gather(Node *n); + RhiVisualizer *visualizer; + QVector drawCalls; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + } m_changeVis; + + class BatchVis { + public: + void prepare(const QDataBuffer &opaqueBatches, + const QDataBuffer &alphaBatches, + RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u, + bool forceUintIndex); + void releaseResources(); + void render(QRhiCommandBuffer *cb); + private: + void gather(Batch *b); + RhiVisualizer *visualizer; + bool forceUintIndex; + QVector drawCalls; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + } m_batchVis; + + class ClipVis { + public: + void prepare(QSGNode *node, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u); + void releaseResources(); + void render(QRhiCommandBuffer *cb); + private: + void gather(QSGNode *node); + RhiVisualizer *visualizer; + QVector drawCalls; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + } m_clipVis; + + class OverdrawVis { + public: + void prepare(Node *n, RhiVisualizer *visualizer, + QRhi *rhi, QRhiResourceUpdateBatch *u); + void releaseResources(); + void render(QRhiCommandBuffer *cb); + private: + void gather(Node *n); + RhiVisualizer *visualizer; + QVector drawCalls; + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ibuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + float step = 0.0f; + QMatrix4x4 rotation; + struct { + QRhiBuffer *vbuf = nullptr; + QRhiBuffer *ubuf = nullptr; + QRhiShaderResourceBindings *srb = nullptr; + QRhiGraphicsPipeline *ps = nullptr; + } box; + } m_overdrawVis; + + friend class Fade; + friend class PipelineCache; + friend class ChangeVis; + friend class ClipVis; + friend class OverdrawVis; +}; + +} // namespace QSGBatchRenderer + +QT_END_NAMESPACE + +#endif // QSGRHIVISUALIZER_P_H diff --git a/src/quick/scenegraph/scenegraph.pri b/src/quick/scenegraph/scenegraph.pri index 3390d2b87a..ee9ea0f5ed 100644 --- a/src/quick/scenegraph/scenegraph.pri +++ b/src/quick/scenegraph/scenegraph.pri @@ -37,9 +37,13 @@ SOURCES += \ qtConfig(opengl(es1|es2)?) { HEADERS += \ - $$PWD/coreapi/qsgbatchrenderer_p.h + $$PWD/coreapi/qsgbatchrenderer_p.h \ + $$PWD/coreapi/qsgopenglvisualizer_p.h \ + $$PWD/coreapi/qsgrhivisualizer_p.h SOURCES += \ $$PWD/coreapi/qsgbatchrenderer.cpp \ + $$PWD/coreapi/qsgopenglvisualizer.cpp \ + $$PWD/coreapi/qsgrhivisualizer.cpp \ $$PWD/coreapi/qsgshaderrewriter.cpp } diff --git a/src/quick/scenegraph/scenegraph.qrc b/src/quick/scenegraph/scenegraph.qrc index c7257c17a0..e409f07610 100644 --- a/src/quick/scenegraph/scenegraph.qrc +++ b/src/quick/scenegraph/scenegraph.qrc @@ -121,5 +121,8 @@ shaders_ng/shadereffect.frag.qsb shaders_ng/sprite.vert.qsb shaders_ng/sprite.frag.qsb + + shaders_ng/visualization.vert.qsb + shaders_ng/visualization.frag.qsb diff --git a/src/quick/scenegraph/shaders_ng/compile.bat b/src/quick/scenegraph/shaders_ng/compile.bat index 8ce42f3483..a0c74c22c7 100755 --- a/src/quick/scenegraph/shaders_ng/compile.bat +++ b/src/quick/scenegraph/shaders_ng/compile.bat @@ -82,3 +82,5 @@ qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -o shadereffect.vert.qsb shade qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o shadereffect.frag.qsb shadereffect.frag qsb -b --glsl "150,120,100 es" --hlsl 50 --msl 12 -o sprite.vert.qsb sprite.vert qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o sprite.frag.qsb sprite.frag +qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o visualization.vert.qsb visualization.vert +qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o visualization.frag.qsb visualization.frag diff --git a/src/quick/scenegraph/shaders_ng/visualization.frag b/src/quick/scenegraph/shaders_ng/visualization.frag new file mode 100644 index 0000000000..29f718fe5d --- /dev/null +++ b/src/quick/scenegraph/shaders_ng/visualization.frag @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in vec2 pos; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 matrix; + mat4 rotation; + vec4 color; + float pattern; + int projection; +} ubuf; + +void main(void) +{ + vec4 c = ubuf.color; + c.xyz += pow(max(sin(pos.x + pos.y), 0.0), 2.0) * ubuf.pattern * 0.25; + fragColor = c; +} diff --git a/src/quick/scenegraph/shaders_ng/visualization.frag.qsb b/src/quick/scenegraph/shaders_ng/visualization.frag.qsb new file mode 100644 index 0000000000..eadad927dc Binary files /dev/null and b/src/quick/scenegraph/shaders_ng/visualization.frag.qsb differ diff --git a/src/quick/scenegraph/shaders_ng/visualization.vert b/src/quick/scenegraph/shaders_ng/visualization.vert new file mode 100644 index 0000000000..c29492417a --- /dev/null +++ b/src/quick/scenegraph/shaders_ng/visualization.vert @@ -0,0 +1,28 @@ +#version 440 + +layout(location = 0) in vec4 v; +layout(location = 0) out vec2 pos; + +layout(std140, binding = 0) uniform buf { + mat4 matrix; + mat4 rotation; + vec4 color; + float pattern; + int projection; +} ubuf; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + vec4 p = ubuf.matrix * v; + + if (ubuf.projection != 0) { + vec4 proj = ubuf.rotation * p; + gl_Position = vec4(proj.x, proj.y, 0, proj.z); + } else { + gl_Position = p; + } + + pos = v.xy * 1.37; +} diff --git a/src/quick/scenegraph/shaders_ng/visualization.vert.qsb b/src/quick/scenegraph/shaders_ng/visualization.vert.qsb new file mode 100644 index 0000000000..bd89847dd3 Binary files /dev/null and b/src/quick/scenegraph/shaders_ng/visualization.vert.qsb differ -- cgit v1.2.3 From b407a77380f189438acf08fefff58e7c5b8fd036 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 7 Aug 2019 12:59:42 +0200 Subject: Fix incorrect vertex data and rendering when shapes change scene When changing scene, the stroke vertices were left with uninitialized stroke colors when the item is re-attached to the scene. Marking the shape data as overall dirty and triggering a polish ensures that the data is kept in sync. Change-Id: I3f472734a97908e6e8f6e58230c0c53dcc041868 Done-with: Fabian Kosmale Fixes: QTBUG-77332 Reviewed-by: Fabian Kosmale Reviewed-by: Laszlo Agocs --- src/quickshapes/qquickshape.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/quickshapes/qquickshape.cpp b/src/quickshapes/qquickshape.cpp index e8779debde..04e8de5aa0 100644 --- a/src/quickshapes/qquickshape.cpp +++ b/src/quickshapes/qquickshape.cpp @@ -969,6 +969,11 @@ void QQuickShape::itemChange(ItemChange change, const ItemChangeData &data) // sync may have been deferred; do it now if the item became visible if (change == ItemVisibleHasChanged && data.boolValue) d->_q_shapePathChanged(); + else if (change == QQuickItem::ItemSceneChange) { + for (int i = 0; i < d->sp.count(); ++i) + QQuickShapePathPrivate::get(d->sp[i])->dirty = QQuickShapePathPrivate::DirtyAll; + d->_q_shapePathChanged(); + } QQuickItem::itemChange(change, data); } -- cgit v1.2.3 From b00b6a2a868f4ecffe538f6bd1d3ef399f0f5918 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 6 Aug 2019 15:00:45 +0200 Subject: Fix tests in qtquickcontrols 1 and 2 This patch partially reverts the 5.15 related #ifdefs from commit f38e071f5b353cbf9ce6c6c104bd82099ae0aa14. Those were introduced without any test coverage and our own modules (qtquickcontrols 1 and 2) were not tested with it or adapted. The change of behavior breaks our own existing code. Task-number: QTBUG-33444 Change-Id: Ie9823638c1a02281798b725f745b15e622f837c5 Reviewed-by: Alexandru Croitor Reviewed-by: Fabian Kosmale (cherry picked from commit c273175ffec925a4164de41a79c21d785a1761a7) Reviewed-by: Simon Hausmann --- src/qml/types/qqmlbind.cpp | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp index 5067b6a02e..4b60108597 100644 --- a/src/qml/types/qqmlbind.cpp +++ b/src/qml/types/qqmlbind.cpp @@ -70,12 +70,8 @@ public: , delayed(false) , pendingEval(false) , restoreBinding(true) -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) , restoreValue(false) , restoreModeExplicit(false) -#else - , restoreValue(true) -#endif {} ~QQmlBindPrivate() { } @@ -93,9 +89,7 @@ public: bool pendingEval:1; bool restoreBinding:1; bool restoreValue:1; -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) bool restoreModeExplicit:1; -#endif void validate(QObject *binding) const; void clearPrev(); @@ -365,8 +359,7 @@ void QQmlBind::setDelayed(bool delayed) \li Binding.RestoreBindingOrValue The original value is always restored. \list - \warning The default value is Binding.RestoreBinding. This will change in - Qt 5.15 to Binding.RestoreBindingOrValue. + The default value is Binding.RestoreBinding. If you rely on any specific behavior regarding the restoration of plain values when bindings get disabled you should migrate to explicitly set the @@ -392,9 +385,7 @@ void QQmlBind::setRestoreMode(RestorationMode newMode) if (newMode != restoreMode()) { d->restoreValue = (newMode & RestoreValue); d->restoreBinding = (newMode & RestoreBinding); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) d->restoreModeExplicit = true; -#endif emit restoreModeChanged(); } } @@ -465,27 +456,11 @@ void QQmlBind::eval() Q_ASSERT(vmemo); vmemo->setVMEProperty(propPriv->core.coreIndex(), *d->v4Value.valueRef()); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } else if (d->prevIsVariant) { if (d->restoreValue) { d->prop.write(d->prevValue); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } return; -- cgit v1.2.3 From 00bdbd1ed3d8565a09a32a2b80eb31abef661370 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Wed, 7 Aug 2019 14:25:17 +0200 Subject: Temporarily remove dependencies.yaml We'll re-add it after we get a working qt5.git wip/qt6 build. Change-Id: I13dcd5d427003ad3ea9547f2dfbded0c8c2f99cc Reviewed-by: Aapo Keskimolo Reviewed-by: Simon Hausmann --- dependencies.yaml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 dependencies.yaml diff --git a/dependencies.yaml b/dependencies.yaml deleted file mode 100644 index ac5797762b..0000000000 --- a/dependencies.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- ../qtbase.git: - ref: f955bd8ced8b93d142b7f755665946424c6d3644 - required: true -- ../qtsvg.git: - ref: 4b186657ef09634c082b03570624288cf7c1baba - required: false -- cgit v1.2.3 From fc680af663fc30311b39c033d2d6c2e681a79f34 Mon Sep 17 00:00:00 2001 From: Yulong Bai Date: Wed, 7 Aug 2019 15:00:07 +0200 Subject: Fix undeclared 'memcpy' identifier Change-Id: Ie2290a1734370ff09e499809b4342c38179131e3 Reviewed-by: Simon Hausmann --- src/qml/common/qv4staticvalue_p.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index 8160bbb748..0716f7ea20 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -51,6 +51,7 @@ // #include +#include #ifdef QT_NO_DEBUG #define QV4_NEARLY_ALWAYS_INLINE Q_ALWAYS_INLINE -- cgit v1.2.3 From d1b72c98b4e617530bfb23c3a5b7ebc68c15c089 Mon Sep 17 00:00:00 2001 From: Yulong Bai Date: Wed, 7 Aug 2019 15:28:41 +0200 Subject: Fix unused captured 'this' warning Change-Id: I90ddcfe42cae7364d321bd1c3a05eaba416081d4 Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4executablecompilationunit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp index 7d46ab2ab1..58cd581eae 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit.cpp +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -694,7 +694,7 @@ bool ExecutableCompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorSt } return CompiledData::SaveableUnitPointer(unitData()).saveToDisk( - [this, &unitUrl, errorString](const char *data, quint32 size) { + [&unitUrl, errorString](const char *data, quint32 size) { return CompiledData::SaveableUnitPointer::writeDataToFile(localCacheFilePath(unitUrl), data, size, errorString); }); -- cgit v1.2.3 From db8fc6e8541f132ad84f7d91a0c25ed3073d7afc Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 8 Aug 2019 15:34:46 +0200 Subject: Blacklist a failing test on MinGW 7.3 configuration and Qt 6.0 It's something to do with calling QQuickText::setElideMode(QQuickText::ElideRight) not causing a width change which the spy object expects to happen. I don't know why it happens, but it's the last thing preventing me from getting a working wip/qt6 branch, so I'm gonna blacklist the test on MinGW. Task-number: QTBUG-77394 Change-Id: Ia01e24f4e133c84ff44ce365595ad10292e845f9 Reviewed-by: Simon Hausmann --- tests/auto/quick/qquicktext/BLACKLIST | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/auto/quick/qquicktext/BLACKLIST b/tests/auto/quick/qquicktext/BLACKLIST index 6c3c3af154..5909e988c0 100644 --- a/tests/auto/quick/qquicktext/BLACKLIST +++ b/tests/auto/quick/qquicktext/BLACKLIST @@ -4,3 +4,5 @@ qemu osx-10.12 [fontSizeMode] opensuse-42.1 +[contentSize] +win32 gcc -- cgit v1.2.3 From cb37a1d0c3fce7951f5d7f1fb4a04f90e038f01b Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Fri, 9 Aug 2019 11:00:21 +0200 Subject: Fix the blacklisting of tst_qquicktext::contentSize The correct keyword set for blacklisting a test on mingw is "windows gcc" not "win32 gcc". Amends db8fc6e8541f132ad84f7d91a0c25ed3073d7afc Change-Id: Idad4c9370b7d4c09acde160e6053191df7ee1aad Reviewed-by: Simon Hausmann --- tests/auto/quick/qquicktext/BLACKLIST | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/quick/qquicktext/BLACKLIST b/tests/auto/quick/qquicktext/BLACKLIST index 5909e988c0..594f9af3b3 100644 --- a/tests/auto/quick/qquicktext/BLACKLIST +++ b/tests/auto/quick/qquicktext/BLACKLIST @@ -5,4 +5,4 @@ osx-10.12 [fontSizeMode] opensuse-42.1 [contentSize] -win32 gcc +windows gcc -- cgit v1.2.3