diff --git a/BUILD_LINUX_CHEATSHEET.md b/BUILD_LINUX_CHEATSHEET.md new file mode 100644 index 0000000000..7d77f5d685 --- /dev/null +++ b/BUILD_LINUX_CHEATSHEET.md @@ -0,0 +1,27 @@ + # this guide is specific to Ubuntu 16.04. + # deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com +sudo su - +apt-get -y update +apt-get install -y software-properties-common +apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15FF1AAE +add-apt-repository "deb http://debian.highfidelity.com stable main" +apt-get -y update +apt-get install -y hifi-domain-server +apt-get install -y hifi-assignment-client + + # When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +apt-get install -y hifi-dev-domain-server +apt-get install -y hifi-dev-assignment-client + + # domain server and assignment clients should already be running. The processes are controlled via: +systemctl start hifi-domain-server +systemctl stop hifi-domain-server + + # Once the machine is setup and processes are running you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (further customizations can be done via http://IPAddress:40100). + + # The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. + # As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). +To do this you can modify /etc/crontab by adding the following lines +0 */1 * * * root apt-get update +1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server +2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index dd3050ba4e..b37784cddc 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -46,8 +46,14 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption helpOption = parser.addHelpOption(); - const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION, - "run single assignment client of given type", "type"); + QString typeDescription = "run single assignment client of given type\n# | Type\n============================"; + for (Assignment::Type type = Assignment::FirstType; + type != Assignment::AllTypes; + type = static_cast(static_cast(type) + 1)) { + typeDescription.append(QStringLiteral("\n%1 | %2").arg(QString::number(type), Assignment::typeToString(type))); + } + const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION, typeDescription, "type"); + parser.addOption(clientTypeOption); const QCommandLineOption poolOption(ASSIGNMENT_POOL_OPTION, "set assignment pool", "pool-name"); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 47a81ba1fe..fb4b65726a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -214,7 +214,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) uint64_t getTimestamp() const override { return _lastEncodeTime; } - const AvatarSharedPointer& getAvatar() const { return _avatar; } + AvatarSharedPointer getAvatar() const { return _avatar; } private: AvatarSharedPointer _avatar; @@ -326,7 +326,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); while (!sortedAvatars.empty()) { - const auto& avatarData = sortedAvatars.top().getAvatar(); + const auto avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); remainingAvatars--; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2d8bf7418a..290f4a7f53 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -945,7 +945,7 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) { // enumerate over all assignment types and see if we've already excluded it - for (Assignment::Type defaultedType = Assignment::AudioMixerType; + for (Assignment::Type defaultedType = Assignment::FirstType; defaultedType != Assignment::AllTypes; defaultedType = static_cast(static_cast(defaultedType) + 1)) { if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 7625d69211..e6b3392aad 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -12,8 +12,10 @@ function(JOIN VALUES GLUE OUTPUT) endfunction() -set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) -generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +if (NOT DEV_BUILD) + set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) + generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +endif() # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -72,7 +74,9 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") +if (NOT DEV_BUILD) list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC}) +endif() if (UNIX) install( diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml new file mode 100644 index 0000000000..7b82b2f705 --- /dev/null +++ b/interface/resources/qml/OverlayWindowTest.qml @@ -0,0 +1,18 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + width: 100 + height: 100 + color: "white" + Rectangle { + width: 10 + height: 10 + color: "red" + } + + Label { + text: OverlayWindowTestString + anchors.centerIn: parent + } +} diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 9a84418b3a..7c1ce704c3 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -22,7 +22,6 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property var source; - property var component; property var dynamicContent; // Keyboard control properties in case needed by QML content. @@ -35,28 +34,9 @@ Windows.Window { dynamicContent.destroy(); dynamicContent = null; } - component = Qt.createComponent(source); - console.log("Created component " + component + " from source " + source); - } - - onComponentChanged: { - console.log("Component changed to " + component) - populate(); - } - - function populate() { - console.log("Populate called: dynamicContent " + dynamicContent + " component " + component); - if (!dynamicContent && component) { - if (component.status == Component.Error) { - console.log("Error loading component:", component.errorString()); - } else if (component.status == Component.Ready) { - console.log("Building dynamic content"); - dynamicContent = component.createObject(contentHolder); - } else { - console.log("Component not yet ready, connecting to status change"); - component.statusChanged.connect(populate); - } - } + QmlSurface.load(source, contentHolder, function(newObject) { + dynamicContent = newObject; + }); } // Handle message traffic from the script that launched us to the loaded QML diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index c068fdcfaf..02c6181952 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -29,12 +29,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index 22b25671c3..e60f646327 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -31,12 +31,12 @@ Original.CheckBox { activeFocusOnPress: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: CheckBoxStyle { diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 32d69cf339..12b8c80003 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -36,12 +36,12 @@ CheckBox { hoverEnabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index bc7bc636fe..9c23171ee1 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -27,12 +27,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index 4ca6cd1b53..b2c720368d 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -41,13 +41,13 @@ Item { onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { mouse.accepted = true; - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); webEntity.synthesizeKeyPress(glyph); webEntity.synthesizeKeyPress(glyph, mirrorText); diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index 65d36d2dcb..a818be072b 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -30,12 +30,12 @@ Original.RadioButton { readonly property int checkRadius: 2 onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: RadioButtonStyle { diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 6ab4fd9758..efc8519c1e 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -49,7 +49,7 @@ Item { } if (WebEngineView.LoadFailedStatus === loadRequest.status) { - console.log(" Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); + console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); } if (WebEngineView.LoadSucceededStatus === loadRequest.status) { diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 3a5c850031..454a9124ae 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -25,13 +25,13 @@ Preference { id: button onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { preference.trigger(); - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } width: 180 anchors.bottom: parent.bottom diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index 8904896ab7..73819839a1 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -41,12 +41,12 @@ Preference { id: checkBox onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } anchors { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index fc7b8c6200..83bf1e2c54 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -246,12 +246,12 @@ Item { anchors.fill: parent; acceptedButtons: Qt.LeftButton; onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction("hifi://" + hifiUrl); } hoverEnabled: true; onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); hoverThunk(); } onExited: unhoverThunk(); @@ -269,7 +269,7 @@ Item { } } function go() { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); } MouseArea { diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 96bb359aea..896b3cf10e 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -45,11 +45,13 @@ OriginalDesktop.Desktop { Toolbar { id: sysToolbar; objectName: "com.highfidelity.interface.toolbar.system"; + property var tablet: Tablet.getTablet("com.highfidelity.interface.tablet.system"); anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. x: sysToolbar.x y: 50 - shown: true + buttonModel: tablet.buttons; + shown: tablet.toolbarMode; } Settings { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index b6699d6ceb..c66904034b 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -61,12 +61,12 @@ Rectangle { scrollGestureEnabled: false; onClicked: { Audio.muted = !Audio.muted; - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } } diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index a058f32994..4de09c1bf3 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -33,6 +33,7 @@ Rectangle { property string itemName; property string itemId; property string itemHref; + property string itemAuthor; property double balanceAfterPurchase; property bool alreadyOwned: false; property int itemPrice: -1; @@ -41,10 +42,11 @@ Rectangle { property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property bool isWearable; + property string referrer; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -59,6 +61,7 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("marketplace checkout"); } } else if (walletStatus === 3) { authSuccessStep(); @@ -71,7 +74,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -79,12 +82,12 @@ Rectangle { if (result.status !== 'success') { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; - UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemPrice, !root.alreadyOwned, result.message); + UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message); } else { root.itemHref = result.data.download_url; root.isWearable = result.data.categories.indexOf("Wearables") > -1; root.activeView = "checkoutSuccess"; - UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemPrice, !root.alreadyOwned); + UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned); } } @@ -114,7 +117,7 @@ Rectangle { } onItemIdChanged: { - commerce.inventory(); + Commerce.inventory(); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -123,14 +126,14 @@ Rectangle { } onItemPriceChanged: { - commerce.balance(); + Commerce.balance(); } Timer { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId, referrer: referrer}); } } @@ -202,7 +205,7 @@ Rectangle { Component.onCompleted: { purchasesReceived = false; balanceReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -223,7 +226,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -408,7 +411,8 @@ Rectangle { Rectangle { id: buyTextContainer; visible: buyText.text !== ""; - anchors.top: parent.top; + anchors.top: cancelPurchaseButton.bottom; + anchors.topMargin: 16; anchors.left: parent.left; anchors.right: parent.right; height: buyText.height + 30; @@ -463,8 +467,8 @@ Rectangle { enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; - anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; - anchors.topMargin: buyTextContainer.visible ? 12 : 16; + anchors.top: checkoutActionButtonsContainer.top; + anchors.topMargin: 16; height: 40; anchors.left: parent.left; anchors.right: parent.right; @@ -473,9 +477,9 @@ Rectangle { if (itemIsJson) { buyButton.enabled = false; if (!root.shouldBuyWithControlledFailure) { - commerce.buy(itemId, itemPrice); + Commerce.buy(itemId, itemPrice); } else { - commerce.buy(itemId, itemPrice, true); + Commerce.buy(itemId, itemPrice, true); } } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -876,6 +880,8 @@ Rectangle { itemName = message.params.itemName; root.itemPrice = message.params.itemPrice; itemHref = message.params.itemHref; + referrer = message.params.referrer; + itemAuthor = message.params.itemAuthor; setBuyText(); break; default: @@ -923,11 +929,11 @@ Rectangle { buyText.text = ""; } } else { - buyText.text = "This free item will not be added to your Purchases. Non-entities can't yet be purchased for HFC."; - buyTextContainer.color = "#FFD6AD"; - buyTextContainer.border.color = "#FAC07D"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 46; + buyText.text = 'This type of item cannot currently be certified, so it will not show up in "My Purchases". You can access it again for free from the Marketplace.'; + buyTextContainer.color = hifi.colors.white; + buyTextContainer.border.color = hifi.colors.white; + buyGlyph.text = ""; + buyGlyph.size = 0; } } @@ -939,8 +945,8 @@ Rectangle { } root.balanceReceived = false; root.purchasesReceived = false; - commerce.inventory(); - commerce.balance(); + Commerce.inventory(); + Commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8b3f017fd2..a4b0f57e8f 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -31,14 +31,14 @@ Item { height: mainContainer.height + additionalDropdownHeight; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 3) { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } else if (walletStatus > 3) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); } @@ -48,7 +48,7 @@ Item { if (!isLoggedIn) { sendToParent({method: "needsLogIn"}); } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -61,13 +61,13 @@ Item { } Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index d98c453a0c..28c32c59de 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -36,8 +36,8 @@ Rectangle { property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onCertificateInfoResult: { if (result.status !== 'success') { @@ -109,7 +109,7 @@ Rectangle { onCertificateIdChanged: { if (certificateId !== "") { - commerce.certificateInfo(certificateId); + Commerce.certificateInfo(certificateId); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index ff9ce16e93..de66be4a88 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -38,8 +38,8 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -54,13 +54,14 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("marketplace purchases"); } } else if (walletStatus === 3) { if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -71,7 +72,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -197,7 +198,7 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; purchasesReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -218,7 +219,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -233,7 +234,7 @@ Rectangle { onSendSignalToParent: { if (msg.method === "authSuccess") { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } @@ -254,7 +255,7 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); break; } } @@ -594,7 +595,7 @@ Rectangle { if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; - commerce.inventory(); + Commerce.inventory(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index ebba2b87c6..409833df98 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -27,8 +27,8 @@ Item { property string keyFilePath; property bool showDebugButtons: true; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -37,7 +37,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } @@ -55,6 +55,37 @@ Item { // Style color: hifi.colors.blueHighlight; } + HifiControlsUit.Button { + id: clearCachedPassphraseButton; + visible: root.showDebugButtons; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.left: helpTitleText.right; + anchors.leftMargin: 20; + height: 40; + width: 150; + text: "DBG: Clear Pass"; + onClicked: { + Commerce.setPassphrase(""); + sendSignalToWallet({method: 'passphraseReset'}); + } + } + HifiControlsUit.Button { + id: resetButton; + visible: root.showDebugButtons; + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + anchors.top: clearCachedPassphraseButton.top; + anchors.left: clearCachedPassphraseButton.right; + height: 40; + width: 150; + text: "DBG: RST Wallet"; + onClicked: { + Commerce.reset(); + sendSignalToWallet({method: 'walletReset'}); + } + } ListModel { id: helpModel; diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index 7ce0cf3853..404d7e84cf 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -30,8 +30,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index a75d511793..91d2ab9f7f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -25,10 +25,6 @@ Item { id: root; - SecurityImageModel { - id: securityImageModel; - } - // Username Text RalewayRegular { id: usernameText; diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 582052c4c3..87430246f3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -36,8 +36,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { titleBarSecurityImage.source = ""; @@ -50,8 +50,12 @@ Item { submitPassphraseInputButton.enabled = true; if (!isAuthenticated) { errorText.text = "Authentication failed - please try again."; + passphraseField.error = true; + UserActivityLogger.commercePassphraseAuthenticationStatus("auth failure"); } else { - sendSignalToParent({method: 'authSuccess'});; + sendSignalToParent({method: 'authSuccess'}); + passphraseField.error = false; + UserActivityLogger.commercePassphraseAuthenticationStatus("auth success"); } } } @@ -70,6 +74,7 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; passphraseField.focus = true; sendSignalToParent({method: 'disableHmdPreview'}); } else { @@ -208,7 +213,7 @@ Item { onAccepted: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } @@ -248,7 +253,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -316,7 +321,7 @@ Item { text: "Submit" onClicked: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } @@ -336,6 +341,7 @@ Item { text: "Cancel" onClicked: { sendSignalToParent({method: 'passphrasePopup_cancelClicked'}); + UserActivityLogger.commercePassphraseAuthenticationStatus("passphrase modal cancelled"); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 9ba066a9fd..50e58f8cc4 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -36,8 +36,8 @@ Item { propagateComposedEvents: false; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { passphrasePageSecurityImage.source = ""; passphrasePageSecurityImage.source = "image://security/securityImage"; @@ -54,6 +54,9 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; + passphraseFieldAgain.error = false; + currentPassphraseField.error = false; if (root.shouldImmediatelyFocus) { focusFirstTextField(); } @@ -160,7 +163,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -283,7 +286,7 @@ Item { passphraseFieldAgain.error = false; currentPassphraseField.error = false; setErrorText(""); - commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); + Commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); return true; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 485b0ec086..d825196655 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -27,8 +27,8 @@ Item { id: root; property string keyFilePath; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -234,7 +234,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 7f767060f6..b261743434 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -26,12 +26,8 @@ Item { id: root; property bool justSubmitted: false; - SecurityImageModel { - id: securityImageModel; - } - - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { securityImageChangePageSecurityImage.source = ""; @@ -213,4 +209,8 @@ Item { securityImageSubmitButton.enabled = true; securityImageSubmitButton.text = "Submit"; } + + function initModel() { + securityImageSelection.initModel(); + } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml index 7b1434aa3c..b8e9db09ab 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml @@ -15,29 +15,28 @@ import QtQuick 2.5 ListModel { id: root; - ListElement{ - sourcePath: "images/01.jpg" - securityImageEnumValue: 1; + + function initModel() { + var array = []; + while (array.length < 6) { + // We currently have 34 security images to choose from + var randomNumber = Math.floor(Math.random() * 34) + 1; + if (array.indexOf(randomNumber) > -1) { + continue; + } + array[array.length] = randomNumber; + } + + var modelElement; + + for (var i = 0; i < 6; i++) { + modelElement = { "sourcePath":"images/" + addLeadingZero(array[i]) + ".jpg", "securityImageEnumValue": (i + 1) } + root.insert(i, modelElement); + } } - ListElement{ - sourcePath: "images/02.jpg" - securityImageEnumValue: 2; - } - ListElement{ - sourcePath: "images/03.jpg" - securityImageEnumValue: 3; - } - ListElement{ - sourcePath: "images/04.jpg" - securityImageEnumValue: 4; - } - ListElement{ - sourcePath: "images/05.jpg" - securityImageEnumValue: 5; - } - ListElement{ - sourcePath: "images/06.jpg" - securityImageEnumValue: 6; + + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; } function getImagePathFromImageID(imageID) { diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index e12332cd0c..1f5e67eaa5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -95,6 +95,10 @@ Item { function getSelectedImageIndex() { return gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue; } + + function initModel() { + gridModel.initModel(); + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml index 75334b1686..11a6c99b0c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml @@ -25,8 +25,8 @@ Item { id: root; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // "Unavailable" diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index ac05bf7c84..ef2b007dc8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -31,13 +31,15 @@ Rectangle { property bool keyboardRaised: false; property bool isPassword: false; + anchors.fill: (typeof parent === undefined) ? undefined : parent; + Image { anchors.fill: parent; source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -47,7 +49,7 @@ Rectangle { } else if (walletStatus === 1) { if (root.activeView !== "walletSetup") { root.activeView = "walletSetup"; - commerce.resetLocalWalletOnly(); + Commerce.resetLocalWalletOnly(); var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); @@ -57,10 +59,13 @@ Rectangle { } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { root.activeView = "passphraseModal"; + UserActivityLogger.commercePassphraseEntry("wallet app"); } } else if (walletStatus === 3) { - root.activeView = "walletHome"; - commerce.getSecurityImage(); + if (root.activeView !== "walletSetup") { + root.activeView = "walletHome"; + Commerce.getSecurityImage(); + } } else { console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus); } @@ -70,7 +75,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else if (isLoggedIn) { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -82,10 +87,6 @@ Rectangle { } } - SecurityImageModel { - id: securityImageModel; - } - HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; visible: false; @@ -180,7 +181,7 @@ Rectangle { if (msg.method === 'walletSetup_finished') { if (msg.referrer === '' || msg.referrer === 'marketplace cta') { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); } else { @@ -209,17 +210,19 @@ Rectangle { Connections { onSendSignalToWallet: { - if (msg.method === 'walletSetup_raiseKeyboard') { - root.keyboardRaised = true; - root.isPassword = msg.isPasswordField; - } else if (msg.method === 'walletSetup_lowerKeyboard') { - root.keyboardRaised = false; - } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { - root.activeView = "security"; - } else { - sendToScript(msg); + if (passphraseChange.visible) { + if (msg.method === 'walletSetup_raiseKeyboard') { + root.keyboardRaised = true; + root.isPassword = msg.isPasswordField; + } else if (msg.method === 'walletSetup_lowerKeyboard') { + root.keyboardRaised = false; + } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { + root.activeView = "security"; + } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { + root.activeView = "security"; + } else { + sendToScript(msg); + } } } } @@ -260,7 +263,7 @@ Rectangle { color: hifi.colors.baseGray; Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -281,7 +284,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -295,7 +298,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } @@ -342,6 +345,7 @@ Rectangle { passphraseChange.clearPassphraseFields(); passphraseChange.resetSubmitButton(); } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageChange.initModel(); root.activeView = "securityImageChange"; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index d23079d3f3..5a5eeeb91f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -28,8 +28,8 @@ Item { property bool historyReceived: false; property int pendingCount: 0; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onBalanceResult : { balanceText.text = result.data.balance; @@ -135,8 +135,8 @@ Item { onVisibleChanged: { if (visible) { historyReceived = false; - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } else { refreshTimer.stop(); } @@ -165,8 +165,8 @@ Item { interval: 4000; onTriggered: { console.log("Refreshing Wallet Home..."); - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index d7859d2800..21da38def0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -41,8 +41,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { if (!exists && root.lastPage === "step_2") { @@ -236,6 +236,7 @@ Item { height: 50; text: "Set Up Wallet"; onClicked: { + securityImageSelection.initModel(); root.activeView = "step_2"; } } @@ -251,7 +252,7 @@ Item { height: 50; text: "Cancel"; onClicked: { - sendSignalToWallet({method: 'walletSetup_cancelClicked'}); + sendSignalToWallet({method: 'walletSetup_cancelClicked', referrer: root.referrer }); } } } @@ -365,7 +366,7 @@ Item { onClicked: { root.lastPage = "step_2"; var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - commerce.chooseSecurityImage(securityImagePath); + Commerce.chooseSecurityImage(securityImagePath); root.activeView = "step_3"; passphraseSelection.clearPassphraseFields(); } @@ -448,7 +449,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getWalletAuthenticatedStatus(); + Commerce.getWalletAuthenticatedStatus(); } } @@ -534,7 +535,7 @@ Item { onClicked: { if (passphraseSelection.validateAndSubmitPassphrase()) { root.lastPage = "step_3"; - commerce.generateKeyPair(); + Commerce.generateKeyPair(); root.activeView = "step_4"; } } @@ -667,7 +668,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01.jpg index 821be6c584..199920a32d 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/01.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg index cfcd3e38bf..821be6c584 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg index 82e3e64be3..1acedf8b10 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg index c6ce0ac0a1..da73727dea 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06.jpg index 1acedf8b10..bbd0399d3f 100644 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg and b/interface/resources/qml/hifi/commerce/wallet/images/06.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/07.jpg b/interface/resources/qml/hifi/commerce/wallet/images/07.jpg new file mode 100644 index 0000000000..e5f86a4a80 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/07.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/08.jpg b/interface/resources/qml/hifi/commerce/wallet/images/08.jpg new file mode 100644 index 0000000000..9d49866c91 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/08.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/09.jpg b/interface/resources/qml/hifi/commerce/wallet/images/09.jpg new file mode 100644 index 0000000000..a1c31bce7d Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/09.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/10.jpg b/interface/resources/qml/hifi/commerce/wallet/images/10.jpg new file mode 100644 index 0000000000..1f6fc1de27 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/10.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/11.jpg b/interface/resources/qml/hifi/commerce/wallet/images/11.jpg new file mode 100644 index 0000000000..37c3b937a5 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/11.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/12.jpg b/interface/resources/qml/hifi/commerce/wallet/images/12.jpg new file mode 100644 index 0000000000..39f653630c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/12.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/13.jpg b/interface/resources/qml/hifi/commerce/wallet/images/13.jpg new file mode 100644 index 0000000000..3ae86be465 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/13.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/14.jpg b/interface/resources/qml/hifi/commerce/wallet/images/14.jpg new file mode 100644 index 0000000000..8de661e58e Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/14.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/15.jpg b/interface/resources/qml/hifi/commerce/wallet/images/15.jpg new file mode 100644 index 0000000000..f0ac801b89 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/15.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/16.jpg b/interface/resources/qml/hifi/commerce/wallet/images/16.jpg new file mode 100644 index 0000000000..f7c32a696b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/16.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/17.jpg b/interface/resources/qml/hifi/commerce/wallet/images/17.jpg new file mode 100644 index 0000000000..75e811ee36 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/17.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/18.jpg b/interface/resources/qml/hifi/commerce/wallet/images/18.jpg new file mode 100644 index 0000000000..e4ca0b6f28 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/18.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/19.jpg b/interface/resources/qml/hifi/commerce/wallet/images/19.jpg new file mode 100644 index 0000000000..4c25f3dda2 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/19.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/20.jpg b/interface/resources/qml/hifi/commerce/wallet/images/20.jpg new file mode 100644 index 0000000000..a5a4565a87 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/20.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/21.jpg b/interface/resources/qml/hifi/commerce/wallet/images/21.jpg new file mode 100644 index 0000000000..3747c9558e Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/21.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/22.jpg b/interface/resources/qml/hifi/commerce/wallet/images/22.jpg new file mode 100644 index 0000000000..66003db9c7 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/22.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/23.jpg b/interface/resources/qml/hifi/commerce/wallet/images/23.jpg new file mode 100644 index 0000000000..72e02c6db0 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/23.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/24.jpg b/interface/resources/qml/hifi/commerce/wallet/images/24.jpg new file mode 100644 index 0000000000..e481a587a5 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/24.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/25.jpg b/interface/resources/qml/hifi/commerce/wallet/images/25.jpg new file mode 100644 index 0000000000..1300edf984 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/25.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/26.jpg b/interface/resources/qml/hifi/commerce/wallet/images/26.jpg new file mode 100644 index 0000000000..9e3aaf9383 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/26.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/27.jpg b/interface/resources/qml/hifi/commerce/wallet/images/27.jpg new file mode 100644 index 0000000000..f64c0c6a3d Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/27.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/28.jpg b/interface/resources/qml/hifi/commerce/wallet/images/28.jpg new file mode 100644 index 0000000000..8f1580ab21 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/28.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/29.jpg b/interface/resources/qml/hifi/commerce/wallet/images/29.jpg new file mode 100644 index 0000000000..fdfb4438ec Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/29.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/30.jpg b/interface/resources/qml/hifi/commerce/wallet/images/30.jpg new file mode 100644 index 0000000000..785ea5a999 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/30.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/31.jpg b/interface/resources/qml/hifi/commerce/wallet/images/31.jpg new file mode 100644 index 0000000000..06dc95afab Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/31.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/32.jpg b/interface/resources/qml/hifi/commerce/wallet/images/32.jpg new file mode 100644 index 0000000000..b987c44e13 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/32.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/33.jpg b/interface/resources/qml/hifi/commerce/wallet/images/33.jpg new file mode 100644 index 0000000000..19b05a5f73 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/33.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/34.jpg b/interface/resources/qml/hifi/commerce/wallet/images/34.jpg new file mode 100644 index 0000000000..39f2a5f4ce Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/34.jpg differ diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/interface/resources/qml/hifi/tablet/NewEntityButton.qml index 7f838717df..1952ef7ee8 100644 --- a/interface/resources/qml/hifi/tablet/NewEntityButton.qml +++ b/interface/resources/qml/hifi/tablet/NewEntityButton.qml @@ -123,11 +123,11 @@ Item { hoverEnabled: true enabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); newEntityButton.clicked(); } onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); newEntityButton.state = "hover state"; } onExited: { diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml deleted file mode 100644 index 2a086bae94..0000000000 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ /dev/null @@ -1,261 +0,0 @@ -import QtQuick 2.5 -import QtGraphicalEffects 1.0 -import QtQuick.Layouts 1.3 - -import "../../styles-uit" -import "../audio" as HifiAudio - -Item { - id: tablet - objectName: "tablet" - property int rowIndex: 6 // by default - property int columnIndex: 1 // point to 'go to location' - property int count: (flowMain.children.length - 1) - - // used to look up a button by its uuid - function findButtonIndex(uuid) { - if (!uuid) { - return -1; - } - - for (var i in flowMain.children) { - var child = flowMain.children[i]; - if (child.uuid === uuid) { - return i; - } - } - return -1; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < flowMain.children.length; i++) { - children[i] = flowMain.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - flowMain.children = children; - } - - // called by C++ code when a button should be added to the tablet - function addButtonProxy(properties) { - var component = Qt.createComponent("TabletButton.qml"); - var button = component.createObject(flowMain); - - // copy all properites to button - var keys = Object.keys(properties).forEach(function (key) { - button[key] = properties[key]; - }); - - // pass a reference to the tabletRoot object to the button. - if (tabletRoot) { - button.tabletRoot = tabletRoot; - } else { - button.tabletRoot = parent.parent; - } - - sortButtons(); - - return button; - } - - // called by C++ code when a button should be removed from the tablet - function removeButtonProxy(properties) { - var index = findButtonIndex(properties.uuid); - if (index < 0) { - console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid); - } else { - flowMain.children[index].destroy(); - } - } - - Rectangle { - id: bgTopBar - height: 90 - - anchors { - top: parent.top - topMargin: 0 - left: parent.left - leftMargin: 0 - right: parent.right - rightMargin: 0 - } - - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#1e1e1e" - } - } - - HifiAudio.MicBar { - anchors { - left: parent.left - leftMargin: 30 - verticalCenter: parent.verticalCenter - } - } - - Item { - width: 150 - height: 50 - anchors.right: parent.right - anchors.rightMargin: 30 - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - anchors.fill: parent - - RalewaySemiBold { - text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - - RalewaySemiBold { - visible: Account.loggedIn - height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 - text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (!Account.loggedIn) { - DialogsManager.showLoginDialog() - } else { - Account.logOut() - } - } - } - } - } - - Rectangle { - id: bgMain - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#0f212e" - } - } - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: bgTopBar.bottom - anchors.topMargin: 0 - - Flickable { - id: flickable - width: parent.width - height: parent.height - contentWidth: parent.width - contentHeight: flowMain.childrenRect.height + flowMain.anchors.topMargin + flowMain.anchors.bottomMargin + flowMain.spacing - clip: true - Flow { - id: flowMain - spacing: 16 - anchors.right: parent.right - anchors.rightMargin: 30 - anchors.left: parent.left - anchors.leftMargin: 30 - anchors.bottom: parent.bottom - anchors.bottomMargin: 30 - anchors.top: parent.top - anchors.topMargin: 30 - } - } - } - - function setCurrentItemState(state) { - var index = rowIndex + columnIndex; - - if (index >= 0 && index <= count ) { - flowMain.children[index].state = state; - } - } - - function nextItem() { - setCurrentItemState("base state"); - var nextColumnIndex = (columnIndex + 3 + 1) % 3; - var nextIndex = rowIndex + nextColumnIndex; - if(nextIndex <= count) { - columnIndex = nextColumnIndex; - }; - setCurrentItemState("hover state"); - } - - function previousItem() { - setCurrentItemState("base state"); - var prevIndex = (columnIndex + 3 - 1) % 3; - if((rowIndex + prevIndex) <= count){ - columnIndex = prevIndex; - } - setCurrentItemState("hover state"); - } - - function upItem() { - setCurrentItemState("base state"); - rowIndex = rowIndex - 3; - if (rowIndex < 0 ) { - rowIndex = (count - (count % 3)); - var index = rowIndex + columnIndex; - if(index > count) { - rowIndex = rowIndex - 3; - } - } - setCurrentItemState("hover state"); - } - - function downItem() { - setCurrentItemState("base state"); - rowIndex = rowIndex + 3; - var index = rowIndex + columnIndex; - if (index > count ) { - rowIndex = 0; - } - setCurrentItemState("hover state"); - } - - function selectItem() { - flowMain.children[rowIndex + columnIndex].clicked(); - if (tabletRoot) { - tabletRoot.playButtonClickSound(); - } - } - - Keys.onRightPressed: nextItem(); - Keys.onLeftPressed: previousItem(); - Keys.onDownPressed: downItem(); - Keys.onUpPressed: upItem(); - Keys.onReturnPressed: selectItem(); -} diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 8fc31d1cd6..1508367318 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -123,7 +123,6 @@ Item { enabled: true preventStealing: true onClicked: { - console.log("Tablet Button Clicked!"); if (tabletButton.inDebugMode) { if (tabletButton.isActive) { tabletButton.isActive = false; @@ -133,12 +132,12 @@ Item { } tabletButton.clicked(); if (tabletRoot) { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } } onEntered: { tabletButton.isEntered = true; - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); if (tabletButton.isActive) { tabletButton.state = "hover active state"; diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml new file mode 100644 index 0000000000..3f9451436c --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -0,0 +1,159 @@ +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import QtQuick.Layouts 1.3 + +import "." +import "../../styles-uit" +import "../audio" as HifiAudio + +Item { + id: tablet + objectName: "tablet" + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + Rectangle { + id: bgTopBar + height: 90 + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#1e1e1e" + } + } + + HifiAudio.MicBar { + anchors { + left: parent.left + leftMargin: 30 + verticalCenter: parent.verticalCenter + } + } + + Item { + width: 150 + height: 50 + anchors.right: parent.right + anchors.rightMargin: 30 + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + anchors.fill: parent + + RalewaySemiBold { + text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + + RalewaySemiBold { + visible: Account.loggedIn + height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 + text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (!Account.loggedIn) { + DialogsManager.showLoginDialog() + } else { + Account.logOut() + } + } + } + } + } + + Rectangle { + id: bgMain + clip: true + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#0f212e" + } + } + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.top: bgTopBar.bottom + + GridView { + id: flickable + anchors.top: parent.top + anchors.topMargin: 15 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: cellWidth * 3 + cellHeight: 145 + cellWidth: 145 + model: tabletProxy.buttons + delegate: Item { + width: flickable.cellWidth + height: flickable.cellHeight + property var proxy: modelData + + TabletButton { + id: tabletButton + anchors.centerIn: parent + onClicked: modelData.clicked() + state: wrapper.GridView.isCurrentItem ? "hover state" : "base state" + } + + Connections { + target: modelData; + onPropertiesChanged: { + updateProperties(); + } + } + + Component.onCompleted: updateProperties() + + function updateProperties() { + var keys = Object.keys(modelData.properties).forEach(function (key) { + if (tabletButton[key] !== modelData.properties[key]) { + tabletButton[key] = modelData.properties[key]; + } + }); + } + } + } + } + + Keys.onRightPressed: flickable.moveCurrentIndexRight(); + Keys.onLeftPressed: flickable.moveCurrentIndexLeft(); + Keys.onDownPressed: flickable.moveCurrentIndexDown(); + Keys.onUpPressed: flickable.moveCurrentIndexUp(); + Keys.onReturnPressed: { + if (flickable.currentItem) { + flickable.currentItem.proxy.clicked(); + if (tabletRoot) { + tabletRoot.playButtonClickSound(); + } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 4a4a6b7f87..636ebfe71f 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -77,12 +77,12 @@ FocusScope { anchors.fill: parent hoverEnabled: true onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); listView.currentIndex = index } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); root.selected(item); } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index a161741049..da544c2114 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -68,37 +68,36 @@ Item { function loadSource(url) { tabletApps.clear(); - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); + loader.load(url) } function loadQMLOnTop(url) { tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); - loader.source = ""; - loader.source = tabletApps.get(currentApp).appUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + loader.load(tabletApps.get(currentApp).appUrl, function(){ + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }) } - function loadWebOnTop(url, injectJavaScriptUrl) { - tabletApps.append({"appUrl": loader.source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); - loader.item.url = tabletApps.get(currentApp).appWebUrl; - loader.item.scriptUrl = tabletApps.get(currentApp).scriptUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + function loadWebContent(source, url, injectJavaScriptUrl) { + tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }); } - function loadWebBase() { - loader.source = ""; - loader.source = "TabletWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } function returnToPreviousApp() { @@ -110,7 +109,7 @@ Item { loadSource("TabletWebView.qml"); loadWebUrl(webUrl, scriptUrl); } else { - loader.source = tabletApps.get(currentApp).appUrl; + loader.load(tabletApps.get(currentApp).appUrl); } } @@ -173,47 +172,79 @@ Item { } } - Loader { - id: loader - objectName: "loader" - asynchronous: false - - width: parent.width - height: parent.height - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } - } - } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); - } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); - } - loader.item.forceActiveFocus(); - - if (openModal) { - openModal.canceled(); - openModal.destroy(); - openModal = null; - } - - if (openBrowser) { - openBrowser.destroy(); - openBrowser = null; + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); } } } + Item { + id: loader + objectName: "loader"; + anchors.fill: parent; + property string source: ""; + property var item: null; + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; + } + } + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; + } + } + + function load(newSource, callback) { + if (loader.source == newSource) { + loader.loaded(); + return; + } + + if (loader.item) { + loader.item.destroy(); + loader.item = null; + } + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (openModal) { + openModal.canceled(); + openModal.destroy(); + openModal = null; + } + + if (openBrowser) { + openBrowser.destroy(); + openBrowser = null; + } + + if (callback) { + callback(); + } + }); + } + } + width: 480 height: 706 diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index e3170f85ef..9c027308b8 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -59,26 +59,25 @@ Windows.ScrollingWindow { } function loadSource(url) { - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; + loader.load(url) } - function loadWebBase() { - loader.source = ""; - loader.source = "WindowWebView.qml"; + function loadWebContent(source, url, injectJavaScriptUrl) { + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("closeButtonVisible")) { + loader.item.closeButtonVisible = false; + } + }); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadWebUrl(url, injectedJavaScriptUrl) { - loader.item.url = url; - loader.item.scriptURL = injectedJavaScriptUrl; - if (loader.item.hasOwnProperty("closeButtonVisible")) { - loader.item.closeButtonVisible = false; - } + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } // used to send a message from qml to interface script. @@ -111,38 +110,68 @@ Windows.ScrollingWindow { username = newUsername; } - Loader { + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); + } + } + } + + Item { id: loader - objectName: "loader" - asynchronous: false + objectName: "loader"; + property string source: ""; + property var item: null; height: pane.scrollHeight width: pane.contentWidth anchors.left: parent.left anchors.top: parent.top - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; } } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + + function load(newSource, callback) { + if (loader.item) { + loader.item.destroy(); + loader.item = null; } - loader.item.forceActiveFocus(); + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (callback) { + callback(); + } + }); } } + implicitWidth: 480 implicitHeight: 706 } diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 01aa29f665..ff7e835690 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -11,6 +11,8 @@ Window { horizontalSpacers: horizontal verticalSpacers: !horizontal } + property var tabletProxy; + property var buttonModel: ListModel {} hideBackground: true resizable: false destroyOnCloseButton: false @@ -23,24 +25,32 @@ Window { activator: Item {} property bool horizontal: true property real buttonSize: 50; - property var buttons: [] - property var container: horizontal ? row : column Settings { category: "toolbar/" + window.objectName property alias x: window.x property alias y: window.y } + + Component { + id: buttonComponent + ToolbarButton { + id: toolbarButton + property var proxy: modelData; + onClicked: proxy.clicked() + Component.onCompleted: updateProperties() - onHorizontalChanged: { - var newParent = horizontal ? row : column; - for (var i in buttons) { - var child = buttons[i]; - child.parent = newParent; - if (horizontal) { - child.y = 0 - } else { - child.x = 0 + Connections { + target: proxy; + onPropertiesChanged: updateProperties(); + } + + function updateProperties() { + Object.keys(proxy.properties).forEach(function (key) { + if (toolbarButton[key] !== proxy.properties[key]) { + toolbarButton[key] = proxy.properties[key]; + } + }); } } } @@ -52,97 +62,22 @@ Window { Row { id: row + visible: window.horizontal spacing: 6 + Repeater { + model: buttonModel + delegate: buttonComponent + } } - + Column { id: column + visible: !window.horizontal spacing: 6 - } - - Component { id: toolbarButtonBuilder; ToolbarButton { } } - } - - - function findButtonIndex(name) { - if (!name) { - return -1; - } - - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === name) { - return i; + Repeater { + model: buttonModel + delegate: buttonComponent } } - return -1; - } - - function findButton(name) { - var index = findButtonIndex(name); - if (index < 0) { - return; - } - return buttons[index]; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < container.children.length; i++) { - children[i] = container.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - container.children = children; - } - - function addButton(properties) { - properties = properties || {} - - // If a name is specified, then check if there's an existing button with that name - // and return it if so. This will allow multiple clients to listen to a single button, - // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - var result = findButton(properties.objectName); - if (result) { - for (var property in properties) { - result[property] = properties[property]; - } - return result; - } - properties.toolbar = this; - properties.opacity = 0; - result = toolbarButtonBuilder.createObject(container, properties); - buttons.push(result); - - result.opacity = 1; - - sortButtons(); - - fadeIn(null); - - return result; - } - - function removeButton(name) { - var index = findButtonIndex(name); - if (index < -1) { - console.warn("Tried to remove non-existent button " + name); - return; - } - - buttons[index].destroy(); - buttons.splice(index, 1); - - if (buttons.length === 0) { - fadeOut(null); - } } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index f27ba6bb24..7781d1140b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -27,7 +27,8 @@ StateImage { property string activeHoverIcon: button.activeIcon property int sortOrder: 100 - property int stableSortOrder: 0 + property int stableOrder: 0 + property var uuid; signal clicked() diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc new file mode 100644 index 0000000000..ab20e996b9 Binary files /dev/null and b/interface/resources/qml/js/Utils.jsc differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e4ab16908..f17e06cb35 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1496,6 +1496,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); + connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [=](const EntityItemID& entityItemID) { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + if (myAvatar) { + myAvatar->clearAvatarEntity(entityItemID); + } + }); + // Keyboard focus handling for Web overlays. auto overlays = &(qApp->getOverlays()); connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) { @@ -2240,27 +2248,65 @@ extern void setupPreferences(); void Application::initializeUi() { // Make sure all QML surfaces share the main thread GL context OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext()); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" }, + [](QQmlContext* context) { + qDebug() << "Whitelist OverlayWindow worked"; + context->setContextProperty("OverlayWindowTestString", "TestWorked"); + }); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" }, + [](QQmlContext* context) { + qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked"; + }); + AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); - QmlCommerce::registerType(); + QmlContextCallback callback = [](QQmlContext* context) { + context->setContextProperty("Commerce", new QmlCommerce()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/checkout/Checkout.qml" }, + QUrl{ "hifi/commerce/common/CommerceLightbox.qml" }, + QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" }, + QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" }, + QUrl{ "hifi/commerce/common/SortableListModel.qml" }, + QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" }, + QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Help.qml" }, + QUrl{ "hifi/commerce/wallet/NeedsLogIn.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" }, + QUrl{ "hifi/commerce/wallet/Security.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" }, + QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" }, + QUrl{ "hifi/commerce/wallet/SendMoney.qml" }, + QUrl{ "hifi/commerce/wallet/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, + }, callback); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); + { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->getTablet(SYSTEM_TABLET); + } auto offscreenUi = DependencyManager::get(); offscreenUi->create(); auto surfaceContext = offscreenUi->getSurfaceContext(); offscreenUi->setProxyWindow(_window->windowHandle()); - offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use - offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml")); + offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves or // do better detection in the offscreen UI of what has focus @@ -2328,9 +2374,6 @@ void Application::initializeUi() { surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); - surfaceContext->setContextProperty("Tablet", DependencyManager::get().data()); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("FaceTracker", DependencyManager::get().data()); @@ -3222,8 +3265,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } } - - void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); @@ -4282,9 +4323,11 @@ void Application::updateLOD(float deltaTime) const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!isThrottleRendering()) { - float batchTime = (float)_gpuContext->getFrameTimerBatchAverage(); + float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); - DependencyManager::get()->autoAdjustLOD(batchTime, engineRunTime, deltaTime); + float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime)); + DependencyManager::get()->autoAdjustLOD(maxRenderTime, deltaTime); } else { DependencyManager::get()->resetLODAdjust(); } @@ -4848,8 +4891,7 @@ void Application::update(float deltaTime) { if (_physicsEnabled) { { PROFILE_RANGE(simulation_physics, "PreStep"); - - PerformanceTimer perfTimer("updateStates)"); + PerformanceTimer perfTimer("preStep)"); static VectorOfMotionStates motionStates; _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); @@ -4882,22 +4924,22 @@ void Application::update(float deltaTime) { } { PROFILE_RANGE(simulation_physics, "Step"); - PerformanceTimer perfTimer("stepSimulation"); + PerformanceTimer perfTimer("step"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { PROFILE_RANGE(simulation_physics, "PostStep"); - PerformanceTimer perfTimer("harvestChanges"); + PerformanceTimer perfTimer("postStep"); if (_physicsEngine->hasOutgoingChanges()) { // grab the collision events BEFORE handleOutgoingChanges() because at this point // we have a better idea of which objects we own or should own. auto& collisionEvents = _physicsEngine->getCollisionEvents(); getEntities()->getTree()->withWriteLock([&] { - PROFILE_RANGE(simulation_physics, "Harvest"); - PerformanceTimer perfTimer("handleOutgoingChanges"); + PROFILE_RANGE(simulation_physics, "HandleChanges"); + PerformanceTimer perfTimer("handleChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); _entitySimulation->handleChangedMotionStates(outgoingChanges); @@ -4908,17 +4950,15 @@ void Application::update(float deltaTime) { }); if (!_aboutToQuit) { - // handleCollisionEvents() AFTER handleOutgoinChanges() + // handleCollisionEvents() AFTER handleOutgoingChanges() { PROFILE_RANGE(simulation_physics, "CollisionEvents"); - PerformanceTimer perfTimer("entities"); avatarManager->handleCollisionEvents(collisionEvents); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _entitySimulation->handleCollisionEvents(collisionEvents); } - PROFILE_RANGE(simulation_physics, "UpdateEntities"); // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). getEntities()->update(true); // update the models... @@ -4929,7 +4969,8 @@ void Application::update(float deltaTime) { myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } - if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && + if (PerformanceTimer::isActive() && + Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) { _physicsEngine->harvestPerformanceStats(); } @@ -5832,9 +5873,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Tablet", DependencyManager::get().data()); + // FIXME remove these deprecated names for the tablet scripting interface + scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); auto toolbarScriptingInterface = DependencyManager::get().data(); DependencyManager::get().data()->setToolbarScriptingInterface(toolbarScriptingInterface); @@ -7241,13 +7282,17 @@ void Application::updateDisplayMode() { } auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); // Make the switch atomic from the perspective of other threads { std::unique_lock lock(_displayPluginLock); - // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. - bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); - offscreenUi->getDesktop()->setProperty("repositionLocked", true); + bool wasRepositionLocked = false; + if (desktop) { + // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. + wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); + offscreenUi->getDesktop()->setProperty("repositionLocked", true); + } if (_displayPlugin) { disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent); @@ -7293,7 +7338,6 @@ void Application::updateDisplayMode() { getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); _displayPlugin = newDisplayPlugin; connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection); - auto desktop = offscreenUi->getDesktop(); if (desktop) { desktop->setProperty("repositionLocked", wasRepositionLocked); } @@ -7506,4 +7550,9 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; } + +void Application::saveNextPhysicsStats(QString filename) { + _physicsEngine->saveNextPhysicsStats(filename); +} + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index 9542c5ccb6..ee16740f20 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -280,6 +280,7 @@ public: void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + void saveNextPhysicsStats(QString filename); signals: void svoImportRequested(const QString& url); @@ -432,6 +433,7 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + private: static void initDisplay(); void init(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 01ccbd0d9a..9e6fabd439 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,6 +19,7 @@ #include "LODManager.h" + Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); @@ -39,156 +40,95 @@ float LODManager::getLODIncreaseFPS() { return getDesktopLODIncreaseFPS(); } -void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec) { +// We use a "time-weighted running average" of the renderTime and compare it against min/max thresholds +// to determine if we should adjust the level of detail (LOD). +// +// A time-weighted running average has a timescale which determines how fast the average tracks the measured +// value in real-time. Given a step-function in the mesured value, and assuming measurements happen +// faster than the runningAverage is computed, the error between the value and its runningAverage will be +// reduced by 1/e every timescale of real-time that passes. +const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec +// +// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its +// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle +// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few +// multiples of the running average timescale: +const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec - // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't - // really want to count them in our average, so we will ignore the real frame rates and stuff - // our moving average with simulated good data - const int IGNORE_THESE_SAMPLES = 100; - if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) { - _lastStable = _lastUpShift = _lastDownShift = usecTimestampNow(); - } +const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; +const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; +void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { // compute time-weighted running average renderTime - const float OVERLAY_AND_SWAP_TIME_BUDGET = 2.0f; // msec - float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET; - float maxTime = glm::max(renderTime, engineRunTime); - const float BLEND_TIMESCALE = 0.3f; // sec - const float MIN_DELTA_TIME = 0.001f; - const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME); - float blend = BLEND_TIMESCALE / safeDeltaTime; - if (blend > 1.0f) { - blend = 1.0f; + // Note: we MUST clamp the blend to 1.0 for stability + float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * renderTime; // msec + if (!_automaticLODAdjust) { + // early exit + return; } - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxTime; // msec - // translate into fps for legacy implementation + float oldOctreeSizeScale = _octreeSizeScale; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; - - _fpsAverageStartWindow.updateAverage(currentFPS); - _fpsAverageDownWindow.updateAverage(currentFPS); - _fpsAverageUpWindow.updateAverage(currentFPS); - - quint64 now = usecTimestampNow(); - - quint64 elapsedSinceDownShift = now - _lastDownShift; - quint64 elapsedSinceUpShift = now - _lastUpShift; - - quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable); - quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift; - - if (_automaticLODAdjust) { - bool changed = false; - - // LOD Downward adjustment - // If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our - // target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift, - // or because we've just started out) then we look at a much longer window to consider whether or not to start - // downshifting. - bool doDownShift = false; - - if (_isDownshifting) { - // only consider things if our DOWN_SHIFT time has elapsed... - if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) { - doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS(); - - if (!doDownShift) { - qCDebug(interfaceapp) << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----"; - _isDownshifting = false; - _lastStable = now; - } - } - } else { - doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED - && _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS()); - } - - if (doDownShift) { - - // Octree items... stepwise adjustment + uint64_t now = usecTimestampNow(); + if (currentFPS < getLODDecreaseFPS()) { + if (now > _decreaseFPSExpiry) { + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale *= ADJUST_LOD_DOWN_BY; + _octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR; if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } - changed = true; - } - - if (changed) { - if (_isDownshifting) { - // subsequent downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN..." - << "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageDownWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceDownShift:" << elapsedSinceDownShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } else { - // first downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN after initial delay..." - << "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageStartWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } - - _lastDownShift = now; - _isDownshifting = true; - + qCDebug(interfaceapp) << "adjusting LOD DOWN" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; emit LODDecreased(); } - } else { - - // LOD Upward adjustment - if (elapsedSinceUpShift > UP_SHIFT_ELPASED) { - - if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) { - - // Octee items... stepwise adjustment - if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } else { - _octreeSizeScale *= ADJUST_LOD_UP_BY; - } - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - changed = true; - } - } - - if (changed) { - qCDebug(interfaceapp) << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageUpWindow.getAverage() - << "upshift point is:" << getLODIncreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - - _lastUpShift = now; - _isDownshifting = false; - - emit LODIncreased(); - } - } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } - - if (changed) { - auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); - if (lodToolsDialog) { - lodToolsDialog->reloadSliders(); + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else if (currentFPS > getLODIncreaseFPS()) { + if (now > _increaseFPSExpiry) { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { + if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; + } else { + _octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR; + } + if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; + } + qCDebug(interfaceapp) << "adjusting LOD UP" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; + emit LODIncreased(); } + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + _decreaseFPSExpiry = _increaseFPSExpiry; + } + + if (oldOctreeSizeScale != _octreeSizeScale) { + auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); + if (lodToolsDialog) { + lodToolsDialog->reloadSliders(); + } + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime + // to be at middle of target zone. It will drift close to its true value within + // about three few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. + float expectedFPS = 0.5f * (getLODIncreaseFPS() + getLODDecreaseFPS()); + _avgRenderTime = MSECS_PER_SECOND / expectedFPS; } } void LODManager::resetLODAdjust() { - _fpsAverageStartWindow.reset(); - _fpsAverageDownWindow.reset(); - _fpsAverageUpWindow.reset(); - _lastUpShift = _lastDownShift = usecTimestampNow(); - _isDownshifting = false; + _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } const float MIN_DECREASE_FPS = 0.5f; @@ -206,7 +146,7 @@ float LODManager::getDesktopLODDecreaseFPS() const { } float LODManager::getDesktopLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_DESKTOP_FPS); + return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS); } void LODManager::setHMDLODDecreaseFPS(float fps) { @@ -222,7 +162,7 @@ float LODManager::getHMDLODDecreaseFPS() const { } float LODManager::getHMDLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_HMD_FPS); + return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS); } QString LODManager::getLODFeedbackText() { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 1b3797a0ca..cf38342db0 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,29 +19,13 @@ #include #include -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 20.0; -const float DEFAULT_HMD_LOD_DOWN_FPS = 20.0; +const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; +const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 59.0; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 74.0; // this is essentially, V-synch - 1 fps -const float INCREASE_LOD_GAP = 15.0f; - -const float START_DELAY_WINDOW_IN_SECS = 3.0f; // wait at least this long after steady state/last upshift to consider downshifts -const float DOWN_SHIFT_WINDOW_IN_SECS = 0.5f; -const float UP_SHIFT_WINDOW_IN_SECS = 2.5f; - -const int ASSUMED_FPS = 60; -const quint64 START_SHIFT_ELPASED = USECS_PER_SECOND * START_DELAY_WINDOW_IN_SECS; -const quint64 DOWN_SHIFT_ELPASED = USECS_PER_SECOND * DOWN_SHIFT_WINDOW_IN_SECS; // Consider adjusting LOD down after half a second -const quint64 UP_SHIFT_ELPASED = USECS_PER_SECOND * UP_SHIFT_WINDOW_IN_SECS; - -const int START_DELAY_SAMPLES_OF_FRAMES = ASSUMED_FPS * START_DELAY_WINDOW_IN_SECS; -const int DOWN_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * DOWN_SHIFT_WINDOW_IN_SECS; -const int UP_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * UP_SHIFT_WINDOW_IN_SECS; - -const float ADJUST_LOD_DOWN_BY = 0.9f; -const float ADJUST_LOD_UP_BY = 1.1f; +const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps +const float INCREASE_LOD_GAP_FPS = 15.0f; // fps // The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; @@ -78,7 +62,7 @@ public: Q_INVOKABLE float getLODIncreaseFPS(); static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec); + void autoAdjustLOD(float renderTime, float realTimeDelta); void loadSettings(); void saveSettings(); @@ -92,21 +76,15 @@ private: LODManager(); bool _automaticLODAdjust = true; - float _avgRenderTime { 0.0 }; + float _avgRenderTime { 0.0f }; float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - - quint64 _lastDownShift = 0; - quint64 _lastUpShift = 0; - quint64 _lastStable = 0; - bool _isDownshifting = false; // start out as if we're not downshifting - - SimpleMovingAverage _fpsAverageStartWindow = START_DELAY_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageDownWindow = DOWN_SHIFT_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageUpWindow = UP_SHIFT_SAMPLES_OF_FRAMES; + + uint64_t _decreaseFPSExpiry { 0 }; + uint64_t _increaseFPSExpiry { 0 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9bbb72357b..eeda9e9036 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -101,7 +101,7 @@ Menu::Menu() { auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J); connect(action, &QAction::triggered, [] { static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml"); - static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml"); + static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml"); static const QString name("RunningScripts"); qApp->showDialog(widgetUrl, tabletUrl, name); }); @@ -338,7 +338,7 @@ Menu::Menu() { connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("ControllerSettings.qml"); + tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); @@ -645,7 +645,8 @@ Menu::Menu() { // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); - addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false, + qApp, SLOT(enablePerfStats(bool))); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 8a294182bd..93caef555f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -150,7 +150,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - const AvatarSharedPointer& getAvatar() const { return _avatar; } + AvatarSharedPointer getAvatar() const { return _avatar; } private: AvatarSharedPointer _avatar; }; @@ -185,7 +185,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { render::Transaction transaction; while (!sortedAvatars.empty()) { const SortableAvatar& sortData = sortedAvatars.top(); - const auto& avatar = std::static_pointer_cast(sortData.getAvatar()); + const auto avatar = std::static_pointer_cast(sortData.getAvatar()); bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -239,7 +239,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { sortedAvatars.pop(); while (inView && !sortedAvatars.empty()) { const SortableAvatar& newSortData = sortedAvatars.top(); - const auto& newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && newAvatar->hasNewJointData()) { numAVatarsNotUpdated++; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 3257a634c7..d7d36dabf6 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -61,7 +61,7 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString void Ledger::signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure) { auto wallet = DependencyManager::get(); - QString signature = key.isEmpty() ? "" : wallet->signWithKey(text, key); + QString signature = wallet->signWithKey(text, key); QJsonObject request; request[propertyName] = QString(text); if (!controlled_failure) { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 69089df9c2..62e87f9c66 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -16,9 +16,7 @@ #include "Wallet.h" #include -HIFI_QML_DEF(QmlCommerce) - -QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { +QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index fd8dd0d395..c53e73d565 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -16,16 +16,14 @@ #define hifi_QmlCommerce_h #include -#include #include -class QmlCommerce : public OffscreenQmlDialog { +class QmlCommerce : public QObject { Q_OBJECT - HIFI_QML_DECL public: - QmlCommerce(QQuickItem* parent = nullptr); + QmlCommerce(); signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 00941d6c50..c3c91e82a8 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -548,13 +548,16 @@ QStringList Wallet::listPublicKeys() { // the horror of code pages and so on (changing the bytes) by just returning a base64 // encoded string representing the signature (suitable for http, etc...) QString Wallet::signWithKey(const QByteArray& text, const QString& key) { - qCInfo(commerce) << "Signing text" << text << "with key" << key; EC_KEY* ecPrivateKey = NULL; + + auto keyFilePathString = keyFilePath().toStdString(); if ((ecPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) { unsigned char* sig = new unsigned char[ECDSA_size(ecPrivateKey)]; unsigned int signatureBytes = 0; + qCInfo(commerce) << "Hashing and signing plaintext" << text << "with key at address" << ecPrivateKey; + QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256); @@ -747,12 +750,10 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack } EC_KEY_free(ec); - QByteArray ba = sig.toLocal8Bit(); - const char *sigChar = ba.data(); QByteArray textByteArray; if (status > -1) { - textByteArray = QByteArray(sigChar, (int) strlen(sigChar)); + textByteArray = sig.toUtf8(); } textByteArraySize = textByteArray.size(); int certIDSize = certID.size(); diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index d4b4ba1480..74a15db0ce 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -11,11 +11,12 @@ #include #include +#include #include #include -#include -#include #include +#include +#include #include "Application.h" @@ -141,6 +142,15 @@ void TestScriptingInterface::endTraceEvent(QString name) { tracing::traceEvent(trace_test(), name, tracing::DurationEnd); } +void TestScriptingInterface::savePhysicsSimulationStats(QString originalPath) { + QString path = FileUtils::replaceDateTimeTokens(originalPath); + path = FileUtils::computeDocumentPath(path); + if (!FileUtils::canCreateFile(path)) { + return; + } + qApp->saveNextPhysicsStats(path); +} + void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) { PROFILE_RANGE(script, name); fn.call(); diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 73b8f0ac93..aca07d110b 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -71,6 +71,11 @@ public slots: void endTraceEvent(QString name); + /**jsdoc + * Write detailed timing stats of next physics stepSimulation() to filename + */ + void savePhysicsSimulationStats(QString filename); + Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); private: diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index f216bb4edc..ff2d4868df 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -31,7 +31,7 @@ #include "scripting/HMDScriptingInterface.h" -static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml"; +static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml"; template void DialogsManager::maybeCreateDialog(QPointer& member) { if (!member) { @@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { ConnectionFailureDialog::hide(); } } else { - static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml"); + static const QUrl url("dialogs/TabletConnectionFailureDialog.qml"); auto hmd = DependencyManager::get(); if (visible) { tablet->initialScreen(url); diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 7ce2a0146d..2e40d3c087 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -46,7 +46,7 @@ void LoginDialog::showWithSelection() if (tablet->getToolbarMode()) { LoginDialog::show(); } else { - static const QUrl url("../../dialogs/TabletLoginDialog.qml"); + static const QUrl url("dialogs/TabletLoginDialog.qml"); tablet->initialScreen(url); if (!hmd->getShouldShowTablet()) { hmd->openTablet(); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e194551add..f991420fe8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -78,6 +78,8 @@ bool Stats::includeTimingRecord(const QString& name) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); } else if (name.startsWith("/paintGL/")) { return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else if (name.startsWith("step/")) { + return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming); } return true; } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index c41663fcc9..16b771567c 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -266,7 +266,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; +static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ba8bf8cbef..67fa94f7e5 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -24,7 +24,6 @@ ModelOverlay::ModelOverlay() : _model(std::make_shared(nullptr, this)), _modelTextures(QVariantMap()) { - _model->init(); _model->setLoadingPriority(_loadPriority); _isLoaded = false; } @@ -38,7 +37,6 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : _scaleToFit(modelOverlay->_scaleToFit), _loadPriority(modelOverlay->_loadPriority) { - _model->init(); _model->setLoadingPriority(_loadPriority); if (_url.isValid()) { _updateModel = true; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 71d1c4b842..fcbc51b541 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -200,7 +200,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); @@ -220,9 +219,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - _webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get().data()); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); // mark the TabletProxy object as cpp ownership. QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index bb7f141cd9..c532e7659f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -138,7 +138,6 @@ Avatar::~Avatar() { void Avatar::init() { getHead()->init(); - _skeletonModel->init(); _initialized = true; } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0993daaa8b..e646ba27f5 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -679,17 +679,13 @@ void OpenGLDisplayPlugin::internalPresent() { void OpenGLDisplayPlugin::present() { auto frameId = (uint64_t)presentCount(); PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId) + uint64_t startPresent = usecTimestampNow(); { PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId) updateFrameData(); } incrementPresentCount(); - { - PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, frameId) - _gpuContext->recycle(); - } - if (_currentFrame) { { withPresentThreadLock([&] { @@ -718,6 +714,7 @@ void OpenGLDisplayPlugin::present() { gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); } + _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } float OpenGLDisplayPlugin::newFramePresentRate() const { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7103e72f54..bf711b94c2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -251,10 +251,10 @@ void EntityTreeRenderer::shutdown() { } void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Add", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); + PROFILE_RANGE_EX(simulation_physics, "AddToScene", 0xffff00ff, (uint64_t)_entitiesToAdd.size()); PerformanceTimer pt("add"); - // Clear any expired entities - // FIXME should be able to use std::remove_if, but it fails due to some + // Clear any expired entities + // FIXME should be able to use std::remove_if, but it fails due to some // weird compilation error related to EntityItemID assignment operators for (auto itr = _entitiesToAdd.begin(); _entitiesToAdd.end() != itr; ) { if (itr->second.expired()) { @@ -272,7 +272,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r continue; } - // Path to the parent transforms is not valid, + // Path to the parent transforms is not valid, // don't add to the scene graph yet if (!entity->isParentPathComplete()) { continue; @@ -296,7 +296,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { - PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; _changedEntitiesGuard.withWriteLock([&] { @@ -349,7 +349,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } - const EntityRendererPointer& getRenderer() const { return _renderer; } + EntityRendererPointer getRenderer() const { return _renderer; } private: EntityRendererPointer _renderer; }; @@ -382,7 +382,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderable = sortedRenderables.top().getRenderer(); + const auto renderable = sortedRenderables.top().getRenderer(); renderable->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderable->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e578e4858d..e40e9a2f0c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -993,19 +993,15 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { } { - // the current frame is set on the server in update() in ModelEntityItem.cpp - int animationCurrentFrame = (int)(glm::floor(entity->getAnimationCurrentFrame())); - - // in the case where the last frame is greater than the framecount then clamp - // it to the end of the animation until it loops around. - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; + float currentFrame = fmod(entity->getAnimationCurrentFrame(), (float)(frameCount)); + if (currentFrame < 0.0f) { + currentFrame += (float)frameCount; } - - if (animationCurrentFrame == _lastKnownCurrentFrame) { + int currentIntegerFrame = (int)(glm::floor(currentFrame)); + if (currentIntegerFrame == _lastKnownCurrentFrame) { return; } - _lastKnownCurrentFrame = animationCurrentFrame; + _lastKnownCurrentFrame = currentIntegerFrame; } if (_jointMapping.size() != _model->getJointStateCount()) { @@ -1213,7 +1209,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); - model->init(); entity->setModel(model); withWriteLock([&] { _model = model; }); } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index fbf85fa8c2..21764dff7f 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -303,6 +303,7 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(0, _verticesBuffer, 0, sizeof(Vertex)); #ifndef POLYLINE_ENTITY_USE_FADE_EFFECT + // glColor4f must be called after setInputFormat if it must be taken into account if (_isFading) { batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); } else { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 3524709395..b00ad0259e 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -137,11 +137,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { }); if (proceduralRender) { - batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { - geometryCache->renderWireShape(batch, geometryShape); + geometryCache->renderWireShape(batch, geometryShape, outColor); } else { - geometryCache->renderShape(batch, geometryShape); + geometryCache->renderShape(batch, geometryShape, outColor); } } else { // FIXME, support instanced multi-shape rendering using multidraw indirect diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cdd260e73c..4c14be7e90 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -30,6 +30,8 @@ using namespace render; using namespace render::entities; +static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; + const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount{ 0 }; // Don't allow more than 100 concurrent web views @@ -124,7 +126,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { // This work must be done on the main thread if (!hasWebSurface()) { - buildWebSurface(entity); + // If we couldn't create a new web surface, exit + if (!buildWebSurface(entity)) { + return; + } } if (_contextPosition != entity->getWorldPosition()) { @@ -188,7 +193,6 @@ void WebEntityRenderer::doRender(RenderArgs* args) { }); batch.setResourceTexture(0, _texture); float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio); DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); @@ -218,6 +222,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }; { + // FIXME use the surface cache instead of explicit creation _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); _webSurface->create(); } @@ -291,7 +296,6 @@ void WebEntityRenderer::loadSourceURL() { if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { _contentType = htmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { @@ -300,12 +304,11 @@ void WebEntityRenderer::loadSourceURL() { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } - _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { item->setProperty("url", _lastSourceUrl); }); } else { _contentType = qmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); _webSurface->load(_lastSourceUrl); if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 503bdd8349..271fef33c8 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include // usecTimestampNow() #include @@ -114,6 +115,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_EDITION_NUMBER; requestedProperties += PROP_ENTITY_INSTANCE_NUMBER; requestedProperties += PROP_CERTIFICATE_ID; + requestedProperties += PROP_STATIC_CERTIFICATE_VERSION; requestedProperties += PROP_NAME; requestedProperties += PROP_HREF; @@ -271,6 +273,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); @@ -819,6 +822,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); @@ -984,6 +988,7 @@ void EntityItem::setCollisionSoundURL(const QString& value) { } void EntityItem::simulate(const quint64& now) { + DETAILED_PROFILE_RANGE(simulation_physics, "Simulate"); if (getLastSimulated() == 0) { setLastSimulated(now); } @@ -1039,6 +1044,7 @@ void EntityItem::simulate(const quint64& now) { } bool EntityItem::stepKinematicMotion(float timeElapsed) { + DETAILED_PROFILE_RANGE(simulation_physics, "StepKinematicMotion"); // get all the data Transform transform; glm::vec3 linearVelocity; @@ -1249,6 +1255,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber); COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); @@ -1356,6 +1363,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber); SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); @@ -1626,6 +1634,10 @@ void EntityItem::setParentID(const QUuid& value) { newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; } + if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) { + oldParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { markDirtyFlags(Simulation::NO_BOOTSTRAPPING); @@ -2783,6 +2795,7 @@ DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID) DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber) DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber) DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID) +DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion) uint32_t EntityItem::getDirtyFlags() const { uint32_t result; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4c7f37bd6a..088d21e84d 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -328,6 +328,8 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); + quint32 getStaticCertificateVersion() const; + void setStaticCertificateVersion(const quint32&); // TODO: get rid of users of getRadius()... float getRadius() const; @@ -547,6 +549,7 @@ protected: quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER }; quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER }; QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID }; + quint32 _staticCertificateVersion { ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION }; // NOTE: Damping is applied like this: v *= pow(1 - damping, dt) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index c7b08f2110..13ebd9ef9f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -324,6 +324,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); + CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); CHECK_PROPERTY_CHANGE(PROP_NAME, name); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); @@ -459,6 +460,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); @@ -742,6 +744,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); @@ -899,6 +902,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(editionNumber); COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); COPY_PROPERTY_IF_CHANGED(certificateID); + COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); COPY_PROPERTY_IF_CHANGED(name); COPY_PROPERTY_IF_CHANGED(collisionSoundURL); @@ -1089,6 +1093,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32); ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString); + ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); @@ -1477,6 +1482,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); } if (propertyCount > 0) { @@ -1828,6 +1834,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); return valid; } @@ -1995,6 +2002,7 @@ void EntityItemProperties::markAllChanged() { _editionNumberChanged = true; _entityInstanceNumberChanged = true; _certificateIDChanged = true; + _staticCertificateVersionChanged = true; _keyLight.markAllChanged(); @@ -2337,6 +2345,9 @@ QList EntityItemProperties::listChangedProperties() { if (certificateIDChanged()) { out += "certificateID"; } + if (staticCertificateVersionChanged()) { + out += "staticCertificateVersion"; + } if (backgroundModeChanged()) { out += "backgroundMode"; @@ -2479,6 +2490,9 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { // of the entity as reviewed during the certification submission. QJsonObject json; + + quint32 staticCertificateVersion = getStaticCertificateVersion(); + if (!getAnimation().getURL().isEmpty()) { json["animationURL"] = getAnimation().getURL(); } @@ -2495,7 +2509,11 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); ADD_STRING_PROPERTY(modelURL, ModelURL); ADD_STRING_PROPERTY(script, Script); + if (staticCertificateVersion >= 1) { + ADD_STRING_PROPERTY(serverScripts, ServerScripts); + } ADD_ENUM_PROPERTY(shapeType, ShapeType); + ADD_INT_PROPERTY(staticCertificateVersion, StaticCertificateVersion); json["type"] = EntityTypes::getEntityTypeName(getType()); return QJsonDocument(json).toJson(QJsonDocument::Compact); @@ -2512,7 +2530,8 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte return false; } - const unsigned char* key = reinterpret_cast(publicKey.toUtf8().constData()); + auto keyByteArray = publicKey.toUtf8(); + auto key = keyByteArray.constData(); int keyLength = publicKey.length(); BIO *bio = BIO_new_mem_buf((void*)key, keyLength); @@ -2530,19 +2549,23 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte // ECSDA verification prototype: note that type is currently ignored // int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, // const unsigned char *sig, int siglen, EC_KEY *eckey); - bool answer = ECDSA_verify(0, + int answer = ECDSA_verify(0, digest, digestLength, signature, signatureLength, ec); long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "ERROR while verifying signature! EC error:" << error_str + if (error != 0 || answer == -1) { + qCWarning(entities) << "ERROR while verifying signature!" << "\nKey:" << publicKey << "\nutf8 Key Length:" << keyLength << "\nDigest:" << digest << "\nDigest Length:" << digestLength << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; + while (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "EC error:" << error_str; + error = ERR_get_error(); + } } EC_KEY_free(ec); if (bio) { @@ -2551,7 +2574,7 @@ bool EntityItemProperties::verifySignature(const QString& publicKey, const QByte if (evp_key) { EVP_PKEY_free(evp_key); } - return answer; + return (answer == 1); } else { if (bio) { BIO_free(bio); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ec192d7c9f..4f7ba1317b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -220,6 +220,7 @@ public: DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER); DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER); DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID); + DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION); // these are used when bouncing location data into and out of scripts DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3); @@ -473,6 +474,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index f4e8a18012..49bce37fbd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -41,6 +41,7 @@ const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0; const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0; const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString(""); +const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 35d40b669a..41c1e77bb8 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -200,6 +200,7 @@ enum EntityPropertyList { PROP_EDITION_NUMBER, PROP_ENTITY_INSTANCE_NUMBER, PROP_CERTIFICATE_ID, + PROP_STATIC_CERTIFICATE_VERSION, PROP_HAZE_MODE, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 35941c09d3..d278283ffa 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1821,6 +1821,19 @@ glm::mat4 EntityScriptingInterface::getEntityLocalTransform(const QUuid& entityI return result; } +QString EntityScriptingInterface::getStaticCertificateJSON(const QUuid& entityID) { + QByteArray result; + if (_entityTree) { + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + if (entity) { + result = entity->getProperties().getStaticCertificateJSON(); + } + }); + } + return result; +} + bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& entityID) { bool result = false; if (_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index cc5d8ff16f..095a821482 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -423,6 +423,12 @@ public slots: */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); + + /**jsdoc + * Return the Static Certificate JSON for the specified {EntityID}. + * @return {QByteArray} The Static Certificate JSON for the specified entity. + */ + Q_INVOKABLE QString getStaticCertificateJSON(const QUuid& entityID); Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); signals: diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index f91d728d78..36b0d8ab2d 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -29,7 +29,6 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { } void EntitySimulation::updateEntities() { - PROFILE_RANGE(simulation_physics, "ES::updateEntities"); QMutexLocker lock(&_mutex); quint64 now = usecTimestampNow(); @@ -38,12 +37,7 @@ void EntitySimulation::updateEntities() { callUpdateOnEntitiesThatNeedIt(now); moveSimpleKinematics(now); updateEntitiesInternal(now); - - { - PROFILE_RANGE(simulation_physics, "Sort"); - PerformanceTimer perfTimer("sortingEntities"); - sortEntitiesThatMoved(); - } + sortEntitiesThatMoved(); } void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { @@ -101,6 +95,7 @@ void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { // protected void EntitySimulation::expireMortalEntities(const quint64& now) { if (now > _nextExpiry) { + PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one _nextExpiry = quint64(-1); QMutexLocker lock(&_mutex); @@ -146,6 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { // protected void EntitySimulation::sortEntitiesThatMoved() { + PROFILE_RANGE_EX(simulation_physics, "SortTree", 0xffff00ff, (uint64_t)_entitiesToSort.size()); // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. MovingEntitiesOperator moveOperator; @@ -265,7 +261,7 @@ void EntitySimulation::clearEntities() { } void EntitySimulation::moveSimpleKinematics(const quint64& now) { - PROFILE_RANGE_EX(simulation_physics, "Kinematics", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); + PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { EntityItemPointer entity = *itemItr; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0d91f3787e..8f780355db 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1189,13 +1189,15 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity key = sent.second; } - QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----"; - bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), actualNonce.toUtf8(), nonce.toUtf8()); + QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; + QByteArray hashedActualNonce = QCryptographicHash::hash(QByteArray(actualNonce.toUtf8()), QCryptographicHash::Sha256); + bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); if (verificationSuccess) { qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed for nonce" << actualNonce << "key" << key << "signature" << nonce; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce << + "\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key; } return verificationSuccess; @@ -1770,24 +1772,26 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { } void EntityTree::update(bool simulate) { - PROFILE_RANGE(simulation_physics, "ET::update"); + PROFILE_RANGE(simulation_physics, "UpdateTree"); fixupNeedsParentFixups(); if (simulate && _simulation) { withWriteLock([&] { _simulation->updateEntities(); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); + { + PROFILE_RANGE(simulation_physics, "Deletes"); + VectorOfEntities pendingDeletes; + _simulation->takeEntitiesToDelete(pendingDeletes); + if (pendingDeletes.size() > 0) { + // translate into list of ID's + QSet idsToDelete; - if (pendingDeletes.size() > 0) { - // translate into list of ID's - QSet idsToDelete; + for (auto entity : pendingDeletes) { + idsToDelete.insert(entity->getEntityItemID()); + } - for (auto entity : pendingDeletes) { - idsToDelete.insert(entity->getEntityItemID()); + // delete these things the roundabout way + deleteEntities(idsToDelete, true); } - - // delete these things the roundabout way - deleteEntities(idsToDelete, true); } }); } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 323584c7ee..3a79df34c6 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -245,7 +245,7 @@ void ModelEntityItem::updateFrameCount() { if (_currentFrame < 0.0f) { return; } - + if (!_lastAnimated) { _lastAnimated = usecTimestampNow(); return; @@ -263,7 +263,7 @@ void ModelEntityItem::updateFrameCount() { } int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; - + if (!getAnimationHold() && getAnimationIsPlaying()) { float deltaTime = (float)interval / (float)USECS_PER_SECOND; _currentFrame += (deltaTime * getAnimationFPS()); @@ -283,8 +283,6 @@ void ModelEntityItem::updateFrameCount() { // qCDebug(entities) << "in update frame " << _currentFrame; setAnimationCurrentFrame(_currentFrame); } - - } void ModelEntityItem::debugDump() const { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 6fb0d7b152..2192f5f45b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -605,6 +605,10 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { if (_input._colorAttribute != newColor) { _input._colorAttribute = newColor; glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); + // Color has been changed and is not white. To prevent colors from bleeding + // between different objects, we need to set the _hadColorAttribute flag + // as if a previous render call had potential colors + _input._hadColorAttribute = (newColor != glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); } (void)CHECK_GL_ERROR(); } @@ -772,7 +776,7 @@ void GLBackend::recycle() const { GLVariableAllocationSupport::manageMemory(); GLVariableAllocationSupport::_frameTexturesCreated = 0; - + Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 1908db614d..5558d3ada1 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -253,6 +253,7 @@ protected: struct InputStageState { bool _invalidFormat { true }; + bool _hadColorAttribute{ true }; Stream::FormatPointer _format; std::string _formatKey; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index e8ebcbe05c..42bd56e6e4 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -62,6 +62,8 @@ void GL41Backend::updateInput() { // now we need to bind the buffers and assign the attrib pointers if (_input._format) { + bool hasColorAttribute{ false }; + const Buffers& buffers = _input._buffers; const Offsets& offsets = _input._bufferOffsets; const Offsets& strides = _input._bufferStrides; @@ -98,6 +100,8 @@ void GL41Backend::updateInput() { uintptr_t pointer = (uintptr_t)(attrib._offset + offsets[bufferNum]); GLboolean isNormalized = attrib._element.isNormalized(); + hasColorAttribute = hasColorAttribute || (slot == Stream::COLOR); + for (size_t locNum = 0; locNum < locationCount; ++locNum) { if (attrib._element.isInteger()) { glVertexAttribIPointer(slot + (GLuint)locNum, count, type, stride, @@ -117,6 +121,15 @@ void GL41Backend::updateInput() { } } } + + if (_input._hadColorAttribute && !hasColorAttribute) { + // The previous input stage had a color attribute but this one doesn't so reset + // color to pure white. + const auto white = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + glVertexAttrib4fv(Stream::COLOR, &white.r); + _input._colorAttribute = white; + } + _input._hadColorAttribute = hasColorAttribute; } // everything format related should be in sync now _input._invalidFormat = false; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index ece62e15f1..4a43fc988c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -32,6 +32,8 @@ void GL45Backend::updateInput() { // Assign the vertex format required if (_input._format) { + bool hasColorAttribute{ false }; + _input._attribBindingBuffers.reset(); const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); @@ -54,6 +56,9 @@ void GL45Backend::updateInput() { GLboolean isNormalized = attrib._element.isNormalized(); GLenum perLocationSize = attrib._element.getLocationSize(); + + hasColorAttribute = hasColorAttribute || (slot == Stream::COLOR); + for (GLuint locNum = 0; locNum < locationCount; ++locNum) { GLuint attriNum = (GLuint)(slot + locNum); newActivation.set(attriNum); @@ -84,6 +89,15 @@ void GL45Backend::updateInput() { glVertexBindingDivisor(bufferChannelNum, frequency); #endif } + + if (_input._hadColorAttribute && !hasColorAttribute) { + // The previous input stage had a color attribute but this one doesn't so reset + // color to pure white. + const auto white = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + glVertexAttrib4fv(Stream::COLOR, &white.r); + _input._colorAttribute = white; + } + _input._hadColorAttribute = hasColorAttribute; } // Manage Activation what was and what is expected now diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 1877b494cf..06208179e0 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -321,13 +321,18 @@ public: void reset() override { } + // Don't keep files open forever. We close them at the beginning of each frame (GLBackend::recycle) + static void releaseOpenKtxFiles(); + protected: std::shared_ptr maybeOpenFile() const; - mutable std::mutex _cacheFileCreateMutex; - mutable std::mutex _cacheFileWriteMutex; + mutable std::shared_ptr _cacheFileMutex { std::make_shared() }; mutable std::weak_ptr _cacheFile; + static std::vector, std::shared_ptr>> _cachedKtxFiles; + static std::mutex _cachedKtxFilesMutex; + std::string _filename; cache::FilePointer _cacheEntry; std::atomic _minMipLevelAvailable; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 08fc4ec101..883d9abf15 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -23,6 +23,9 @@ using namespace gpu; using PixelsPointer = Texture::PixelsPointer; using KtxStorage = Texture::KtxStorage; +std::vector, std::shared_ptr>> KtxStorage::_cachedKtxFiles; +std::mutex KtxStorage::_cachedKtxFilesMutex; + struct GPUKTXPayload { using Version = uint8; @@ -187,36 +190,44 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } } +// maybeOpenFile should be called with _cacheFileMutex already held to avoid modifying the file from multiple threads std::shared_ptr KtxStorage::maybeOpenFile() const { - // 1. Try to get the shared ptr - // 2. If it doesn't exist, grab the mutex around its creation - // 3. If it was created before we got the mutex, return it - // 4. Otherwise, create it - + // Try to get the shared_ptr std::shared_ptr file = _cacheFile.lock(); if (file) { return file; } + // If the file isn't open, create it and save a weak_ptr to it + file = std::make_shared(_filename.c_str()); + _cacheFile = file; + { - std::lock_guard lock{ _cacheFileCreateMutex }; - - file = _cacheFile.lock(); - if (file) { - return file; - } - - file = std::make_shared(_filename.c_str()); - _cacheFile = file; + // Add the shared_ptr to the global list of open KTX files, to be released at the beginning of the next present thread frame + std::lock_guard lock(_cachedKtxFilesMutex); + _cachedKtxFiles.emplace_back(file, _cacheFileMutex); } return file; } +void KtxStorage::releaseOpenKtxFiles() { + std::vector, std::shared_ptr>> localKtxFiles; + { + std::lock_guard lock(_cachedKtxFilesMutex); + localKtxFiles.swap(_cachedKtxFiles); + } + for (auto& cacheFileAndMutex : localKtxFiles) { + std::lock_guard lock(*(cacheFileAndMutex.second)); + cacheFileAndMutex.first.reset(); + } +} + PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (file) { auto storageView = file->createView(faceSize, faceOffset); @@ -262,6 +273,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor return; } + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (!file) { qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename); @@ -279,8 +291,6 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor imageData += ktx::IMAGE_SIZE_WIDTH; { - std::lock_guard lock { _cacheFileWriteMutex }; - if (level != _minMipLevelAvailable - 1) { qWarning() << "Invalid level to be stored"; return; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 9e4ab2e43f..f78ed1a583 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -981,7 +981,7 @@ public: static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { QImage image(faceWidth, faceWidth, source.format()); - glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height()); + glm::vec2 dstInvSize(1.0f / faceWidth); struct CubeToXYZ { gpu::Texture::CubeFace _face; diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index ad650cf067..6c2471f680 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -177,9 +177,10 @@ void Midi::MidiCleanup() { #endif void Midi::noteReceived(int status, int note, int velocity) { - if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && - ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON)) { - return; // NOTE: only sending note-on and note-off to Javascript + if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && + ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) && + ((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) { + return; // NOTE: only sending note-on, note-off, and control-change to Javascript } QVariantMap eventData; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index b8f81dc7a4..8153dba3a5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -217,8 +217,6 @@ gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, co if (!result) { _texturesByHashes[hash] = texture; result = texture; - } else { - qCWarning(modelnetworking) << "QQQ Swapping out texture with previous live texture in hash " << hash.c_str(); } } return result; diff --git a/libraries/model/src/model/Haze.h b/libraries/model/src/model/Haze.h index a0cc7c3bc7..2a575eb151 100644 --- a/libraries/model/src/model/Haze.h +++ b/libraries/model/src/model/Haze.h @@ -128,7 +128,7 @@ namespace model { Parameters() {} }; - UniformBufferView _hazeParametersBuffer; + UniformBufferView _hazeParametersBuffer { nullptr }; }; using HazePointer = std::shared_ptr; diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 27d4a31ccf..58a4446aa6 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -127,7 +127,11 @@ void Assignment::swap(Assignment& otherAssignment) { } const char* Assignment::getTypeName() const { - switch (_type) { + return typeToString(_type); +} + +const char* Assignment::typeToString(Assignment::Type type) { + switch (type) { case Assignment::AudioMixerType: return "audio-mixer"; case Assignment::AvatarMixerType: diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index bbec40682f..e958c84d87 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -28,6 +28,7 @@ class Assignment : public QObject { public: enum Type : uint8_t { + FirstType = 0, AudioMixerType = 0, AvatarMixerType = 1, AgentType = 2, @@ -89,6 +90,7 @@ public: const QString& getNodeVersion() const { return _nodeVersion; } const char* getTypeName() const; + static const char* typeToString(Assignment::Type type); friend QDebug operator<<(QDebug debug, const Assignment& assignment); friend QDataStream& operator<<(QDataStream &out, const Assignment& assignment); diff --git a/libraries/networking/src/SentPacketHistory.cpp b/libraries/networking/src/SentPacketHistory.cpp index fbb7eff41a..3bd6d4cf03 100644 --- a/libraries/networking/src/SentPacketHistory.cpp +++ b/libraries/networking/src/SentPacketHistory.cpp @@ -24,8 +24,7 @@ SentPacketHistory::SentPacketHistory(int size) } -void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& packet) { - +void SentPacketHistory::untrackedPacketSent(uint16_t sequenceNumber) { // check if given seq number has the expected value. if not, something's wrong with // the code calling this function uint16_t expectedSequenceNumber = _newestSequenceNumber + (uint16_t)1; @@ -34,6 +33,10 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& pack << "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber; } _newestSequenceNumber = sequenceNumber; +} + +void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& packet) { + untrackedPacketSent(sequenceNumber); QWriteLocker locker(&_packetsLock); _sentPackets.insert(NLPacket::createCopy(packet)); diff --git a/libraries/networking/src/SentPacketHistory.h b/libraries/networking/src/SentPacketHistory.h index 72150658e3..dc92d38b25 100644 --- a/libraries/networking/src/SentPacketHistory.h +++ b/libraries/networking/src/SentPacketHistory.h @@ -27,6 +27,8 @@ class SentPacketHistory { public: SentPacketHistory(int size = MAX_REASONABLE_SEQUENCE_GAP); + void untrackedPacketSent(uint16_t sequenceNumber); + void packetSent(uint16_t sequenceNumber, const NLPacket& packet); const NLPacket* getPacket(uint16_t sequenceNumber) const; diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index 0965c9834f..c63170de75 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -89,17 +89,19 @@ void UserActivityLoggerScriptingInterface::doLogAction(QString action, QJsonObje Q_ARG(QJsonObject, details)); } -void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem) { +void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem) { QJsonObject payload; payload["marketplaceID"] = marketplaceID; + payload["contentCreator"] = contentCreator; payload["cost"] = cost; payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; doLogAction("commercePurchaseSuccess", payload); } -void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails) { +void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails) { QJsonObject payload; payload["marketplaceID"] = marketplaceID; + payload["contentCreator"] = contentCreator; payload["cost"] = cost; payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; payload["errorDetails"] = errorDetails; @@ -141,3 +143,16 @@ void UserActivityLoggerScriptingInterface::commerceWalletSetupFinished(int times payload["secondsToComplete"] = secondsToComplete; doLogAction("commerceWalletSetupFinished", payload); } + +void UserActivityLoggerScriptingInterface::commercePassphraseEntry(QString source) { + QJsonObject payload; + payload["source"] = source; + doLogAction("commercePassphraseEntry", payload); +} + +void UserActivityLoggerScriptingInterface::commercePassphraseAuthenticationStatus(QString status) { + QJsonObject payload; + payload["status"] = status; + doLogAction("commercePassphraseAuthenticationStatus", payload); + +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index e71723f03c..71d411056d 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -33,12 +33,14 @@ public: Q_INVOKABLE void bubbleToggled(bool newValue); Q_INVOKABLE void bubbleActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); - Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem); - Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails); + Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem); + Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails); Q_INVOKABLE void commerceEntityRezzed(QString marketplaceID, QString source, QString type); Q_INVOKABLE void commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain); Q_INVOKABLE void commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName); Q_INVOKABLE void commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete); + Q_INVOKABLE void commercePassphraseEntry(QString source); + Q_INVOKABLE void commercePassphraseAuthenticationStatus(QString status); private: void doLogAction(QString action, QJsonObject details = {}); }; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index f42049f107..77ed589e0b 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -11,6 +11,8 @@ #include "Connection.h" +#include + #include #include @@ -60,6 +62,15 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); + + + // setup psuedo-random number generation shared by all connections + static std::random_device rd; + static std::mt19937 generator(rd()); + static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); + + // randomize the intial sequence number + _initialSequenceNumber = SequenceNumber(distribution(generator)); } Connection::~Connection() { @@ -79,11 +90,11 @@ void Connection::stopSendQueue() { // tell the send queue to stop and be deleted sendQueue->stop(); + + _lastMessageNumber = sendQueue->getCurrentMessageNumber(); + sendQueue->deleteLater(); - // since we're stopping the send queue we should consider our handshake ACK not receieved - _hasReceivedHandshakeACK = false; - // wait on the send queue thread so we know the send queue is gone sendQueueThread->quit(); sendQueueThread->wait(); @@ -101,13 +112,19 @@ void Connection::setMaxBandwidth(int maxBandwidth) { SendQueue& Connection::getSendQueue() { if (!_sendQueue) { - // we may have a sequence number from the previous inactive queue - re-use that so that the // receiver is getting the sequence numbers it expects (given that the connection must still be active) // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination); - _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + + if (!_hasReceivedHandshakeACK) { + // First time creating a send queue for this connection + _sendQueue = SendQueue::create(_parentSocket, _destination, _initialSequenceNumber - 1, _lastMessageNumber, _hasReceivedHandshakeACK); + _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + } else { + // Connection already has a handshake from a previous send queue + _sendQueue = SendQueue::create(_parentSocket, _destination, _lastReceivedACK, _lastMessageNumber, _hasReceivedHandshakeACK); + } #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -142,14 +159,6 @@ void Connection::queueInactive() { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Connection to" << _destination << "has stopped its SendQueue."; #endif - - if (!_hasReceivedHandshake || !_isReceivingData) { -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection SendQueue to" << _destination << "stopped and no data is being received - stopping connection."; -#endif - - deactivate(); - } } void Connection::queueTimeout() { @@ -182,12 +191,23 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { pendingMessage.enqueuePacket(std::move(packet)); + bool processedLastOrOnly = false; + while (pendingMessage.hasAvailablePackets()) { auto packet = pendingMessage.removeNextPacket(); + + auto packetPosition = packet->getPacketPosition(); + _parentSocket->messageReceived(std::move(packet)); + + // if this was the last or only packet, then we can remove the pending message from our hash + if (packetPosition == Packet::PacketPosition::LAST || + packetPosition == Packet::PacketPosition::ONLY) { + processedLastOrOnly = true; + } } - if (pendingMessage.isComplete()) { + if (processedLastOrOnly) { _pendingReceivedMessages.erase(messageNumber); } } @@ -208,19 +228,6 @@ void Connection::sync() { && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { // the receive side of this connection is expired _isReceivingData = false; - - // if we don't have a send queue that means the whole connection has expired and we can emit our signal - // otherwise we'll wait for it to also timeout before cleaning up - if (!_sendQueue) { - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "no longer receiving any data and there is currently no send queue - stopping connection."; -#endif - - deactivate(); - - return; - } } // reset the number of light ACKs or non SYN ACKs during this sync interval @@ -242,26 +249,6 @@ void Connection::sync() { sendTimeoutNAK(); } } - } else if (!_sendQueue) { - // we haven't received a packet and we're not sending - // this most likely means we were started erroneously - // check the start time for this connection and auto expire it after 5 seconds of not receiving or sending any data - static const int CONNECTION_NOT_USED_EXPIRY_SECONDS = 5; - auto secondsSinceStart = duration_cast(p_high_resolution_clock::now() - _connectionStart).count(); - - if (secondsSinceStart >= CONNECTION_NOT_USED_EXPIRY_SECONDS) { - // it's been CONNECTION_NOT_USED_EXPIRY_SECONDS and nothing has actually happened with this connection - // consider it inactive and emit our inactivity signal - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "did not receive or send any data in last" - << CONNECTION_NOT_USED_EXPIRY_SECONDS << "seconds - stopping connection."; -#endif - - deactivate(); - - return; - } } } @@ -444,7 +431,6 @@ void Connection::sendHandshakeRequest() { } bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize) { - if (!_hasReceivedHandshake) { // Refuse to process any packets until we've received the handshake // Send handshake request to re-request a handshake @@ -536,7 +522,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _stats.recordReceivedPackets(payloadSize, packetSize); } - + return !wasDuplicate; } @@ -827,11 +813,13 @@ void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); - // hand off this handshake ACK to the send queue so it knows it can start sending - getSendQueue().handshakeACK(initialSequenceNumber); - - // indicate that handshake ACK was received - _hasReceivedHandshakeACK = true; + if (initialSequenceNumber == _initialSequenceNumber) { + // hand off this handshake ACK to the send queue so it knows it can start sending + getSendQueue().handshakeACK(); + + // indicate that handshake ACK was received + _hasReceivedHandshakeACK = true; + } } } diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index c134081dde..0017eb204a 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -37,7 +37,6 @@ class Socket; class PendingReceivedMessage { public: void enqueuePacket(std::unique_ptr packet); - bool isComplete() const { return _hasLastPacket && _numPackets == _packets.size(); } bool hasAvailablePackets() const; std::unique_ptr removeNextPacket(); @@ -72,8 +71,6 @@ public: void queueReceivedMessagePacket(std::unique_ptr packet); ConnectionStats::Stats sampleStats() { return _stats.sample(); } - - bool isActive() const { return _isActive; } HifiSockAddr getDestination() const { return _destination; } @@ -83,7 +80,6 @@ public: signals: void packetSent(); - void connectionInactive(const HifiSockAddr& sockAddr); void receiverHandshakeRequestComplete(const HifiSockAddr& sockAddr); private slots: @@ -112,8 +108,6 @@ private: void resetReceiveState(); void resetRTT(); - void deactivate() { _isActive = false; emit connectionInactive(_destination); } - SendQueue& getSendQueue(); SequenceNumber nextACK() const; void updateRTT(int rtt); @@ -138,9 +132,11 @@ private: p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection - bool _isActive { true }; // flag used for inactivity of connection - SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer SendQueue on creation, identifies connection during re-connect requests + SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests + SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests + + MessageNumber _lastMessageNumber { 0 }; LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 207ddf6bbb..0a75e8c31b 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::HazeEffect); + return static_cast(EntityVersion::OwnershipChallengeFix); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConnectionIdentifier); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e5cb87c379..85f321a198 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -197,9 +197,11 @@ uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); enum class EntityVersion : PacketVersion { - StrokeColorProperty = 77, + StrokeColorProperty = 0, HasDynamicOwnershipTests, - HazeEffect + HazeEffect, + StaticCertJsonVersionOne, + OwnershipChallengeFix, }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 9560f2f187..0560855ecb 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -15,7 +15,7 @@ using namespace udt; -PacketQueue::PacketQueue() { +PacketQueue::PacketQueue(MessageNumber messageNumber) : _currentMessageNumber(messageNumber) { _channels.emplace_back(new std::list()); } diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index 2b3d3a4b5b..bc4c5e3432 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -34,7 +34,7 @@ class PacketQueue { using Channels = std::vector; public: - PacketQueue(); + PacketQueue(MessageNumber messageNumber = 0); void queuePacket(PacketPointer packet); void queuePacketList(PacketListPointer packetList); @@ -42,6 +42,8 @@ public: PacketPointer takePacket(); Mutex& getLock() { return _packetsLock; } + + MessageNumber getCurrentMessageNumber() const { return _currentMessageNumber; } private: MessageNumber getNextMessageNumber(); diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 0c029751aa..b62624aab9 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -12,7 +12,6 @@ #include "SendQueue.h" #include -#include #include #include @@ -62,10 +61,12 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination)); + auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber, + currentMessageNumber, hasReceivedHandshakeACK)); // Setup queue private thread QThread* thread = new QThread; @@ -84,25 +85,18 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) : + _packets(currentMessageNumber), _socket(socket), _destination(dest) { - // setup psuedo-random number generation for all instances of SendQueue - static std::random_device rd; - static std::mt19937 generator(rd()); - static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); - - // randomize the intial sequence number - _initialSequenceNumber = SequenceNumber(distribution(generator)); - - // set our member variables from randomized initial number - _currentSequenceNumber = _initialSequenceNumber - 1; + // set our member variables from current sequence number + _currentSequenceNumber = currentSequenceNumber; _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); - _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; + _lastACKSequenceNumber = uint32_t(_currentSequenceNumber); - // default the last receiver response to the current time - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); + _hasReceivedHandshakeACK = hasReceivedHandshakeACK; } SendQueue::~SendQueue() { @@ -114,8 +108,8 @@ void SendQueue::queuePacket(std::unique_ptr packet) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -125,8 +119,8 @@ void SendQueue::queuePacketList(std::unique_ptr packetList) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -144,9 +138,6 @@ int SendQueue::sendPacket(const Packet& packet) { } void SendQueue::ack(SequenceNumber ack) { - // this is a response from the client, re-set our timeout expiry and our last response time - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - if (_lastACKSequenceNumber == (uint32_t) ack) { return; } @@ -173,10 +164,7 @@ void SendQueue::ack(SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::nak(SequenceNumber start, SequenceNumber end) { - // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - +void SendQueue::nak(SequenceNumber start, SequenceNumber end) { { std::lock_guard nakLocker(_naksLock); _naks.insert(start, end); @@ -197,9 +185,6 @@ void SendQueue::fastRetransmit(udt::SequenceNumber ack) { } void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { - // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - { std::lock_guard nakLocker(_naksLock); _naks.clear(); @@ -225,8 +210,11 @@ void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client, send another now + // if the handshake hasn't been completed, then the initial sequence number + // should be the current sequence number + 1 + SequenceNumber initialSequenceNumber = _currentSequenceNumber + 1; auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber)); - handshakePacket->writePrimitive(_initialSequenceNumber); + handshakePacket->writePrimitive(initialSequenceNumber); _socket->writeBasePacket(*handshakePacket, _destination); // we wait for the ACK or the re-send interval to expire @@ -235,18 +223,14 @@ void SendQueue::sendHandshake() { } } -void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { - if (initialSequenceNumber == _initialSequenceNumber) { - { - std::lock_guard locker { _handshakeMutex }; - _hasReceivedHandshakeACK = true; - } - - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - - // Notify on the handshake ACK condition - _handshakeACKCondition.notify_one(); +void SendQueue::handshakeACK() { + { + std::lock_guard locker { _handshakeMutex }; + _hasReceivedHandshakeACK = true; } + + // Notify on the handshake ACK condition + _handshakeACKCondition.notify_one(); } SequenceNumber SendQueue::getNextSequenceNumber() { @@ -540,28 +524,6 @@ bool SendQueue::maybeResendPacket() { bool SendQueue::isInactive(bool attemptedToSendPacket) { // check for connection timeout first - // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been - // at least 5 seconds - static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16; - static const int MIN_MS_BEFORE_INACTIVE = 5 * 1000; - - auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse); - - if (sinceLastResponse > 0 && - sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && - sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { - // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, - // then signal the queue is inactive and return so it can be cleaned up - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts" - << "and" << MIN_MS_BEFORE_INACTIVE << "milliseconds before receiving any ACK/NAK and is now inactive. Stopping."; -#endif - - deactivate(); - return true; - } - if (!attemptedToSendPacket) { // During our processing above we didn't send any packets diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 484afcb88e..a11aacdb91 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,7 +50,9 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination, + SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, + bool hasReceivedHandshakeACK); virtual ~SendQueue(); @@ -58,6 +60,7 @@ public: void queuePacketList(std::unique_ptr packetList); SequenceNumber getCurrentSequenceNumber() const { return SequenceNumber(_atomicCurrentSequenceNumber); } + MessageNumber getCurrentMessageNumber() const { return _packets.getCurrentMessageNumber(); } void setFlowWindowSize(int flowWindowSize) { _flowWindowSize = flowWindowSize; } @@ -76,7 +79,7 @@ public slots: void nak(SequenceNumber start, SequenceNumber end); void fastRetransmit(SequenceNumber ack); void overrideNAKListFromPacket(ControlPacket& packet); - void handshakeACK(SequenceNumber initialSequenceNumber); + void handshakeACK(); signals: void packetSent(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); @@ -91,7 +94,8 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest); + SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -115,8 +119,6 @@ private: Socket* _socket { nullptr }; // Socket to send packet on HifiSockAddr _destination; // Destination addr - - SequenceNumber _initialSequenceNumber; // Randomized on SendQueue creation, identifies connection during re-connect requests std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number @@ -128,7 +130,6 @@ private: std::atomic _estimatedTimeout { 0 }; // Estimated timeout, set from CC std::atomic _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC - std::atomic _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) std::atomic _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index a3374a0f47..55643985c8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -257,9 +257,6 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { congestionControl->setMaxBandwidth(_maxBandwidth); auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); - // we queue the connection to cleanup connection in case it asks for it during its own rate control sync - QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); - // allow higher-level classes to find out when connections have completed a handshake QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete, this, &Socket::clientHandshakeRequestComplete); diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 7563122290..3190f25d7e 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef _WIN32 -#define _USE_MATH_DEFINES -#endif - #include #include #include diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 9cb383df41..7e46831faa 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -291,6 +291,9 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& // release the new packet releaseQueuedPacketList(nodeUUID, std::move(newPacket)); + // tell the sent packet history that we used a sequence number for an untracked packet + auto& sentPacketHistory = _sentPacketHistories[nodeUUID]; + sentPacketHistory.untrackedPacketSent(sequence); } else { std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 7e8b431ceb..a688d521d6 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -14,8 +14,9 @@ #include #include #include -#include #include +#include +#include #include "BulletUtil.h" #include "EntityMotionState.h" @@ -325,6 +326,7 @@ bool EntityMotionState::isCandidateForOwnership() const { } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation assert(_body); @@ -476,6 +478,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. assert(_entity); @@ -516,6 +519,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); assert(_entity); assert(entityTreeIsLocked()); @@ -731,6 +735,7 @@ void EntityMotionState::resetMeasuredBodyAcceleration() { } void EntityMotionState::measureBodyAcceleration() { + DETAILED_PROFILE_RANGE(simulation_physics, "MeasureAccel"); // try to manually measure the true acceleration of the object uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); uint32_t numSubsteps = thisStep - _lastMeasureStep; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3e87b9437d..e4ba47e205 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -10,12 +10,14 @@ // +#include "PhysicalEntitySimulation.h" + +#include #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" -#include "PhysicalEntitySimulation.h" PhysicalEntitySimulation::PhysicalEntitySimulation() { } @@ -274,20 +276,24 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio } void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionStates& motionStates) { + PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); // walk the motionStates looking for those that correspond to entities - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + { + PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + EntityItemPointer entity = entityState->getEntity(); + assert(entity.get()); + if (entityState->isCandidateForOwnership()) { + _outgoingChanges.insert(entityState); + } + _entitiesToSort.insert(entity); } - _entitiesToSort.insert(entity); } } @@ -302,6 +308,7 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta } // look for entities to prune or update + PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a64796308e..fe794772e2 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -11,7 +11,12 @@ #include +#include + +#include + #include +#include #include "CharacterController.h" #include "ObjectMotionState.h" @@ -290,6 +295,7 @@ void PhysicsEngine::stepSimulation() { float timeStep = btMin(dt, MAX_TIMESTEP); if (_myAvatarController) { + DETAILED_PROFILE_RANGE(simulation_physics, "avatarController"); BT_PROFILE("avatarController"); // TODO: move this stuff outside and in front of stepSimulation, because // the updateShapeIfNecessary() call needs info from MyAvatar and should @@ -328,45 +334,107 @@ void PhysicsEngine::stepSimulation() { } } +class CProfileOperator { +public: + CProfileOperator() {} + void recurse(CProfileIterator* itr, QString context) { + // The context string will get too long if we accumulate it properly + //QString newContext = context + QString("/") + itr->Get_Current_Parent_Name(); + // so we use this four-character indentation + QString newContext = context + QString(".../"); + process(itr, newContext); + + // count the children + int32_t numChildren = 0; + itr->First(); + while (!itr->Is_Done()) { + itr->Next(); + ++numChildren; + } + + // recurse the children + if (numChildren > 0) { + // recurse the children + for (int32_t i = 0; i < numChildren; ++i) { + itr->Enter_Child(i); + recurse(itr, newContext); + } + } + // retreat back to parent + itr->Enter_Parent(); + } + virtual void process(CProfileIterator*, QString context) = 0; +}; + +class StatsHarvester : public CProfileOperator { +public: + StatsHarvester() {} + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time()); + PerformanceTimer::addTimerRecord(name, time); + }; +}; + +class StatsWriter : public CProfileOperator { +public: + StatsWriter(QString filename) : _file(filename) { + _file.open(QFile::WriteOnly); + if (_file.error() != QFileDevice::NoError) { + qCDebug(physics) << "unable to open file " << _file.fileName() << " to save stepSimulation() stats"; + } + } + ~StatsWriter() { + _file.close(); + } + void process(CProfileIterator* itr, QString context) override { + QString name = context + itr->Get_Current_Parent_Name(); + float time = (btScalar)MSECS_PER_SECOND * itr->Get_Current_Parent_Total_Time(); + if (_file.error() == QFileDevice::NoError) { + QTextStream s(&_file); + s << name << " = " << time << "\n"; + } + } +protected: + QFile _file; +}; + void PhysicsEngine::harvestPerformanceStats() { // unfortunately the full context names get too long for our stats presentation format //QString contextName = PerformanceTimer::getContextName(); // TODO: how to show full context name? QString contextName("..."); - CProfileIterator* profileIterator = CProfileManager::Get_Iterator(); - if (profileIterator) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { // hunt for stepSimulation context - profileIterator->First(); - for (int32_t childIndex = 0; !profileIterator->Is_Done(); ++childIndex) { - if (QString(profileIterator->Get_Current_Name()) == "stepSimulation") { - profileIterator->Enter_Child(childIndex); - recursivelyHarvestPerformanceStats(profileIterator, contextName); + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsHarvester harvester; + harvester.recurse(itr, "step/"); break; } - profileIterator->Next(); + itr->Next(); } } } -void PhysicsEngine::recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName) { - QString parentContextName = contextName + QString("/") + QString(profileIterator->Get_Current_Parent_Name()); - // get the stats for the children - int32_t numChildren = 0; - profileIterator->First(); - while (!profileIterator->Is_Done()) { - QString childContextName = parentContextName + QString("/") + QString(profileIterator->Get_Current_Name()); - uint64_t time = (uint64_t)((btScalar)MSECS_PER_SECOND * profileIterator->Get_Current_Total_Time()); - PerformanceTimer::addTimerRecord(childContextName, time); - profileIterator->Next(); - ++numChildren; +void PhysicsEngine::printPerformanceStatsToFile(const QString& filename) { + CProfileIterator* itr = CProfileManager::Get_Iterator(); + if (itr) { + // hunt for stepSimulation context + itr->First(); + for (int32_t childIndex = 0; !itr->Is_Done(); ++childIndex) { + if (QString(itr->Get_Current_Name()) == "stepSimulation") { + itr->Enter_Child(childIndex); + StatsWriter writer(filename); + writer.recurse(itr, ""); + break; + } + itr->Next(); + } } - // recurse the children - for (int32_t i = 0; i < numChildren; ++i) { - profileIterator->Enter_Child(i); - recursivelyHarvestPerformanceStats(profileIterator, contextName); - } - // retreat back to parent - profileIterator->Enter_Parent(); } void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { @@ -399,6 +467,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const } void PhysicsEngine::updateContactMap() { + DETAILED_PROFILE_RANGE(simulation_physics, "updateContactMap"); BT_PROFILE("updateContactMap"); ++_numContactFrames; @@ -515,10 +584,21 @@ const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { void PhysicsEngine::dumpStatsIfNecessary() { if (_dumpNextStats) { _dumpNextStats = false; + CProfileManager::Increment_Frame_Counter(); + if (_saveNextStats) { + _saveNextStats = false; + printPerformanceStatsToFile(_statsFilename); + } CProfileManager::dumpAll(); } } +void PhysicsEngine::saveNextPhysicsStats(QString filename) { + _saveNextStats = true; + _dumpNextStats = true; + _statsFilename = filename; +} + // Bullet collision flags are as follows: // CF_STATIC_OBJECT= 1, // CF_KINEMATIC_OBJECT= 2, diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 3063a4a89a..6619a5489d 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -62,6 +62,7 @@ public: void stepSimulation(); void harvestPerformanceStats(); + void printPerformanceStatsToFile(const QString& filename); void updateContactMap(); bool hasOutgoingChanges() const { return _hasOutgoingChanges; } @@ -76,6 +77,9 @@ public: /// \brief prints timings for last frame if stats have been requested. void dumpStatsIfNecessary(); + /// \brief saves timings for last frame in filename + void saveNextPhysicsStats(QString filename); + /// \param offset position of simulation origin in domain-frame void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } @@ -94,7 +98,6 @@ public: private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); - void recursivelyHarvestPerformanceStats(CProfileIterator* profileIterator, QString contextName); /// \brief bump any objects that touch this one, then remove contact info void bumpAndPruneContacts(ObjectMotionState* motionState); @@ -116,6 +119,7 @@ private: QHash _objectDynamics; QHash> _objectDynamicsByBody; std::set _activeStaticBodies; + QString _statsFilename; glm::vec3 _originOffset; @@ -124,8 +128,9 @@ private: uint32_t _numContactFrames = 0; uint32_t _numSubsteps; - bool _dumpNextStats = false; - bool _hasOutgoingChanges = false; + bool _dumpNextStats { false }; + bool _saveNextStats { false }; + bool _hasOutgoingChanges { false }; }; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 24cfbc2609..5b8c0d5843 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -18,6 +18,7 @@ #include #include "ThreadSafeDynamicsWorld.h" +#include "Profile.h" ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( btDispatcher* dispatcher, @@ -29,6 +30,7 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep, SubStepCallback onSubStep) { + DETAILED_PROFILE_RANGE(simulation_physics, "stepWithCB"); BT_PROFILE("stepSimulationWithSubstepCallback"); int subSteps = 0; if (maxSubSteps) { @@ -68,11 +70,13 @@ int ThreadSafeDynamicsWorld::stepSimulationWithSubstepCallback(btScalar timeStep saveKinematicState(fixedTimeStep*clampedSimulationSteps); { + DETAILED_PROFILE_RANGE(simulation_physics, "applyGravity"); BT_PROFILE("applyGravity"); applyGravity(); } for (int i=0;i #include +#include #include #include +#include #include #include "Plugin.h" @@ -203,6 +205,7 @@ public: virtual void cycleDebugOutput() {} void waitForPresent(); + float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec std::function getHUDOperator(); @@ -220,6 +223,8 @@ protected: std::function _hudOperator { std::function() }; + MovingAverage _movingAveragePresent; + private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 3bb2aa2ef9..396cd13508 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -40,17 +40,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE); - if (useCauterizedMesh) { - ModelPointer model = _model.lock(); - if (model) { - CauterizedModel* skeleton = static_cast(model.get()); - useCauterizedMesh = useCauterizedMesh && skeleton->getEnableCauterization(); - } else { - useCauterizedMesh = false; - } - } - + bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization; if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer); diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 1c98f5abf3..44eddc6e31 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -21,9 +21,12 @@ public: void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; } + private: gpu::BufferPointer _cauterizedClusterBuffer; Transform _cauterizedTransform; + bool _enableCauterization { false }; }; #endif // hifi_CauterizedMeshPartPayload_h diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index c437a8c556..dbb82ab638 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -178,6 +178,12 @@ void CauterizedModel::updateRenderItems() { modelTransform.setTranslation(self->getTranslation()); modelTransform.setRotation(self->getRotation()); + bool isWireframe = self->isWireframe(); + bool isVisible = self->isVisible(); + bool isLayeredInFront = self->isLayeredInFront(); + bool isLayeredInHUD = self->isLayeredInHUD(); + bool enableCauterization = self->getEnableCauterization(); + render::Transaction transaction; for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) { @@ -186,7 +192,10 @@ void CauterizedModel::updateRenderItems() { auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices); - transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized](CauterizedMeshPartPayload& data) { + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized, invalidatePayloadShapeKey, + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized); Transform renderTransform = modelTransform; @@ -200,6 +209,11 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0])); } data.updateTransformForCauterizedMesh(renderTransform); + + data.setEnableCauterization(enableCauterization); + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index f70daf1e77..de2d41be6b 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -91,6 +91,7 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <@endfunc@> +<@include Haze.slh@> <@func declareEvalSkyboxGlobalColor(supportScattering)@> @@ -101,8 +102,6 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <$declareDeferredCurvature()$> <@endif@> -<@include Haze.slh@> - vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness <@if supportScattering@> @@ -122,7 +121,6 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu color += ambientDiffuse; color += ambientSpecular; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation @@ -135,52 +133,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Attenuate the light if haze effect selected if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { - // Directional light attenuation is simulated by assuming the light source is at a fixed height above the - // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height - // - // The distance is computed from the height and the directional light orientation - // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees - - // Get directional light - Light light = getLight(); - vec3 lightDirection = getLightDirection(light); - - // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) - float height_95p = 2000.0; - if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { - height_95p = -log(0.05) / hazeParams.hazeKeyLightAltitudeFactor; - } - - // Note that the sine will always be positive - float sin_pitch = sqrt(1.0 - lightDirection.y * lightDirection.y); - - float distance; - const float minimumSinPitch = 0.001; - if (sin_pitch < minimumSinPitch) { - distance = height_95p / minimumSinPitch; - } else { - distance = height_95p / sin_pitch; - } - - // Position of fragment in world coordinates - vec4 worldFragPos = invViewMat * vec4(position, 0.0); - - // Integration is from the fragment towards the light source - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeKeyLightRangeFactor * - exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - // Note that t is constant and equal to -log(0.05) - // float t = hazeParams.hazeAltitudeFactor * height_95p; - // hazeIntegral *= (1.0 - exp (-t)) / t; - hazeIntegral *= 0.3171178; - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - color = mix(color, vec3(0.0, 0.0, 0.0), hazeAmount); + color = computeHazeColorKeyLightAttenuation(color, lightDirection, position); } return color; @@ -213,9 +166,6 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur } <@endfunc@> - - - <@func declareEvalGlobalLightingAlphaBlended()@> <$declareLightingAmbient(1, 1, 1)$> @@ -233,7 +183,6 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl color += ambientDiffuse; color += ambientSpecular / opacity; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; @@ -244,6 +193,43 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl return color; } +vec3 evalGlobalLightingAlphaBlendedWithHaze( + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) +{ + <$prepareGlobalLight()$> + + color += emissive * isEmissiveEnabled(); + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance); + color += ambientDiffuse; + color += ambientSpecular / opacity; + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; + + // Haze + if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { + vec4 colorV4 = computeHazeColor( + vec4(color, 1.0), // fragment original color + position, // fragment position in eye coordinates + fragEyeVector, // fragment position in world coordinates + invViewMat[3].y, // eye height in world coordinates + lightDirection // keylight direction vector + ); + + color = colorV4.rgb; + } + + return color; +} <@endfunc@> diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 76c354bdf8..2616d08600 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -760,6 +760,20 @@ void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) { _shapes[shape].drawWire(batch); } +void GeometryCache::renderShape(gpu::Batch& batch, Shape shape, const glm::vec4& color) { + batch.setInputFormat(getSolidStreamFormat()); + // Color must be set after input format + batch._glColor4f(color.r, color.g, color.b, color.a); + _shapes[shape].draw(batch); +} + +void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape, const glm::vec4& color) { + batch.setInputFormat(getSolidStreamFormat()); + // Color must be set after input format + batch._glColor4f(color.r, color.g, color.b, color.a); + _shapes[shape].drawWire(batch); +} + void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) { gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); batch.setInputBuffer(gpu::Stream::COLOR, colorView); @@ -811,6 +825,14 @@ void GeometryCache::renderWireCube(gpu::Batch& batch) { renderWireShape(batch, Cube); } +void GeometryCache::renderCube(gpu::Batch& batch, const glm::vec4& color) { + renderShape(batch, Cube, color); +} + +void GeometryCache::renderWireCube(gpu::Batch& batch, const glm::vec4& color) { + renderWireShape(batch, Cube, color); +} + void GeometryCache::renderSphere(gpu::Batch& batch) { renderShape(batch, Sphere); } @@ -819,6 +841,14 @@ void GeometryCache::renderWireSphere(gpu::Batch& batch) { renderWireShape(batch, Sphere); } +void GeometryCache::renderSphere(gpu::Batch& batch, const glm::vec4& color) { + renderShape(batch, Sphere, color); +} + +void GeometryCache::renderWireSphere(gpu::Batch& batch, const glm::vec4& color) { + renderWireShape(batch, Sphere, color); +} + void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, int majorRows, int majorCols, float majorEdge, int minorRows, int minorCols, float minorEdge, diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index cd8c43f1df..0585cc9e55 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -251,14 +251,20 @@ public: // Dynamic geometry void renderShape(gpu::Batch& batch, Shape shape); void renderWireShape(gpu::Batch& batch, Shape shape); + void renderShape(gpu::Batch& batch, Shape shape, const glm::vec4& color); + void renderWireShape(gpu::Batch& batch, Shape shape, const glm::vec4& color); size_t getShapeTriangleCount(Shape shape); void renderCube(gpu::Batch& batch); void renderWireCube(gpu::Batch& batch); + void renderCube(gpu::Batch& batch, const glm::vec4& color); + void renderWireCube(gpu::Batch& batch, const glm::vec4& color); size_t getCubeTriangleCount(); void renderSphere(gpu::Batch& batch); void renderWireSphere(gpu::Batch& batch); + void renderSphere(gpu::Batch& batch, const glm::vec4& color); + void renderWireSphere(gpu::Batch& batch, const glm::vec4& color); size_t getSphereTriangleCount(); void renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index b366e6d639..0270aa58f0 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -23,6 +23,7 @@ <@include Haze.slh@> uniform sampler2D colorMap; +uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { float Zeye = -texture(linearDepthMap, texcoord).x; @@ -45,104 +46,15 @@ void main(void) { discard; } - // Distance to fragment - vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - float distance = length(eyeFragPos.xyz); - vec4 fragColor = texture(colorMap, varTexCoord0); - vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - // Directional light component is a function of the angle from the eye, between the fragment and the sun - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - vec4 worldFragPos = getViewInverse() * eyeFragPos; - vec3 eyeFragDir = normalize(worldFragPos.xyz); + mat4 viewInverse = getViewInverse(); + vec4 worldFragPos = viewInverse * eyeFragPos; + vec4 worldEyePos = viewInverse[3]; Light light = getLight(); vec3 lightDirection = getLightDirection(light); - float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); - float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); - - vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); - - // Use the haze colour for the glare colour, if blend is not enabled - vec4 blendedHazeColor; - if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { - blendedHazeColor = mix(hazeColor, glareColor, power); - } else { - blendedHazeColor = hazeColor; - } - - vec4 potentialFragColor; - - if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { - // Compute separately for each colour - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - vec3 hazeDensityDistribution = - hazeParams.colorModulationFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - vec3 hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - hazeIntegral *= (1.0 - exp (-t)) / t; - } - - vec3 hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); - } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { - // Haze is based only on range - float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } else { - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeRangeFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - // Protect from wild values - if (abs(t) > 0.0000001) { - hazeIntegral *= (1.0 - exp (-t)) / t; - } - } - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } - - // Mix with background at far range - const float BLEND_DISTANCE = 27000.0; - if (distance > BLEND_DISTANCE) { - outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); - } else { - outFragColor = potentialFragColor; - } + outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection); } - diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index de7f0ac246..15e484e055 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -40,7 +40,160 @@ layout(std140) uniform hazeBuffer { HazeParams hazeParams; }; -uniform sampler2D linearDepthMap; + +// Input: +// color - fragment original color +// lightDirection - parameters of the keylight +// worldFragPos - fragment position in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 worldFragPos) { + // Directional light attenuation is simulated by assuming the light source is at a fixed height above the + // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height + // + // The distance is computed from the height and the directional light orientation + // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees + + // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) + float height_95p = 2000.0; + const float log_p_005 = log(0.05); + if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { + height_95p = -log_p_005 / hazeParams.hazeKeyLightAltitudeFactor; + } + + // Note that we need the sine to be positive + float sin_pitch = abs(lightDirection.y); + + float distance; + const float minimumSinPitch = 0.001; + if (sin_pitch < minimumSinPitch) { + distance = height_95p / minimumSinPitch; + } else { + distance = height_95p / sin_pitch; + } + + // Integration is from the fragment towards the light source + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeKeyLightRangeFactor * + exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + // Note that t is constant and equal to -log(0.05) + // float t = hazeParams.hazeAltitudeFactor * height_95p; + // hazeIntegral *= (1.0 - exp (-t)) / t; + hazeIntegral *= 0.3171178; + + return color * exp(-hazeIntegral); +} + +// Input: +// fragColor - fragment original color +// eyeFragPos - fragment position in eye coordinates +// worldFragPos - fragment position in world coordinates +// worldEyeHeight - eye height in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float worldEyeHeight, vec3 lightDirection) { + // Distance to fragment + float distance = length(eyeFragPos); + + // Convert haze colour from uniform into a vec4 + vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + + // Directional light component is a function of the angle from the eye, between the fragment and the sun + vec3 eyeFragDir = normalize(worldFragPos); + + float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); + float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); + + vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); + + // Use the haze colour for the glare colour, if blend is not enabled + vec4 blendedHazeColor; + if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { + blendedHazeColor = mix(hazeColor, glareColor, power); + } else { + blendedHazeColor = hazeColor; + } + + vec4 potentialFragColor; + + if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { + // Compute separately for each colour + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + vec3 hazeDensityDistribution = + hazeParams.colorModulationFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + vec3 hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + hazeIntegral *= (1.0 - exp (-t)) / t; + } + + vec3 hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); + } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { + // Haze is based only on range + float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } else { + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeRangeFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + // Protect from wild values + const float EPSILON = 0.0000001f; + if (abs(t) > EPSILON) { + hazeIntegral *= (1.0 - exp (-t)) / t; + } + } + + float hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } + + // Mix with background at far range + const float BLEND_DISTANCE = 27000.0f; + vec4 outFragColor; + if (distance > BLEND_DISTANCE) { + outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); + } else { + outFragColor = potentialFragColor; + } + + return outFragColor; +} <@endif@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 1ea3e1a705..2a59c7d3c5 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -122,11 +122,6 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputFormat((_drawMesh->getVertexFormat())); batch.setInputStream(0, _drawMesh->getVertexStream()); - - // TODO: Get rid of that extra call - if (!_hasColorAttrib) { - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - } } void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, bool enableTextures) const { @@ -325,7 +320,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in _shapeID(shapeIndex) { assert(model && model->isLoaded()); - _model = model; + _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); const Model::MeshState& state = model->getMeshState(_meshIndex); @@ -339,13 +334,10 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in } updateTransformForSkinnedMesh(renderTransform, transform); - initCache(); + initCache(model); } -void ModelMeshPartPayload::initCache() { - ModelPointer model = _model.lock(); - assert(model && model->isLoaded()); - +void ModelMeshPartPayload::initCache(const ModelPointer& model) { if (_drawMesh) { auto vertexFormat = _drawMesh->getVertexFormat(); _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); @@ -355,6 +347,7 @@ void ModelMeshPartPayload::initCache() { const FBXMesh& mesh = geometry.meshes.at(_meshIndex); _isBlendShaped = !mesh.blendshapes.isEmpty(); + _hasTangents = !mesh.tangents.isEmpty(); } auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); @@ -388,94 +381,70 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -ItemKey ModelMeshPartPayload::getKey() const { +void ModelMeshPartPayload::setKey(bool isVisible, bool isLayered) { ItemKey::Builder builder; builder.withTypeShape(); - ModelPointer model = _model.lock(); - if (model) { - if (!model->isVisible()) { - builder.withInvisible(); - } + if (!isVisible) { + builder.withInvisible(); + } - if (model->isLayeredInFront() || model->isLayeredInHUD()) { - builder.withLayered(); - } + if (isLayered) { + builder.withLayered(); + } - if (_isBlendShaped || _isSkinned) { - builder.withDeformed(); - } + if (_isBlendShaped || _isSkinned) { + builder.withDeformed(); + } - if (_drawMaterial) { - auto matKey = _drawMaterial->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterial) { + auto matKey = _drawMaterial->getKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } } - return builder.build(); + + _itemKey = builder.build(); +} + +ItemKey ModelMeshPartPayload::getKey() const { + return _itemKey; +} + +void ModelMeshPartPayload::setLayer(bool isLayeredInFront, bool isLayeredInHUD) { + if (isLayeredInFront) { + _layer = Item::LAYER_3D_FRONT; + } else if (isLayeredInHUD) { + _layer = Item::LAYER_3D_HUD; + } else { + _layer = Item::LAYER_3D; + } } int ModelMeshPartPayload::getLayer() const { - ModelPointer model = _model.lock(); - if (model) { - if (model->isLayeredInFront()) { - return Item::LAYER_3D_FRONT; - } else if (model->isLayeredInHUD()) { - return Item::LAYER_3D_HUD; - } - } - return Item::LAYER_3D; + return _layer; } -ShapeKey ModelMeshPartPayload::getShapeKey() const { - // guard against partially loaded meshes - ModelPointer model = _model.lock(); - if (!model || !model->isLoaded() || !model->getGeometry()) { - return ShapeKey::Builder::invalid(); +void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe) { + if (invalidateShapeKey) { + _shapeKey = ShapeKey::Builder::invalid(); + return; } - const FBXGeometry& geometry = model->getFBXGeometry(); - const auto& networkMeshes = model->getGeometry()->getMeshes(); - - // guard against partially loaded meshes - if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)model->_meshStates.size()) { - return ShapeKey::Builder::invalid(); - } - - const FBXMesh& mesh = geometry.meshes.at(_meshIndex); - - // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown - // to false to rebuild out mesh groups. - if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) { - model->_needsFixupInScene = true; // trigger remove/add cycle - model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - return ShapeKey::Builder::invalid(); - } - - - int vertexCount = mesh.vertices.size(); - if (vertexCount == 0) { - // sanity check - return ShapeKey::Builder::invalid(); // FIXME - } - - model::MaterialKey drawMaterialKey; if (_drawMaterial) { drawMaterialKey = _drawMaterial->getKey(); } bool isTranslucent = drawMaterialKey.isTranslucent(); - bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty(); + bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents; bool hasSpecular = drawMaterialKey.isMetallicMap(); bool hasLightmap = drawMaterialKey.isLightmapMap(); bool isUnlit = drawMaterialKey.isUnlit(); bool isSkinned = _isSkinned; - bool wireframe = model->isWireframe(); - if (wireframe) { + if (isWireframe) { isTranslucent = hasTangents = hasSpecular = hasLightmap = isSkinned = false; } @@ -500,10 +469,14 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { if (isSkinned) { builder.withSkinned(); } - if (wireframe) { + if (isWireframe) { builder.withWireframe(); } - return builder.build(); + _shapeKey = builder.build(); +} + +ShapeKey ModelMeshPartPayload::getShapeKey() const { + return _shapeKey; } void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { @@ -515,10 +488,9 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); batch.setInputFormat((_drawMesh->getVertexFormat())); - ModelPointer model = _model.lock(); - if (model) { - batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); + if (_blendedVertexBuffer) { + batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3)); + batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); } else { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); @@ -526,11 +498,6 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } } - - // TODO: Get rid of that extra call - if (!_hasColorAttrib) { - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - } } void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { @@ -544,31 +511,9 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline: void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); - ModelPointer model = _model.lock(); - if (!model || !model->isAddedToScene() || !model->isVisible()) { - return; // bail asap - } - - if (_state == WAITING_TO_START) { - if (model->isLoaded()) { - _state = STARTED; - model->setRenderItemsNeedUpdate(); - } else { - return; - } - } - - if (_materialNeedsUpdate && model->getGeometry()->areTexturesLoaded()) { - model->setRenderItemsNeedUpdate(); - _materialNeedsUpdate = false; - } - if (!args) { return; } - if (!getShapeKey().isValid()) { - return; - } gpu::Batch& batch = *(args->_batch); auto locations = args->_shapePipeline->locations; diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 971c6fe90b..fb55883101 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -96,32 +96,32 @@ public: render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) override; + void setKey(bool isVisible, bool isLayered); + void setLayer(bool isLayeredInFront, bool isLayeredInHUD); + void setShapeKey(bool invalidateShapeKey, bool isWireframe); + // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; - void initCache(); - void computeAdjustedLocalBound(const std::vector& clusterMatrices); gpu::BufferPointer _clusterBuffer; - ModelWeakPointer _model; int _meshIndex; int _shapeID; bool _isSkinned{ false }; bool _isBlendShaped { false }; - bool _materialNeedsUpdate { true }; + bool _hasTangents { false }; private: + void initCache(const ModelPointer& model); - enum State : uint8_t { - WAITING_TO_START = 0, - STARTED = 1, - }; - - mutable State _state { WAITING_TO_START } ; + gpu::BufferPointer _blendedVertexBuffer; + render::ItemKey _itemKey { render::ItemKey::Builder::opaqueShape().build() }; + render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; + int _layer { render::Item::LAYER_3D }; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c4bc435691..7717ceda6f 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -210,6 +210,24 @@ int Model::getRenderInfoTextureCount() { return _renderInfoTextureCount; } +bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { + if (!getGeometry()) { + return true; + } + + const FBXGeometry& geometry = getFBXGeometry(); + const auto& networkMeshes = getGeometry()->getMeshes(); + // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown + // to false to rebuild out mesh groups. + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size()) { + _needsFixupInScene = true; // trigger remove/add cycle + invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid + return true; + } + + return false; +} + void Model::updateRenderItems() { if (!_addedToScene) { return; @@ -237,6 +255,11 @@ void Model::updateRenderItems() { Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); + bool isWireframe = self->isWireframe(); + bool isVisible = self->isVisible(); + bool isLayeredInFront = self->isLayeredInFront(); + bool isLayeredInHUD = self->isLayeredInHUD(); + render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -244,13 +267,20 @@ void Model::updateRenderItems() { auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); - transaction.updateItem(itemID, [modelTransform, clusterMatrices](ModelMeshPartPayload& data) { + bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices, invalidatePayloadShapeKey, + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { data.updateClusterBuffer(clusterMatrices); Transform renderTransform = modelTransform; if (clusterMatrices.size() == 1) { renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + + data.setKey(isVisible, isLayeredInFront || isLayeredInHUD); + data.setLayer(isLayeredInFront, isLayeredInHUD); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); } @@ -272,16 +302,6 @@ void Model::setRenderItemsNeedUpdate() { emit requestRenderUpdate(); } -void Model::initJointTransforms() { - if (isLoaded()) { - glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig.setModelOffset(modelOffset); - } -} - -void Model::init() { -} - void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7568a17342..50ccb22131 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -122,7 +122,6 @@ public: void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } - void init(); void reset(); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); @@ -346,11 +345,7 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; - -protected: - virtual void deleteGeometry(); - void initJointTransforms(); QVector _blendshapeCoefficients; @@ -419,6 +414,8 @@ protected: bool _isLayeredInFront { false }; bool _isLayeredInHUD { false }; + bool shouldInvalidatePayloadShapeKey(int meshIndex); + private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 5c9abbabed..f860c0494e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -163,6 +163,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); + const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); + task.addJob("DrawHazeDeferred", drawHazeInputs); + // Render transparent objects forward in LightingBuffer const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); @@ -173,9 +176,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DebugLightClusters", debugLightClustersInputs); } - const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); - task.addJob("DrawHaze", drawHazeInputs); - const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); // Add bloom @@ -335,6 +335,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + // Setup haze iff curretn zone has haze + auto hazeStage = args->_scene->getStage(); + if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { + model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); + batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + } + // From the lighting model define a global shapKey ORED with individiual keys ShapeKey::Builder keyBuilder; if (lightingModel->isWireframeEnabled()) { diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index f51201d77d..e41b7edc77 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -16,7 +16,6 @@ #include #include "LightingModel.h" - class BeginGPURangeTimer { public: using JobModel = render::Job::ModelO; diff --git a/libraries/render-utils/src/forward_model_translucent.slf b/libraries/render-utils/src/forward_model_translucent.slf index 52e8ce50e7..906393db1f 100644 --- a/libraries/render-utils/src/forward_model_translucent.slf +++ b/libraries/render-utils/src/forward_model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 6cf99a68ef..38f162fdc3 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index c46b396ebc..9d5477304c 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -76,7 +76,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf index 748eea329c..b26e70f465 100644 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ b/libraries/render-utils/src/overlay3D_model_translucent.slf @@ -65,7 +65,7 @@ void main(void) { vec3 fragNormal; <$transformEyeToWorldDir(cam, _normal, fragNormal)$> - vec4 color = vec4(evalGlobalLightingAlphaBlended( + vec4 color = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index b9eb921e9d..b16b19c8b4 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -45,7 +45,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 20c7907bbe..ad260210a7 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -57,7 +57,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 762b7712d7..4254280fa1 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -87,6 +87,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); gpu::Shader::makeProgram(*program, slotBindings); @@ -107,6 +108,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeParametersBuffer"); ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index f0749504eb..101c060306 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -238,6 +238,7 @@ public: LIGHT, LIGHT_AMBIENT_BUFFER, FADE_PARAMETERS, + HAZE_MODEL }; enum MAP { @@ -270,6 +271,7 @@ public: int lightAmbientMapUnit; int fadeMaskTextureUnit; int fadeParameterBufferUnit; + int hazeParameterBufferUnit; }; using LocationsPointer = std::shared_ptr; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 6f2e7d8b19..b33b330fc0 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -30,9 +30,30 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" }; const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; #else - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; +#endif + return staticResourcePath; +} + +#ifdef DEV_BUILD +const QString& PathUtils::projectRootPath() { + static QString sourceFolder; + static std::once_flag once; + std::call_once(once, [&] { + QDir thisDir = QFileInfo(__FILE__).absoluteDir(); + sourceFolder = QDir::cleanPath(thisDir.absoluteFilePath("../../../")); + }); + return sourceFolder; +} +#endif + +const QString& PathUtils::qmlBasePath() { +#ifdef DEV_BUILD + static const QString staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/qml/").toString(); +#else + static const QString staticResourcePath = "qrc:///qml/"; #endif return staticResourcePath; diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index bc058e7820..2b4fe35d97 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -34,6 +34,10 @@ class PathUtils : public QObject, public Dependency { Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: static const QString& resourcesPath(); + static const QString& qmlBasePath(); +#ifdef DEV_BUILD + static const QString& projectRootPath(); +#endif static QString getAppDataPath(); static QString getAppLocalDataPath(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index dc6a877bb9..279fa42ea4 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -28,7 +28,7 @@ glm::vec3 getPosition() const override { return _thing->getPosition(); } float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } uint64_t getTimestamp() const override { return _thing->getLastTime(); } - const Thing& getThing() const { return _thing; } + Thing getThing() const { return _thing; } private: Thing _thing; }; @@ -43,6 +43,13 @@ (3) Loop over your priority queue and do timeboxed work: + NOTE: Be careful using references to members of instances of T from std::priority_queue. + Under the hood std::priority_queue may re-use instances of T. + For example, after a pop() or a push() the top T may have the same memory address + as the top T before the pop() or push() (but point to a swapped instance of T). + This causes a reference to member variable of T to point to a different value + when operations taken on std::priority_queue shuffle around the instances of T. + uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; while (!sortedThings.empty()) { const Thing& thing = sortedThings.top(); diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index d7feb65ff3..3f6a2dd643 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -31,6 +30,8 @@ #include "Gzip.h" #include "PortableHighResolutionClock.h" +#include "SharedLogging.h" +#include "shared/FileUtils.h" #include "shared/GlobalAppProperties.h" using namespace tracing; @@ -104,30 +105,13 @@ void TraceEvent::writeJson(QTextStream& out) const { #endif } -void Tracer::serialize(const QString& originalPath) { - - QString path = originalPath; - - // Filter for specific tokens potentially present in the path: - auto now = QDateTime::currentDateTime(); - - path = path.replace("{DATE}", now.date().toString("yyyyMMdd")); - path = path.replace("{TIME}", now.time().toString("HHmm")); - - // If the filename is relative, turn it into an absolute path relative to the document directory. - QFileInfo originalFileInfo(path); - if (originalFileInfo.isRelative()) { - QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - path = docsLocation + "/" + path; - QFileInfo info(path); - if (!info.absoluteDir().exists()) { - QString originalRelativePath = originalFileInfo.path(); - QDir(docsLocation).mkpath(originalRelativePath); - } +void Tracer::serialize(const QString& filename) { + QString fullPath = FileUtils::replaceDateTimeTokens(filename); + fullPath = FileUtils::computeDocumentPath(fullPath); + if (!FileUtils::canCreateFile(fullPath)) { + return; } - - std::list currentEvents; { std::lock_guard guard(_eventsMutex); @@ -137,11 +121,6 @@ void Tracer::serialize(const QString& originalPath) { } } - // If the file exists and we can't remove it, fail early - if (QFileInfo(path).exists() && !QFile::remove(path)) { - return; - } - // If we can't open a temp file for writing, fail early QByteArray data; { @@ -159,15 +138,16 @@ void Tracer::serialize(const QString& originalPath) { out << "\n]"; } - if (path.endsWith(".gz")) { + if (fullPath.endsWith(".gz")) { QByteArray compressed; gzip(data, compressed); data = compressed; - } - + } + { - QFile file(path); + QFile file(fullPath); if (!file.open(QIODevice::WriteOnly)) { + qDebug(shared) << "failed to open file '" << fullPath << "'"; return; } file.write(data); @@ -191,7 +171,6 @@ void Tracer::serialize(const QString& originalPath) { } } } }; - data = document.toJson(QJsonDocument::Compact); } #endif diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 8c962dfd6d..dba0af7b16 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -12,6 +12,7 @@ #include "FileUtils.h" +#include #include #include #include @@ -20,6 +21,8 @@ #include #include +#include "../SharedLogging.h" + QString FileUtils::readFile(const QString& filename) { QFile file(filename); @@ -82,20 +85,54 @@ QString FileUtils::standardPath(QString subfolder) { // standard path // Mac: ~/Library/Application Support/Interface QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - if (!subfolder.startsWith("/")) { subfolder.prepend("/"); } - if (!subfolder.endsWith("/")) { subfolder.append("/"); } - path.append(subfolder); QDir logDir(path); if (!logDir.exists(path)) { logDir.mkpath(path); } - return path; } + +QString FileUtils::replaceDateTimeTokens(const QString& originalPath) { + // Filter for specific tokens potentially present in the path: + auto now = QDateTime::currentDateTime(); + QString path = originalPath; + path.replace("{DATE}", now.date().toString("yyyyMMdd")); + path.replace("{TIME}", now.time().toString("HHmm")); + return path; +} + + +QString FileUtils::computeDocumentPath(const QString& originalPath) { + // If the filename is relative, turn it into an absolute path relative to the document directory. + QString path = originalPath; + QFileInfo originalFileInfo(originalPath); + if (originalFileInfo.isRelative()) { + QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + path = docsLocation + "/" + originalPath; + } + return path; +} + +bool FileUtils::canCreateFile(const QString& fullPath) { + // If the file exists and we can't remove it, fail early + QFileInfo fileInfo(fullPath); + if (fileInfo.exists() && !QFile::remove(fullPath)) { + qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; + return false; + } + QDir dir(fileInfo.absolutePath()); + if (!dir.exists()) { + if (!dir.mkpath(fullPath)) { + qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + return false; + } + } + return true; +} diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h index 4f2c1b7af5..d68fcd8a44 100644 --- a/libraries/shared/src/shared/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -21,6 +21,9 @@ public: static QString standardPath(QString subfolder); static QString readFile(const QString& filename); static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts); + static QString replaceDateTimeTokens(const QString& path); + static QString computeDocumentPath(const QString& path); + static bool canCreateFile(const QString& fullPath); }; #endif // hifi_FileUtils_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 16a62cea4b..905a224ef4 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -134,9 +134,6 @@ void OffscreenUi::create() { myContext->setContextProperty("OffscreenUi", this); myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); myContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); - auto tabletScriptingInterface = DependencyManager::get(); - TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); - myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 14d8ec8985..90b91c5ec2 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -61,8 +61,9 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { } QUrl url { properties[SOURCE_PROPERTY].toString() }; + // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" && - url.scheme() != "atp") { + url.scheme() != "atp" && url.scheme() != "qrc") { properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString(); } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 8f696275ba..902f91f9b8 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -52,6 +52,8 @@ #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" +#include "TabletScriptingInterface.h" +#include "ToolbarScriptingInterface.h" #include "Logging.h" Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") @@ -66,7 +68,10 @@ public: void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback) { withWriteLock([&] { - for (const auto& url : urls) { + for (auto url : urls) { + if (url.isRelative()) { + url = QUrl(PathUtils::qmlBasePath() + url.toString()); + } _callbacks[url].push_back(callback); } }); @@ -103,7 +108,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list } -QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {}; +QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; struct TextureSet { // The number of surfaces with this size @@ -450,6 +455,15 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); #endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); + static std::once_flag once; + std::call_once(once, [&] { + qRegisterMetaType(); + qRegisterMetaType(); + }); + rootContext->setContextProperty("Tablet", DependencyManager::get().data()); + rootContext->setContextProperty("Toolbars", DependencyManager::get().data()); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + engine->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QQmlEngine* acquireEngine(QQuickWindow* window) { @@ -668,10 +682,11 @@ void OffscreenQmlSurface::create() { auto qmlEngine = acquireEngine(_quickWindow); _qmlContext = new QQmlContext(qmlEngine->rootContext()); - + _qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBasePath() }); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("webEntity", this); + _qmlContext->setContextProperty("QmlSurface", this); // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated @@ -818,55 +833,70 @@ QQuickItem* OffscreenQmlSurface::getRootItem() { return _rootItem; } -void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { - _qmlContext->setBaseUrl(baseUrl); +QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { + // Get any whitelist functionality + QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); + // If we have whitelisted content, we must load a new context + forceNewContext |= !callbacks.empty(); + + QQmlContext* targetContext = parent ? QQmlEngine::contextForObject(parent) : _qmlContext; + if (!targetContext) { + targetContext = _qmlContext; + } + if (_rootItem && forceNewContext) { + targetContext = new QQmlContext(targetContext); + } + + for (const auto& callback : callbacks) { + callback(targetContext); + } + + return targetContext; } -void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { + QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); + }); +} + +void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) { + loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback); +} + +void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) { if (QThread::currentThread() != thread()) { qCWarning(uiLogging) << "Called load on a non-surface thread"; } // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); - // Get any whitelist functionality - QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); - // If we have whitelisted content, we must load a new context - createNewContext |= !callbacks.empty(); - callbacks.push_back(onQmlLoadedCallback); - - QQmlContext* targetContext = _qmlContext; - if (_rootItem && createNewContext) { - targetContext = new QQmlContext(targetContext); - } - - - // FIXME eliminate loading of relative file paths for QML QUrl finalQmlSource = qmlSource; if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { finalQmlSource = _qmlContext->resolvedUrl(qmlSource); } + auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); if (qmlComponent->isLoading()) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); }); return; } - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); } -void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, true, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, false, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) { return load(QUrl(qmlSourceFile), onQmlLoadedCallback); } @@ -874,7 +904,8 @@ void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks) { + +void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { @@ -896,6 +927,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext return; } + if (!newObject) { + if (!_rootItem) { + qFatal("Could not load object as root item"); + return; + } + qCWarning(uiLogging) << "Unable to load QML item"; + return; + } + + QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); + if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { + // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper + // Find a way to flag older scripts using this mechanism and wanr that this is deprecated + qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + } + qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); // All quick items should be focusable @@ -906,41 +953,30 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext newItem->setFlag(QQuickItem::ItemIsFocusScope, true); } + // Make sure we will call callback for this codepath // Call this before qmlComponent->completeCreate() otherwise ghost window appears - if (newItem && _rootItem) { - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); - } - } + // If we already have a root, just set a couple of flags and the ancestry + if (_rootItem) { + callback(qmlContext, newItem); - QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); - if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { - // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper - // Find a way to flag older scripts using this mechanism and wanr that this is deprecated - qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + if (!parent) { + parent = _rootItem; + } + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); + newObject->setParent(parent); + newItem->setParentItem(parent); } qmlComponent->completeCreate(); qmlComponent->deleteLater(); - // If we already have a root, just set a couple of flags and the ancestry - if (newItem && _rootItem) { - // Allow child windows to be destroyed from JS - QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); - newObject->setParent(_rootItem); - if (newItem) { - newItem->setParentItem(_rootItem); - } + if (_rootItem) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); return; } - if (!newItem) { - qFatal("Could not load object as root item"); - return; - } - connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); // The root item is ready. Associate it with the window. @@ -948,11 +984,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext _rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setSize(_quickWindow->renderTargetSize()); - // Call this callback after rootitem is set, otherwise VrMenu wont work - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); + if (_rootItem->objectName() == "tabletRoot") { + _qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem)); + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this); + QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); + _qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); + // Call this callback after rootitem is set, otherwise VrMenu wont work + callback(qmlContext, newItem); } void OffscreenQmlSurface::updateQuick() { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 03f7475f61..4251bb6a9d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -33,12 +33,14 @@ class QQmlContext; class QQmlComponent; class QQuickWindow; class QQuickItem; +class QJSValue; // GPU resources are typically buffered for one copy being used by the renderer, // one copy in flight, and one copy being used by the receiver #define GPU_RESOURCE_BUFFER_SIZE 3 -using QmlContextCallback = std::function; +using QmlContextCallback = std::function; +using QmlContextObjectCallback = std::function; class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis { Q_OBJECT @@ -46,7 +48,7 @@ class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -59,10 +61,15 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + // Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... }) + Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); + + // For C++ use + Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + void clearCache(); void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling @@ -77,7 +84,6 @@ public: bool isPaused() const; bool getCleaned() { return _isCleaned; } - void setBaseUrl(const QUrl& baseUrl); QQuickItem* getRootItem(); QQuickWindow* getWindow(); QObject* getEventHandler(); @@ -142,13 +148,13 @@ protected: private: static QOpenGLContext* getSharedContext(); - void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks); + QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false); + void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); + void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); - void setupFbo(); bool allowNewFrame(uint8_t fps); void render(); void cleanup(); - QJsonObject getGLContextData(); void disconnectAudioOutputTimer(); private slots: diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 2a0ca4a2e9..9b6b031dd9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { auto surface = QSharedPointer(new OffscreenQmlSurface()); surface->create(); - surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); surface->load(rootSource); surface->resize(QSize(100, 100)); return surface; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index dd26dd7be7..0e793fef21 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -32,6 +32,14 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +static QString getUsername() { + QString username = "Unknown user"; + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + username = accountManager->getAccountInfo().getUsername(); + } + return username; +} static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav", "/sounds/Button04.wav", @@ -39,6 +47,51 @@ static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStr "/sounds/Tab01.wav", "/sounds/Tab02.wav" }); +TabletButtonListModel::TabletButtonListModel() { + +} + +TabletButtonListModel::~TabletButtonListModel() { + +} + +enum ButtonDeviceRole { + ButtonProxyRole = Qt::UserRole, +}; + +QHash TabletButtonListModel::_roles{ + { ButtonProxyRole, "buttonProxy" }, +}; + +Qt::ItemFlags TabletButtonListModel::_flags{ Qt::ItemIsSelectable | Qt::ItemIsEnabled }; + +QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= rowCount() || role != ButtonProxyRole) { + return QVariant(); + } + + return QVariant::fromValue(_buttons.at(index.row()).data()); +} + +TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { + auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + beginResetModel(); + _buttons.push_back(tabletButtonProxy); + endResetModel(); + return tabletButtonProxy.data(); +} + +void TabletButtonListModel::removeButton(TabletButtonProxy* button) { + auto itr = std::find(_buttons.begin(), _buttons.end(), button); + if (itr == _buttons.end()) { + qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << button; + return; + } + beginResetModel(); + _buttons.erase(itr); + endResetModel(); +} + TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); } @@ -210,9 +263,9 @@ QObject* TabletScriptingInterface::getFlags() { // TabletProxy // -static const char* TABLET_SOURCE_URL = "Tablet.qml"; -static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; -static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; +static const char* TABLET_HOME_SOURCE_URL = "hifi/tablet/TabletHome.qml"; +static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml"; +static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml"; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } @@ -247,9 +300,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { auto offscreenUi = DependencyManager::get(); if (toolbarMode) { - removeButtonsFromHomeScreen(); - addButtonsToToolbar(); - // create new desktop window auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); @@ -264,11 +314,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); } else { - removeButtonsFromToolbar(); - - if (_currentPathLoaded == TABLET_SOURCE_URL) { - addButtonsToHomeScreen(); - } else { + if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { loadHomeScreen(true); } //check if running scripts window opened and save it for reopen in Tablet @@ -282,40 +328,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } -} -static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (buttonProxy == NULL){ - qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL"; - return; - } - - QVariant resultVar; - bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); - if (!hasResult) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result"; - return; - } - - QObject* qmlButton = qvariant_cast(resultVar); - if (!qmlButton) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject"; - return; - } - QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); - buttonProxy->setQmlButton(qobject_cast(qmlButton)); -} - -static QString getUsername() { - QString username = "Unknown user"; - auto accountManager = DependencyManager::get(); - if (accountManager->isLoggedIn()) { - return accountManager->getAccountInfo().getUsername(); - } else { - return "Unknown user"; - } + emit toolbarModeChanged(); } void TabletProxy::initialScreen(const QVariant& url) { @@ -363,7 +377,7 @@ void TabletProxy::onTabletShown() { static_cast(parent())->playSound(TabletScriptingInterface::TabletOpen); if (_showRunningScripts) { _showRunningScripts = false; - pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml"); + pushOntoStack("hifi/dialogs/TabletRunningScripts.qml"); } } } @@ -397,10 +411,7 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { }); if (_toolbarMode) { - // if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet. - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); } // force to the tablet to go to the homescreen @@ -436,7 +447,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } else { - removeButtonsFromHomeScreen(); _state = State::Uninitialized; emit screenChanged(QVariant("Closed"), QVariant("")); _currentPathLoaded = ""; @@ -465,7 +475,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { } if (root) { - removeButtonsFromHomeScreen(); auto offscreenUi = DependencyManager::get(); QObject* menu = offscreenUi->getRootMenu(); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); @@ -539,7 +548,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } if (root) { - removeButtonsFromHomeScreen(); //works only in Tablet QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; if (path != _currentPathLoaded) { @@ -621,9 +629,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) { if (!_toolbarMode && _qmlTabletRoot) { - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); } else if (_toolbarMode && _desktopWindow) { // close desktop window @@ -632,8 +638,8 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { } } _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); - _currentPathLoaded = TABLET_SOURCE_URL; + emit screenChanged(QVariant("Home"), QVariant(TABLET_HOME_SOURCE_URL)); + _currentPathLoaded = TABLET_HOME_SOURCE_URL; } } @@ -683,17 +689,18 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS } if (root) { - removeButtonsFromHomeScreen(); if (loadOtherBase) { - QMetaObject::invokeMethod(root, "loadTabletWebBase"); + QMetaObject::invokeMethod(root, "loadTabletWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } else { - QMetaObject::invokeMethod(root, "loadWebBase"); + QMetaObject::invokeMethod(root, "loadWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } - QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); + _currentPathLoaded = QVariant(url); } else { // tablet is not initialized yet, save information and load when // the tablet root is set @@ -702,10 +709,8 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS _initialWebPathParams.first = injectedJavaScriptUrl; _initialWebPathParams.second = loadOtherBase; _initialScreen = true; + } - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - _currentPathLoaded = QVariant(url); } TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { @@ -715,24 +720,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { return result; } - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - _tabletButtonProxies.push_back(tabletButtonProxy); - if (!_toolbarMode && _qmlTabletRoot) { - auto tablet = getQmlTablet(); - if (tablet) { - addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data()); - } else { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - if (toolbarProxy) { - // copy properties from tablet button proxy to toolbar button proxy. - auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties()); - tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); - } - } - return tabletButtonProxy.data(); + return _buttons.addButton(properties); } bool TabletProxy::onHomeScreen() { @@ -751,35 +739,7 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { return; } - auto tablet = getQmlTablet(); - if (!tablet) { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - - QSharedPointer buttonProxy; - { - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter == _tabletButtonProxies.end()) { - qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; - return; - } - buttonProxy = *iter; - _tabletButtonProxies.erase(iter); - } - - if (!_toolbarMode && _qmlTabletRoot) { - buttonProxy->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - // remove button from toolbarProxy - if (toolbarProxy) { - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } - } + _buttons.removeButton(tabletButtonProxy); } void TabletProxy::emitScriptEvent(const QVariant& msg) { @@ -808,57 +768,16 @@ void TabletProxy::sendToQml(const QVariant& msg) { } } -void TabletProxy::addButtonsToHomeScreen() { - auto tablet = getQmlTablet(); - if (!tablet || _toolbarMode) { - return; - } - for (auto& buttonProxy : _tabletButtonProxies) { - addButtonProxyToQmlTablet(tablet, buttonProxy.data()); - } - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); -} OffscreenQmlSurface* TabletProxy::getTabletSurface() { return _qmlOffscreenSurface; } -void TabletProxy::removeButtonsFromHomeScreen() { - auto tablet = getQmlTablet(); - for (auto& buttonProxy : _tabletButtonProxies) { - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - buttonProxy->setQmlButton(nullptr); - } -} void TabletProxy::desktopWindowClosed() { gotoHomeScreen(); } -void TabletProxy::addButtonsToToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // copy properties from tablet button proxy to toolbar button proxy. - buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties())); - } - - // make the toolbar visible - toolbarProxy->writeProperty("visible", QVariant(true)); -} - -void TabletProxy::removeButtonsFromToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // remove button from toolbarProxy - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } -} QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { @@ -928,25 +847,6 @@ TabletButtonProxy::~TabletButtonProxy() { } } -void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (_qmlButton) { - QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr); - } - _qmlButton = qmlButton; - if (_qmlButton) { - QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; }); - } -} - -void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { - Q_ASSERT(QThread::currentThread() == thread()); - _toolbarButtonProxy = toolbarButtonProxy; - if (_toolbarButtonProxy) { - QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); - } -} - QVariantMap TabletButtonProxy::getProperties() { if (QThread::currentThread() != thread()) { QVariantMap result; @@ -963,20 +863,19 @@ void TabletButtonProxy::editProperties(const QVariantMap& properties) { return; } + bool changed = false; QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { const auto& key = iter.key(); const auto& value = iter.value(); if (!_properties.contains(key) || _properties[key] != value) { - _properties[iter.key()] = iter.value(); - if (_qmlButton) { - QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value())); - } + _properties[key] = value; + changed = true; } ++iter; } - if (_toolbarButtonProxy) { - QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties)); + if (changed) { + emit propertiesChanged(); } } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index ad6a7c8001..d24b3b6947 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -12,13 +12,16 @@ #include #include -#include -#include +#include +#include +#include +#include + #include -#include -#include -#include -#include +#include +#include + +#include #include #include @@ -90,6 +93,31 @@ protected: bool _toolbarMode { false }; }; +class TabletButtonListModel : public QAbstractListModel { + Q_OBJECT + +public: + TabletButtonListModel(); + ~TabletButtonListModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override { Q_UNUSED(parent); return (int)_buttons.size(); } + QHash roleNames() const override { return _roles; } + Qt::ItemFlags flags(const QModelIndex& index) const override { return _flags; } + QVariant data(const QModelIndex& index, int role) const override; + + +protected: + friend class TabletProxy; + TabletButtonProxy* addButton(const QVariant& properties); + void removeButton(TabletButtonProxy* button); + using List = std::list>; + static QHash _roles; + static Qt::ItemFlags _flags; + std::vector> _buttons; +}; + +Q_DECLARE_METATYPE(TabletButtonListModel*); + /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet @@ -99,9 +127,10 @@ protected: class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) - Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode NOTIFY toolbarModeChanged) Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape) Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged) + Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT) public: TabletProxy(QObject* parent, const QString& name); ~TabletProxy(); @@ -204,6 +233,7 @@ public: QQuickItem* getQmlMenu() const; + TabletButtonListModel* getButtons() { return &_buttons; } signals: /**jsdoc * Signaled when this tablet receives an event from the html/js embedded in the tablet @@ -236,21 +266,19 @@ signals: */ void tabletShownChanged(); + void toolbarModeChanged(); + protected slots: - void addButtonsToHomeScreen(); void desktopWindowClosed(); void emitWebEvent(const QVariant& msg); void onTabletShown(); + protected: - void removeButtonsFromHomeScreen(); void loadHomeScreen(bool forceOntoHomeScreen); - void addButtonsToToolbar(); - void removeButtonsFromToolbar(); bool _initialScreen { false }; QVariant _currentPathLoaded { "" }; QString _name; - std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; OffscreenQmlSurface* _qmlOffscreenSurface { nullptr }; QmlWindowClass* _desktopWindow { nullptr }; @@ -263,6 +291,8 @@ protected: std::pair _initialWebPathParams; bool _landscape { false }; bool _showRunningScripts { false }; + + TabletButtonListModel _buttons; }; Q_DECLARE_METATYPE(TabletProxy*); @@ -274,13 +304,11 @@ Q_DECLARE_METATYPE(TabletProxy*); class TabletButtonProxy : public QObject { Q_OBJECT Q_PROPERTY(QUuid uuid READ getUuid) + Q_PROPERTY(QVariantMap properties READ getProperties NOTIFY propertiesChanged) public: TabletButtonProxy(const QVariantMap& properties); ~TabletButtonProxy(); - void setQmlButton(QQuickItem* qmlButton); - void setToolbarButtonProxy(QObject* toolbarButtonProxy); - QUuid getUuid() const { return _uuid; } /**jsdoc @@ -297,9 +325,6 @@ public: */ Q_INVOKABLE void editProperties(const QVariantMap& properties); -public slots: - void clickedSlot() { emit clicked(); } - signals: /**jsdoc * Signaled when this button has been clicked on by the user. @@ -307,12 +332,11 @@ signals: * @returns {Signal} */ void clicked(); + void propertiesChanged(); protected: QUuid _uuid; int _stableOrder; - QQuickItem* _qmlButton { nullptr }; - QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 7fdebada79..049ccbc03d 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -234,7 +234,7 @@ Recorder.setUp(); - // Tablet/toolbar button. + // tablet/toolbar button. button = tablet.addButton({ icon: APP_ICON_INACTIVE, activeIcon: APP_ICON_ACTIVE, diff --git a/scripts/developer/inputRecording.js b/scripts/developer/inputRecording.js index 85bda623b3..6fb8e471cd 100644 --- a/scripts/developer/inputRecording.js +++ b/scripts/developer/inputRecording.js @@ -19,7 +19,7 @@ tablet.gotoHomeScreen(); onRecordingScreen = false; } else { - tablet.loadQMLSource("InputRecorder.qml"); + tablet.loadQMLSource("hifi/tablet/InputRecorder.qml"); onRecordingScreen = true; } } diff --git a/scripts/developer/tests/qmlTest.js b/scripts/developer/tests/qmlTest.js index c891b6a1b7..0eaabac6d1 100644 --- a/scripts/developer/tests/qmlTest.js +++ b/scripts/developer/tests/qmlTest.js @@ -1,7 +1,7 @@ print("Launching web window"); qmlWindow = new OverlayWindow({ title: 'Test Qml', - source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", + source: "qrc:///qml/OverlayWindowTest.qml", height: 240, width: 320, toolWindow: false, diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 0a3471fa81..a93177ca38 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -15,7 +15,7 @@ var TABLET_BUTTON_NAME = "AUDIO"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; -var AUDIO_QML_SOURCE = "../audio/Audio.qml"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; var MUTE_ICONS = { icon: "icons/tablet-icons/mic-mute-i.svg", diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index a4cdc5b097..cde44e78c9 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -26,8 +26,8 @@ // Relevant Variables: // -WALLET_QML_SOURCE: The path to the Wallet QML // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; + var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var onWalletScreen = false; function onButtonClicked() { if (!tablet) { @@ -61,10 +61,26 @@ function fromQml(message) { switch (message.method) { case 'passphrasePopup_cancelClicked': - case 'walletSetup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoHomeScreen(); break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + tablet.gotoHomeScreen(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: // User needs to return to an individual marketplace item URL + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + break; + } + break; case 'needsLogIn_loginClicked': openLoginWindow(); break; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 8a7e36465d..e28f877d85 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -427,7 +427,7 @@ var toolBar = (function () { }); createButton = activeButton; tablet.screenChanged.connect(function (type, url) { - if (isActive && (type !== "QML" || url !== "Edit.qml")) { + if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) { that.setActive(false) } }); @@ -656,7 +656,7 @@ var toolBar = (function () { selectionDisplay.triggerMapping.disable(); tablet.landscape = false; } else { - tablet.loadQMLSource("Edit.qml", true); + tablet.loadQMLSource("hifi/tablet/Edit.qml", true); UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); gridTool.setVisible(true); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 7d97f13757..082528ffc5 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -18,7 +18,7 @@ var buttonName = "Settings"; var toolBar = null; var tablet = null; - var settings = "TabletGeneralPreferences.qml" + var settings = "hifi/tablet/TabletGeneralPreferences.qml" function onClicked(){ if (tablet) { tablet.loadQMLSource(settings); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index b361f1120b..2b016884f1 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -199,7 +199,7 @@ var purchasesElement = document.createElement('a'); var dropDownElement = document.getElementById('user-dropdown'); - $('#user-dropdown').find('.username')[0].style = "max-width:80px;white-space:nowrap;overflow:hidden;" + + $('#user-dropdown').find('.username')[0].style = "max-width:80px;white-space:nowrap;overflow:hidden;" + "text-overflow:ellipsis;display:inline-block;position:relative;top:4px;"; $('#user-dropdown').find('.caret')[0].style = "position:relative;top:-3px;"; @@ -243,13 +243,15 @@ }); } - function buyButtonClicked(id, name, author, price, href) { + function buyButtonClicked(id, name, author, price, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, itemName: name, itemPrice: price ? parseInt(price, 10) : 0, - itemHref: href + itemHref: href, + referrer: referrer, + itemAuthor: author })); } @@ -316,7 +318,8 @@ $(this).closest('.grid-item').find('.item-title').text(), $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), - $(this).attr('data-href')); + $(this).attr('data-href'), + "mainPage"); }); } @@ -389,27 +392,41 @@ var href = purchaseButton.attr('href'); purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + var availability = $.trim($('.item-availability').text()); + if (availability === 'available') { + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + } else { + purchaseButton.css({ + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "padding-bottom": "10px" + }); + } var cost = $('.item-cost').text(); - - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + if (availability !== 'available') { + purchaseButton.html('UNAVAILABLE (' + availability + ')'); + } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE ' + cost); } purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); + if ('available' === availability) { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href, + "itemPage"); + } + }); maybeAddPurchasesButton(); } } diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js index 6df0aa3a87..69092b30fc 100644 --- a/scripts/system/libraries/accountUtils.js +++ b/scripts/system/libraries/accountUtils.js @@ -10,7 +10,7 @@ openLoginWindow = function openLoginWindow() { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Menu.triggerOption("Login / Sign Up"); } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + tablet.loadQMLOnTop("dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } }; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index fedadbb2b4..d9003ffeaa 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -898,8 +898,7 @@ } } function keyReleaseEvent(event) { - if ((event.text.toUpperCase() === "X") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl - && !event.isAlt) { + if (event.text.toUpperCase() === "X" && !event.isAutoRepeat) { updateTriggers(0.0, true, Controller.Standard.RightHand); } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 004375bff7..15e72c8e8a 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -23,10 +23,9 @@ var selectionDisplay = null; // for gridTool.js to ignore var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; - var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; + var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -114,7 +113,7 @@ var selectionDisplay = null; // for gridTool.js to ignore function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); @@ -437,7 +436,7 @@ var selectionDisplay = null; // for gridTool.js to ignore wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', - referrer: message.itemId + referrer: message.referrer === "itemPage" ? message.itemId : message.referrer }); openWallet(); break; @@ -492,7 +491,7 @@ var selectionDisplay = null; // for gridTool.js to ignore Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'purchases_openGoTo': - tablet.loadQMLSource("TabletAddressDialog.qml"); + tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': setCertificateInfo("", message.itemCertificateId); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b5551cf596..ed7059f9f3 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,7 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now -var PAL_QML_SOURCE = "../Pal.qml"; +var PAL_QML_SOURCE = "hifi/Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 929c6e0e5c..2a0e827932 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -24,7 +24,7 @@ print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); } - var gotoQmlSource = "TabletAddressDialog.qml"; + var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml"; var buttonName = "GOTO"; var onGotoScreen = false; var shouldActivateButton = false; diff --git a/tests/gpu-test/src/TestShapes.cpp b/tests/gpu-test/src/TestShapes.cpp index 253d89cf61..67a348c002 100644 --- a/tests/gpu-test/src/TestShapes.cpp +++ b/tests/gpu-test/src/TestShapes.cpp @@ -29,19 +29,18 @@ void TestShapes::renderTest(size_t testId, RenderArgs* args) { float seconds = secTimestampNow() - startSecs; seconds /= 4.0f; batch.setModelTransform(Transform()); - batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + const auto color = glm::vec4(0.8f, 0.25f, 0.25f, 1.0f); bool wire = (seconds - floorf(seconds) > 0.5f); int shapeIndex = ((int)seconds) % TYPE_COUNT; if (wire) { - geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + geometryCache->renderWireShape(batch, SHAPE[shapeIndex], color); } else { - geometryCache->renderShape(batch, SHAPE[shapeIndex]); + geometryCache->renderShape(batch, SHAPE[shapeIndex], color); } batch.setModelTransform(Transform().setScale(1.01f)); - batch._glColor4f(1, 1, 1, 1); - geometryCache->renderWireCube(batch); + geometryCache->renderWireCube(batch, glm::vec4(1,1,1,1)); } diff --git a/tests/shared/src/PathUtilsTests.cpp b/tests/shared/src/PathUtilsTests.cpp new file mode 100644 index 0000000000..1c445908f7 --- /dev/null +++ b/tests/shared/src/PathUtilsTests.cpp @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "PathUtilsTests.h" + +#include + +#include + +QTEST_MAIN(PathUtilsTests) + +void PathUtilsTests::testPathUtils() { + QString result = PathUtils::qmlBasePath(); +#if DEV_BUILD + QVERIFY(result.startsWith("file:///")); +#else + QVERIFY(result.startsWith("qrc:///")); +#endif + QVERIFY(result.endsWith("/")); +} + diff --git a/tests/shared/src/PathUtilsTests.h b/tests/shared/src/PathUtilsTests.h new file mode 100644 index 0000000000..8ae410d6cb --- /dev/null +++ b/tests/shared/src/PathUtilsTests.h @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PathUtilsTests_h +#define hifi_PathUtilsTests_h + +#include + +class PathUtilsTests : public QObject { + Q_OBJECT +private slots: + void testPathUtils(); +}; + +#endif // hifi_PathUtilsTests_h diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index e7a135ec9e..7bc65722cd 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -24,7 +24,7 @@ if (onSkyboxChangerScreen) { tablet.gotoHomeScreen(); } else { - tablet.loadQMLSource("../SkyboxChanger.qml"); + tablet.loadQMLSource("hifi/SkyboxChanger.qml"); } } diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index 9eb543e768..730a5808b8 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -393,7 +393,7 @@ // Relevant Variables: // -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML // -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app. - var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml"; + var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml"; var onSpectatorCameraScreen = false; function onTabletButtonClicked() { if (!tablet) {