diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 570db05871..72e9751294 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -368,11 +368,11 @@ void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); - unsigned short domainServerPort = (unsigned short) localPortValue.toUInt(); + int domainServerPort = localPortValue.toInt(); QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - unsigned short domainServerDTLSPort = 0; + int domainServerDTLSPort = INVALID_PORT; if (_isUsingDTLS) { domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js new file mode 100644 index 0000000000..027d6fe8db --- /dev/null +++ b/interface/resources/html/createGlobalEventBridge.js @@ -0,0 +1,43 @@ +// +// createGlobalEventBridge.js +// +// Created by Anthony J. Thibault on 9/7/2016 +// Copyright 2016 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 +// + +// Stick a EventBridge object in the global namespace. +var EventBridge; +(function () { + // the TempEventBridge class queues up emitWebEvent messages and executes them when the real EventBridge is ready. + // Similarly, it holds all scriptEventReceived callbacks, and hooks them up to the real EventBridge. + function TempEventBridge() { + var self = this; + this._callbacks = []; + this._messages = []; + this.scriptEventReceived = { + connect: function (callback) { + self._callbacks.push(callback); + } + }; + this.emitWebEvent = function (message) { + self._messages.push(message); + }; + }; + + EventBridge = new TempEventBridge(); + + var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + // replace the TempEventBridge with the real one. + var tempEventBridge = EventBridge; + EventBridge = channel.objects.eventBridgeWrapper.eventBridge; + tempEventBridge._callbacks.forEach(function (callback) { + EventBridge.scriptEventReceived.connect(callback); + }); + tempEventBridge._messages.forEach(function (message) { + EventBridge.emitWebEvent(message); + }); + }); +})(); diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js new file mode 100644 index 0000000000..723767790a --- /dev/null +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -0,0 +1,41 @@ +// +// Created by Anthony Thibault on 2016-09-02 +// Copyright 2016 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 +// +// Sends messages over the EventBridge when text input is required. +// +(function () { + var POLL_FREQUENCY = 500; // ms + var MAX_WARNINGS = 3; + var numWarnings = 0; + + function shouldRaiseKeyboard() { + if (document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA") { + return true; + } else { + // check for contenteditable attribute + for (var i = 0; i < document.activeElement.attributes.length; i++) { + if (document.activeElement.attributes[i].name === "contenteditable" && + document.activeElement.attributes[i].value === "true") { + return true; + } + } + return false; + } + }; + + setInterval(function () { + var event = shouldRaiseKeyboard() ? "_RAISE_KEYBOARD" : "_LOWER_KEYBOARD"; + if (typeof EventBridge != "undefined") { + EventBridge.emitWebEvent(event); + } else { + if (numWarnings < MAX_WARNINGS) { + console.log("WARNING: no global EventBridge object found"); + numWarnings++; + } + } + }, POLL_FREQUENCY); +})(); diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 62226859b6..631036580e 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 -import QtWebEngine 1.1 +import QtWebEngine 1.2 import "controls-uit" import "styles" as HifiStyles @@ -223,6 +223,9 @@ ScrollingWindow { var newWindow = component.createObject(desktop); request.openIn(newWindow.webView) } + onWindowCloseRequested: { + root.destroy(); + } Component.onCompleted: { desktop.initWebviewProfileHandlers(webview.profile) diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index faf7f746a2..cefaf653fc 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -15,7 +15,7 @@ WebEngineView { id: root property var newUrl; - profile.httpUserAgent: "Mozilla/5.0 Chrome/38.0 (HighFidelityInterface)" + profile: desktop.browserProfile Component.onCompleted: { console.log("Connecting JS messaging to Hifi Logging") @@ -60,9 +60,4 @@ WebEngineView { } } } - - - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile } diff --git a/interface/resources/qml/controls/Key.qml b/interface/resources/qml/controls/Key.qml new file mode 100644 index 0000000000..2218474936 --- /dev/null +++ b/interface/resources/qml/controls/Key.qml @@ -0,0 +1,164 @@ +import QtQuick 2.0 + +Item { + id: keyItem + width: 45 + height: 50 + property string glyph: "a" + property bool toggle: false // does this button have the toggle behaivor? + property bool toggled: false // is this button currently toggled? + property alias mouseArea: mouseArea1 + + function resetToggledMode(mode) { + toggled = mode; + if (toggled) { + state = "mouseDepressed"; + } else { + state = ""; + } + } + + MouseArea { + id: mouseArea1 + width: 36 + anchors.fill: parent + hoverEnabled: true + + onCanceled: { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + + onClicked: { + mouse.accepted = true; + webEntity.synthesizeKeyPress(glyph); + if (toggle) { + toggled = !toggled; + } + } + + onDoubleClicked: { + mouse.accepted = true; + } + + onEntered: { + keyItem.state = "mouseOver"; + } + + onExited: { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + + onPressed: { + keyItem.state = "mouseClicked"; + mouse.accepted = true; + } + + onReleased: { + if (containsMouse) { + keyItem.state = "mouseOver"; + } else { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + mouse.accepted = true; + } + } + + Rectangle { + id: roundedRect + width: 30 + color: "#121212" + radius: 2 + border.color: "#00000000" + anchors.right: parent.right + anchors.rightMargin: 4 + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + anchors.top: parent.top + anchors.topMargin: 4 + } + + Text { + id: letter + y: 6 + width: 50 + color: "#ffffff" + text: glyph + style: Text.Normal + font.family: "Tahoma" + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 8 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 28 + } + + states: [ + State { + name: "mouseOver" + + PropertyChanges { + target: roundedRect + color: "#121212" + radius: 3 + border.width: 2 + border.color: "#00b4ef" + } + + PropertyChanges { + target: letter + color: "#00b4ef" + style: Text.Normal + } + }, + State { + name: "mouseClicked" + PropertyChanges { + target: roundedRect + color: "#1080b8" + border.width: 2 + border.color: "#00b4ef" + } + + PropertyChanges { + target: letter + color: "#121212" + styleColor: "#00000000" + style: Text.Normal + } + }, + State { + name: "mouseDepressed" + PropertyChanges { + target: roundedRect + color: "#0578b1" + border.width: 0 + } + + PropertyChanges { + target: letter + color: "#121212" + styleColor: "#00000000" + style: Text.Normal + } + } + ] +} diff --git a/interface/resources/qml/controls/Keyboard.qml b/interface/resources/qml/controls/Keyboard.qml new file mode 100644 index 0000000000..eb34740402 --- /dev/null +++ b/interface/resources/qml/controls/Keyboard.qml @@ -0,0 +1,390 @@ +import QtQuick 2.0 + +Item { + id: keyboardBase + height: 200 + property alias shiftKey: key27 + property bool shiftMode: false + + function resetShiftMode(mode) { + shiftMode = mode; + shiftKey.resetToggledMode(mode); + } + + function toUpper(str) { + if (str === ",") { + return "<"; + } else if (str === ".") { + return ">"; + } else if (str === "/") { + return "?"; + } else { + return str.toUpperCase(str); + } + } + + function toLower(str) { + if (str === "<") { + return ","; + } else if (str === ">") { + return "."; + } else if (str === "?") { + return "/"; + } else { + return str.toLowerCase(str); + } + } + + function forEachKey(func) { + var i, j; + for (i = 0; i < column1.children.length; i++) { + var row = column1.children[i]; + for (j = 0; j < row.children.length; j++) { + var key = row.children[j]; + func(key); + } + } + } + + onShiftModeChanged: { + forEachKey(function (key) { + if (shiftMode) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } + }); + } + + function alphaKeyClickedHandler(mouseArea) { + // reset shift mode to false after first keypress + if (shiftMode) { + resetShiftMode(false); + } + } + + Component.onCompleted: { + // hook up callbacks to every ascii key + forEachKey(function (key) { + if (/^[a-z]+$/i.test(key.glyph)) { + key.mouseArea.onClicked.connect(alphaKeyClickedHandler); + } + }); + } + + Rectangle { + id: leftRect + y: 0 + height: 200 + color: "#252525" + anchors.right: keyboardRect.left + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + } + + Rectangle { + id: keyboardRect + x: 206 + y: 0 + width: 480 + height: 200 + color: "#252525" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + Column { + id: column1 + width: 480 + height: 200 + + Row { + id: row1 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key1 + width: 44 + glyph: "q" + } + + Key { + id: key2 + width: 44 + glyph: "w" + } + + Key { + id: key3 + width: 44 + glyph: "e" + } + + Key { + id: key4 + width: 43 + glyph: "r" + } + + Key { + id: key5 + width: 43 + glyph: "t" + } + + Key { + id: key6 + width: 44 + glyph: "y" + } + + Key { + id: key7 + width: 44 + glyph: "u" + } + + Key { + id: key8 + width: 43 + glyph: "i" + } + + Key { + id: key9 + width: 42 + glyph: "o" + } + + Key { + id: key10 + width: 44 + glyph: "p" + } + + Key { + id: key28 + width: 45 + glyph: "←" + } + } + + Row { + id: row2 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 18 + + Key { + id: key11 + width: 43 + } + + Key { + id: key12 + width: 43 + glyph: "s" + } + + Key { + id: key13 + width: 43 + glyph: "d" + } + + Key { + id: key14 + width: 43 + glyph: "f" + } + + Key { + id: key15 + width: 43 + glyph: "g" + } + + Key { + id: key16 + width: 43 + glyph: "h" + } + + Key { + id: key17 + width: 43 + glyph: "j" + } + + Key { + id: key18 + width: 43 + glyph: "k" + } + + Key { + id: key19 + width: 43 + glyph: "l" + } + + Key { + id: key32 + width: 75 + glyph: "⏎" + } + } + + Row { + id: row3 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key27 + width: 46 + glyph: "⇪" + toggle: true + onToggledChanged: { + shiftMode = toggled; + } + } + + Key { + id: key20 + width: 43 + glyph: "z" + } + + Key { + id: key21 + width: 43 + glyph: "x" + } + + Key { + id: key22 + width: 43 + glyph: "c" + } + + Key { + id: key23 + width: 43 + glyph: "v" + } + + Key { + id: key24 + width: 43 + glyph: "b" + } + + Key { + id: key25 + width: 43 + glyph: "n" + } + + Key { + id: key26 + width: 44 + glyph: "m" + } + + Key { + id: key31 + width: 43 + glyph: "," + } + + Key { + id: key33 + width: 43 + glyph: "." + } + + Key { + id: key36 + width: 46 + glyph: "/" + } + + } + + Row { + id: row4 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 19 + + Key { + id: key30 + width: 89 + glyph: "&123" + mouseArea.onClicked: { + keyboardBase.parent.punctuationMode = true; + } + } + + Key { + id: key29 + width: 285 + glyph: " " + } + + Key { + id: key34 + width: 43 + glyph: "⇦" + } + + Key { + id: key35 + x: 343 + width: 43 + glyph: "⇨" + } + + } + } + } + + Rectangle { + id: rightRect + y: 280 + height: 200 + color: "#252525" + border.width: 0 + anchors.left: keyboardRect.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + + Rectangle { + id: rectangle1 + color: "#ffffff" + anchors.bottom: keyboardRect.top + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + +} diff --git a/interface/resources/qml/controls/KeyboardPunctuation.qml b/interface/resources/qml/controls/KeyboardPunctuation.qml new file mode 100644 index 0000000000..6fef366772 --- /dev/null +++ b/interface/resources/qml/controls/KeyboardPunctuation.qml @@ -0,0 +1,324 @@ +import QtQuick 2.0 + +Item { + id: keyboardBase + height: 200 + Rectangle { + id: leftRect + y: 0 + height: 200 + color: "#252525" + anchors.right: keyboardRect.left + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + } + + Rectangle { + id: keyboardRect + x: 206 + y: 0 + width: 480 + height: 200 + color: "#252525" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + Column { + id: column1 + width: 480 + height: 200 + + Row { + id: row1 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key1 + width: 43 + glyph: "1" + } + + Key { + id: key2 + width: 43 + glyph: "2" + } + + Key { + id: key3 + width: 43 + glyph: "3" + } + + Key { + id: key4 + width: 43 + glyph: "4" + } + + Key { + id: key5 + width: 43 + glyph: "5" + } + + Key { + id: key6 + width: 43 + glyph: "6" + } + + Key { + id: key7 + width: 43 + glyph: "7" + } + + Key { + id: key8 + width: 43 + glyph: "8" + } + + Key { + id: key9 + width: 43 + glyph: "9" + } + + Key { + id: key10 + width: 43 + glyph: "0" + } + + Key { + id: key28 + width: 50 + glyph: "←" + } + } + + Row { + id: row2 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 0 + + Key { + id: key11 + width: 43 + glyph: "!" + } + + Key { + id: key12 + width: 43 + glyph: "@" + } + + Key { + id: key13 + width: 43 + glyph: "#" + } + + Key { + id: key14 + width: 43 + glyph: "$" + } + + Key { + id: key15 + width: 43 + glyph: "%" + } + + Key { + id: key16 + width: 43 + glyph: "^" + } + + Key { + id: key17 + width: 43 + glyph: "&" + } + + Key { + id: key18 + width: 43 + glyph: "*" + } + + Key { + id: key19 + width: 43 + glyph: "(" + } + + Key { + id: key32 + width: 43 + glyph: ")" + } + + Key { + id: key37 + width: 50 + glyph: "⏎" + } + } + + Row { + id: row3 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + id: key27 + width: 43 + glyph: "=" + } + + Key { + id: key20 + width: 43 + glyph: "+" + } + + Key { + id: key21 + width: 43 + glyph: "-" + } + + Key { + id: key22 + width: 43 + glyph: "_" + } + + Key { + id: key23 + width: 43 + glyph: ";" + } + + Key { + id: key24 + width: 43 + glyph: ":" + } + + Key { + id: key25 + width: 43 + glyph: "'" + } + + Key { + id: key26 + width: 43 + glyph: "\"" + } + + Key { + id: key31 + width: 43 + glyph: "<" + } + + Key { + id: key33 + width: 43 + glyph: ">" + } + + Key { + id: key36 + width: 43 + glyph: "?" + } + + } + + Row { + id: row4 + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 19 + + Key { + id: key30 + width: 65 + glyph: "abc" + mouseArea.onClicked: { + keyboardBase.parent.punctuationMode = false + } + } + + Key { + id: key29 + width: 285 + glyph: " " + } + + Key { + id: key34 + width: 43 + glyph: "⇦" + } + + Key { + id: key35 + x: 343 + width: 43 + glyph: "⇨" + } + + } + } + } + + Rectangle { + id: rightRect + y: 280 + height: 200 + color: "#252525" + border.width: 0 + anchors.left: keyboardRect.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + + Rectangle { + id: rectangle1 + color: "#ffffff" + anchors.bottom: keyboardRect.top + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + +} diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 84ef31e87f..2f7a668d65 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,63 +1,136 @@ import QtQuick 2.5 import QtWebEngine 1.1 +import QtWebChannel 1.0 -WebEngineView { - id: root - property var newUrl; +Item { + property alias url: root.url + property alias eventBridge: eventBridgeWrapper.eventBridge + property bool keyboardRaised: false + property bool punctuationMode: false - profile: desktop.browserProfile - - Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") - // Ensure the JS from the web-engine makes it to our logging - root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); - }); + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; } - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 - Timer { - id: urlReplacementTimer - running: false - repeat: false - interval: 50 - onTriggered: url = newUrl; - } + WebEngineView { + id: root + x: 0 + y: 0 + width: parent.width + height: keyboardRaised ? parent.height - keyboard1.height : parent.height - onUrlChanged: { - var originalUrl = url.toString(); - newUrl = urlHandler.fixupUrl(originalUrl).toString(); - if (newUrl !== originalUrl) { - root.stop(); - if (urlReplacementTimer.running) { - console.warn("Replacement timer already running"); - return; - } - urlReplacementTimer.start(); + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld } - } - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } - onLoadingChanged: { - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + + property string newUrl: "" + + webChannel.registeredObjects: [eventBridgeWrapper] + + Component.onCompleted: { + console.log("Connecting JS messaging to Hifi Logging"); + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); + }); + + root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 + Timer { + id: urlReplacementTimer + running: false + repeat: false + interval: 50 + onTriggered: url = root.newUrl; + } + + onUrlChanged: { + var originalUrl = url.toString(); + root.newUrl = urlHandler.fixupUrl(originalUrl).toString(); + if (root.newUrl !== originalUrl) { + root.stop(); + if (urlReplacementTimer.running) { + console.warn("Replacement timer already running"); + return; + } + urlReplacementTimer.start(); + } + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onLoadingChanged: { + keyboardRaised = false; + punctuationMode = false; + keyboard1.resetShiftMode(false); + + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } } } } - } - onNewViewRequested:{ - if (desktop) { - var component = Qt.createComponent("../Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView); + onNewViewRequested:{ + // desktop is not defined for web-entities + if (desktop) { + var component = Qt.createComponent("../Browser.qml"); + var newWindow = component.createObject(desktop); + request.openIn(newWindow.webView); + } } } + + // virtual keyboard, letters + Keyboard { + id: keyboard1 + y: keyboardRaised ? parent.height : 0 + height: keyboardRaised ? 200 : 0 + visible: keyboardRaised && !punctuationMode + enabled: keyboardRaised && !punctuationMode + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } + + KeyboardPunctuation { + id: keyboard2 + y: keyboardRaised ? parent.height : 0 + height: keyboardRaised ? 200 : 0 + visible: keyboardRaised && punctuationMode + enabled: keyboardRaised && punctuationMode + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + } } diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 62f31f07fc..7e523bbb70 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -46,8 +46,20 @@ OriginalDesktop.Desktop { } } - property var toolbars: ({}) Component { id: toolbarBuilder; Toolbar { } } + // This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted. + // Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got + // wiped during startup. + Toolbar { + id: sysToolbar; + objectName: "com.highfidelity.interface.toolbar.system"; + // Magic: sysToolbar.x and y come from settings, and are bound before the properties specified here are applied. + x: sysToolbar.x; + y: sysToolbar.y; + } + property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar + map[sysToolbar.objectName] = sysToolbar; + return map; })({}); Component.onCompleted: { WebEngine.settings.javascriptCanOpenWindows = true; @@ -55,7 +67,6 @@ OriginalDesktop.Desktop { WebEngine.settings.spatialNavigationEnabled = false; WebEngine.settings.localContentCanAccessRemoteUrls = true; - var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); var toggleHudButton = sysToolbar.addButton({ objectName: "hudToggle", imageURL: "../../../icons/hud.svg", diff --git a/interface/resources/qml/hifi/dialogs/NetworkingPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/NetworkingPreferencesDialog.qml new file mode 100644 index 0000000000..652d3347fc --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/NetworkingPreferencesDialog.qml @@ -0,0 +1,19 @@ +import QtQuick 2.5 +import Qt.labs.settings 1.0 + +import "../../dialogs" + +PreferencesDialog { + id: root + objectName: "NetworkingPreferencesDialog" + title: "Networking Settings" + showCategories: ["Networking"] + property var settings: Settings { + category: root.objectName + property alias x: root.x + property alias y: root.y + property alias width: root.width + property alias height: root.height + } +} + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8620b384ec..904a6c5b65 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -400,12 +400,10 @@ static const QString STATE_GROUNDED = "Grounded"; static const QString STATE_NAV_FOCUSED = "NavigationFocused"; bool setupEssentials(int& argc, char** argv) { - unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); - if (portStr) { - listenPort = atoi(portStr); - } + const int listenPort = portStr ? atoi(portStr) : INVALID_PORT; + // Set build version QCoreApplication::setApplicationVersion(BuildInfo::VERSION); diff --git a/interface/src/Application.h b/interface/src/Application.h index 8bfae51179..02682defca 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -223,7 +223,7 @@ public: // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // rendering of several elements depend on that // TODO: carry that information on the Camera as a setting - bool isHMDMode() const; + virtual bool isHMDMode() const override; glm::mat4 getHMDSensorPose() const; glm::mat4 getEyeOffset(int eye) const; glm::mat4 getEyeProjection(int eye) const; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 50dc748461..d3caa4a092 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -522,6 +522,11 @@ Menu::Menu() { // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); + action = addActionToQMenuAndActionHash(networkMenu, MenuOption::Networking); + connect(action, &QAction::triggered, [] { + DependencyManager::get()->toggle(QUrl("hifi/dialogs/NetworkingPreferencesDialog.qml"), + "NetworkingPreferencesDialog"); + }); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ee00644746..b25603caeb 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -129,6 +129,7 @@ namespace MenuOption { const QString MuteEnvironment = "Mute Environment"; const QString MuteFaceTracking = "Mute Face Tracking"; const QString NamesAboveHeads = "Names Above Heads"; + const QString Networking = "Networking..."; const QString NoFaceTracking = "None"; const QString OctreeStats = "Entity Statistics"; const QString OnePointCalibration = "1 Point Calibration"; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 7fdafc9bda..0ba057a5e3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -332,4 +332,19 @@ void setupPreferences() { preferences->addPreference(preference); } } + { + static const QString RENDER("Networking"); + + auto nodelist = DependencyManager::get(); + { + static const int MIN_PORT_NUMBER { 0 }; + static const int MAX_PORT_NUMBER { 65535 }; + auto getter = [nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; + auto setter = [nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; + auto preference = new IntSpinnerPreference(RENDER, "Listening Port", getter, setter); + preference->setMin(MIN_PORT_NUMBER); + preference->setMax(MAX_PORT_NUMBER); + preferences->addPreference(preference); + } + } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 4c683a27f8..4a5021155f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -512,7 +512,7 @@ void OpenGLDisplayPlugin::compositeOverlay() { batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), _currentFrame->framebuffer->getSize())); + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } }); @@ -536,7 +536,7 @@ void OpenGLDisplayPlugin::compositePointer() { batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), _currentFrame->framebuffer->getSize())); + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } }); @@ -726,7 +726,7 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const { - uvec2 vpSize = _currentFrame->framebuffer->getSize(); + uvec2 vpSize = _compositeFramebuffer->getSize(); vpSize.x /= 2; uvec2 vpPos; if (eye == Eye::Right) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 4a42df561e..cc022d9df2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -26,6 +26,7 @@ #include #include "EntityTreeRenderer.h" +#include "EntitiesRendererLogging.h" const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount { 0 }; @@ -37,6 +38,35 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static int MAX_WINDOW_SIZE = 4096; static float OPAQUE_ALPHA_THRESHOLD = 0.99f; +void WebEntityAPIHelper::synthesizeKeyPress(QString key) { + if (_renderableWebEntityItem) { + _renderableWebEntityItem->synthesizeKeyPress(key); + } +} + +void WebEntityAPIHelper::emitScriptEvent(const QVariant& message) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } else { + emit scriptEventReceived(message); + } +} + +void WebEntityAPIHelper::emitWebEvent(const QVariant& message) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } else { + // special case to handle raising and lowering the virtual keyboard + if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(true); + } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(false); + } else { + emit webEventReceived(message); + } + } +} + EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderableWebEntityItem(entityID) }; entity->setProperties(properties); @@ -46,9 +76,26 @@ EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID) : WebEntityItem(entityItemID) { qDebug() << "Created web entity " << getID(); + + _touchDevice.setCapabilities(QTouchDevice::Position); + _touchDevice.setType(QTouchDevice::TouchScreen); + _touchDevice.setName("RenderableWebEntityItemTouchDevice"); + _touchDevice.setMaximumTouchPoints(4); + + _webEntityAPIHelper = new WebEntityAPIHelper; + _webEntityAPIHelper->setRenderableWebEntityItem(this); + _webEntityAPIHelper->moveToThread(qApp->thread()); + + // forward web events to EntityScriptingInterface + auto entities = DependencyManager::get(); + QObject::connect(_webEntityAPIHelper, &WebEntityAPIHelper::webEventReceived, [=](const QVariant& message) { + emit entities->webEventReceived(entityItemID, message); + }); } RenderableWebEntityItem::~RenderableWebEntityItem() { + _webEntityAPIHelper->setRenderableWebEntityItem(nullptr); + _webEntityAPIHelper->deleteLater(); destroyWebSurface(); qDebug() << "Destroyed web entity " << getID(); } @@ -60,6 +107,20 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { } qDebug() << "Building web surface"; + QString javaScriptToInject; + QFile webChannelFile(":qtwebchannel/qwebchannel.js"); + QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js"); + if (webChannelFile.open(QFile::ReadOnly | QFile::Text) && + createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) { + QString webChannelStr = QTextStream(&webChannelFile).readAll(); + QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll(); + + // concatenate these js files + javaScriptToInject = webChannelStr + createGlobalEventBridgeStr; + } else { + qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js"; + } + ++_currentWebCount; // Save the original GL context, because creating a QML surface will create a new context QOpenGLContext * currentContext = QOpenGLContext::currentContext(); @@ -67,10 +128,14 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface = new OffscreenQmlSurface(); _webSurface->create(currentContext); _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/")); - _webSurface->load("WebView.qml"); + _webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) { + context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); + }); _webSurface->resume(); + _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper)); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); + _webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper); _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) { _texture = textureId; }); @@ -93,10 +158,14 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { point.setState(Qt::TouchPointReleased); glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); + point.setScenePos(windowPoint); point.setPos(windowPoint); QList touchPoints; touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints); + touchEvent->setWindow(_webSurface->getWindow()); + touchEvent->setDevice(&_touchDevice); + touchEvent->setTarget(_webSurface->getRootItem()); QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent); } }); @@ -210,7 +279,6 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); QPointF windowPoint(windowPos.x, windowPos.y); - if (event.getType() == PointerEvent::Move) { // Forward a mouse move event to webSurface QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier); @@ -252,9 +320,9 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) { touchPoints.push_back(point); QTouchEvent* touchEvent = new QTouchEvent(type); - touchEvent->setWindow(nullptr); - touchEvent->setDevice(nullptr); - touchEvent->setTarget(nullptr); + touchEvent->setWindow(_webSurface->getWindow()); + touchEvent->setDevice(&_touchDevice); + touchEvent->setTarget(_webSurface->getRootItem()); touchEvent->setTouchPoints(touchPoints); touchEvent->setTouchPointStates(touchPointState); @@ -303,3 +371,63 @@ bool RenderableWebEntityItem::isTransparent() { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; return fadeRatio < OPAQUE_ALPHA_THRESHOLD; } + +// UTF-8 encoded symbols +static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift +static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace +static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow +static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow +static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols +static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return +static const char PUNCTUATION_STRING[] = "&123"; +static const char ALPHABET_STRING[] = "abc"; + +static bool equals(const QByteArray& byteArray, const uint8_t* ptr) { + int i; + for (i = 0; i < byteArray.size(); i++) { + if ((char)ptr[i] != byteArray[i]) { + return false; + } + } + return ptr[i] == 0x00; +} + +void RenderableWebEntityItem::synthesizeKeyPress(QString key) { + auto utf8Key = key.toUtf8(); + + int scanCode = (int)utf8Key[0]; + QString keyString = key; + if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) || + equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) { + return; // ignore + } else if (equals(utf8Key, LEFT_ARROW)) { + scanCode = Qt::Key_Backspace; + keyString = "\x08"; + } else if (equals(utf8Key, RETURN_SYMBOL)) { + scanCode = Qt::Key_Return; + keyString = "\x0d"; + } else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Left; + keyString = ""; + } else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Right; + keyString = ""; + } + + QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); + QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString); + QCoreApplication::postEvent(getEventHandler(), pressEvent); + QCoreApplication::postEvent(getEventHandler(), releaseEvent); +} + +void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { + _webEntityAPIHelper->emitScriptEvent(message); +} + +void RenderableWebEntityItem::setKeyboardRaised(bool raised) { + + // raise the keyboard only while in HMD mode and it's being requested. + bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised; + + _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(value)); +} diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 03234ce690..47808c4262 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -22,6 +22,27 @@ class OffscreenQmlSurface; class QWindow; class QObject; class EntityTreeRenderer; +class RenderableWebEntityItem; + +class WebEntityAPIHelper : public QObject { + Q_OBJECT +public: + void setRenderableWebEntityItem(RenderableWebEntityItem* renderableWebEntityItem) { + _renderableWebEntityItem = renderableWebEntityItem; + } + Q_INVOKABLE void synthesizeKeyPress(QString key); + + // event bridge +public slots: + void emitScriptEvent(const QVariant& scriptMessage); + void emitWebEvent(const QVariant& webMessage); +signals: + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); + +protected: + RenderableWebEntityItem* _renderableWebEntityItem{ nullptr }; +}; class RenderableWebEntityItem : public WebEntityItem { public: @@ -42,10 +63,16 @@ public: void update(const quint64& now) override; bool needsToCallUpdate() const override { return _webSurface != nullptr; } + virtual void emitScriptEvent(const QVariant& message) override; + void setKeyboardRaised(bool raised); + SIMPLE_RENDERABLE(); virtual bool isTransparent() override; +public: + void synthesizeKeyPress(QString key); + private: bool buildWebSurface(EntityTreeRenderer* renderer); void destroyWebSurface(); @@ -58,6 +85,8 @@ private: bool _pressed{ false }; QTouchEvent _lastTouchEvent { QEvent::TouchUpdate }; uint64_t _lastRenderTime{ 0 }; + QTouchDevice _touchDevice; + WebEntityAPIHelper* _webEntityAPIHelper; QMetaObject::Connection _mousePressConnection; QMetaObject::Connection _mouseReleaseConnection; @@ -65,5 +94,4 @@ private: QMetaObject::Connection _hoverLeaveConnection; }; - #endif // hifi_RenderableWebEntityItem_h diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 85080ccc0a..e01e5e087c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -456,6 +456,8 @@ public: bool isFading() const { return _isFading; } float getFadingRatio() const { return (isFading() ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f); } + virtual void emitScriptEvent(const QVariant& message) {} + protected: void setSimulated(bool simulated) { _simulated = simulated; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 2dca21ac73..12b9e2fa79 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1289,6 +1289,17 @@ bool EntityScriptingInterface::wantsHandControllerPointerEvents(QUuid id) { return result; } +void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, const QVariant& message) { + if (_entityTree) { + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + if (entity) { + entity->emitScriptEvent(message); + } + }); + } +} + float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { return std::abs(mass * (newVelocity - oldVelocity)); } @@ -1305,3 +1316,4 @@ float EntityScriptingInterface::getCostMultiplier() { void EntityScriptingInterface::setCostMultiplier(float value) { costMultiplier = value; } + diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index be9b1d27e7..cb69cebe6b 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -205,6 +205,8 @@ public slots: Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id); + Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); + signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -232,6 +234,8 @@ signals: void clearingEntities(); void debitEnergySource(float value); + void webEventReceived(const EntityItemID& entityItemID, const QVariant& message); + private: bool actionWorker(const QUuid& entityID, std::function actor); bool setVoxels(QUuid entityID, std::function actor); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 7973ed4b4f..5ab2678474 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -479,6 +479,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { auto rootContext = getRootContext(); rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); } void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 6d9de2dbc1..7fad66d608 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ #include #include +#include #include #include @@ -34,12 +36,14 @@ #include "NetworkLogging.h" #include "udt/Packet.h" +static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); + const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, NodeType::AudioMixer }; -LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : +LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : _sessionUUID(), _nodeHash(), _nodeMutex(QReadWriteLock::Recursive), @@ -62,11 +66,11 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short } qRegisterMetaType("ConnectionStep"); - - _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); + auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); + _nodeSocket.bind(QHostAddress::AnyIPv4, port); qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort(); - if (dtlsListenPort > 0) { + if (dtlsListenPort != INVALID_PORT) { // only create the DTLS socket during constructor if a custom port is passed _dtlsSocket = new QUdpSocket(this); @@ -157,6 +161,18 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { } } +void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setSocketLocalPort", Qt::QueuedConnection, + Q_ARG(quint16, socketLocalPort)); + return; + } + if (_nodeSocket.localPort() != socketLocalPort) { + _nodeSocket.rebind(socketLocalPort); + LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort); + } +} + QUdpSocket& LimitedNodeList::getDTLSSocket() { if (!_dtlsSocket) { // DTLS socket getter called but no DTLS socket exists, create it now @@ -606,6 +622,12 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t }); } + // Signal when a socket changes, so we can start the hole punch over. + auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambda to hold a strong ref + connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [=] { + emit nodeSocketUpdated(weakPtr); + }); + return newNodePointer; } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 49a3a155a2..cd343a5232 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -45,6 +45,8 @@ #include "udt/Socket.h" #include "UUIDHasher.h" +const int INVALID_PORT = -1; + const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000; extern const std::set SOLO_NODE_TYPES; @@ -113,6 +115,8 @@ public: bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } + Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort); + QUdpSocket& getDTLSSocket(); PacketReceiver& getPacketReceiver() { return *_packetReceiver; } @@ -250,6 +254,7 @@ signals: void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); + void nodeSocketUpdated(SharedNodePointer); void nodeKilled(SharedNodePointer); void nodeActivated(SharedNodePointer); @@ -267,9 +272,9 @@ protected slots: void errorTestingLocalSocket(); protected: - LimitedNodeList(unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); - LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton - void operator=(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton + LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); + LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton + void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode, const HifiSockAddr& overridenSockAddr); diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index da2eced05c..2ebee1b71f 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -63,6 +63,7 @@ void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { if (!wasOldSocketNull) { qCDebug(networking) << "Public socket change for node" << *this; + emit socketUpdated(); } } } @@ -82,6 +83,7 @@ void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { if (!wasOldSocketNull) { qCDebug(networking) << "Local socket change for node" << *this; + emit socketUpdated(); } } } @@ -101,6 +103,7 @@ void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { if (!wasOldSocketNull) { qCDebug(networking) << "Symmetric socket change for node" << *this; + emit socketUpdated(); } } } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 8298a2dad4..7185ffef29 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -81,9 +81,12 @@ public: public slots: void startPingTimer(); void stopPingTimer(); + signals: void pingTimerTimeout(); void socketActivated(const HifiSockAddr& sockAddr); + void socketUpdated(); + protected: void setActiveSocket(HifiSockAddr* discoveredSocket); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3a07ea8b54..593a79b311 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -33,7 +33,7 @@ const int KEEPALIVE_PING_INTERVAL_MS = 1000; -NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : +NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), _ownerType(newOwnerType), _nodeTypesOfInterest(), @@ -93,6 +93,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); + connect(this, &LimitedNodeList::nodeSocketUpdated, this, &NodeList::startNodeHolePunch); // anytime we get a new node we may need to re-send our set of ignored node IDs to it connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index f3cd5bed0d..f08c0dbe45 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -116,10 +116,10 @@ private slots: void maybeSendIgnoreSetToNode(SharedNodePointer node); private: - NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile - NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); - NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton - void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton + NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile + NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); + NodeList(NodeList const&) = delete; // Don't implement, needed to avoid copies of singleton + void operator=(NodeList const&) = delete; // Don't implement, needed to avoid copies of singleton void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index a39100f8d1..37ededa55c 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -63,10 +63,12 @@ void Socket::bind(const QHostAddress& address, quint16 port) { } void Socket::rebind() { - quint16 oldPort = _udpSocket.localPort(); + rebind(_udpSocket.localPort()); +} +void Socket::rebind(quint16 localPort) { _udpSocket.close(); - bind(QHostAddress::AnyIPv4, oldPort); + bind(QHostAddress::AnyIPv4, localPort); } void Socket::setSystemBufferSizes() { diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 6b8ccf1fa8..bc4393d4bd 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -61,8 +61,9 @@ public: qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& sockAddr); void bind(const QHostAddress& address, quint16 port = 0); + void rebind(quint16 port); void rebind(); - + void setPacketFilterOperator(PacketFilterOperator filterOperator) { _packetFilterOperator = filterOperator; } void setPacketHandler(PacketHandler handler) { _packetHandler = handler; } void setMessageHandler(MessageHandler handler) { _messageHandler = handler; } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 65fa693914..362c0cc1bf 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -48,6 +48,8 @@ public: virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual bool isHMDMode() const = 0; + // FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface static AbstractViewStateInterface* instance(); static void setInstance(AbstractViewStateInterface* instance); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 7c81aeea22..c1f011fd79 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -705,7 +705,7 @@ void RenderDeferredLocals::run(const render::SceneContextPointer& sceneContext, static int frame = 0; frame++; - if (frame % 1000 == 0) { + if (frame % 2000 == 0) { lightClusters->updateFrustum(viewFrustum); lightClusters->updateVisibleLights(lightIndices); diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh new file mode 100644 index 0000000000..004339ff34 --- /dev/null +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -0,0 +1,49 @@ + +<@if not RENDER_LIGHT_CLUSTER_GRID_SLH@> +<@def RENDER_LIGHT_CLUSTER_GRID_SLH@> + + + +struct FrustumGrid { + float frustumNear; + float rangeNear; + float rangeFar; + float frustumFar; + ivec3 dims; + float spare; + mat4 eyeToGridProj; + mat4 worldToEyeMat; + mat4 eyeToWorldMat; +}; + +uniform frustumGridBuffer { + FrustumGrid frustumGrid; +}; + +float projection_getNear(mat4 projection) { + float planeC = projection[2][3] + projection[2][2]; + float planeD = projection[3][2]; + return planeD / planeC; +} +float projection_getFar(mat4 projection) { + //float planeA = projection[0][3] - projection[0][2]; All Zeros + //float planeB = projection[1][3] - projection[1][2]; All Zeros + float planeC = projection[2][3] - projection[2][2]; + float planeD = /*projection[3][3]*/ -projection[3][2]; + return planeD / planeC; +} + +// glsl / C++ compatible source as interface for FrustrumGrid +<@include LightClusterGrid_shared.slh@> + +// end of hybrid include + +<@endif@> diff --git a/libraries/render-utils/src/LightClusterGrid_shared.slh b/libraries/render-utils/src/LightClusterGrid_shared.slh new file mode 100644 index 0000000000..c89d0e4c9b --- /dev/null +++ b/libraries/render-utils/src/LightClusterGrid_shared.slh @@ -0,0 +1,92 @@ + // glsl / C++ compatible source as interface for FrustrumGrid + + int frustumGrid_numClusters() { + return frustumGrid.dims.x * frustumGrid.dims.y * frustumGrid.dims.z; + } + + + float frustumGrid_depthRamp(float linear) { + // return linear; + return linear * linear; + } + float frustumGrid_depthRampInverse(float volume) { + // return volume; + return sqrt(volume); + } + + vec3 frustumGrid_gridToVolume(vec3 pos, ivec3 dims) { + vec3 gridScale = vec3(1.0, 1.0, 1.0) / vec3(dims); + vec3 volumePos = pos * gridScale; + volumePos.z = frustumGrid_depthRamp(volumePos.z); + return volumePos; + } + + + vec3 frustumGrid_volumeToGrid(vec3 vpos, ivec3 dims) { + vec3 gridPos = vec3(vpos.x, vpos.y, frustumGrid_depthRampInverse(vpos.z)) * vec3(dims); + return gridPos; + } + + vec4 frustumGrid_volumeToClip(vec3 vpos, float rangeNear, float rangeFar) { + vec3 ndcPos = vec3(-1.0 + 2.0 * vpos.x, -1.0 + 2.0 * vpos.y, vpos.z); + float depth = rangeNear * (1 - ndcPos.z) + rangeFar * (ndcPos.z); + vec4 clipPos = vec4(ndcPos.x * depth, ndcPos.y * depth, 1.0, depth); + return clipPos; + } + + vec3 frustumGrid_clipToEye(vec4 clipPos, mat4 projection) { + return vec3( + (clipPos.x + projection[2][0] * clipPos.w) / projection[0][0], + (clipPos.y + projection[2][1] * clipPos.w) / projection[1][1], + -clipPos.w + //, (clipPos.z - projection[3][3] * clipPos.w) / projection[3][2] + ); + } + + vec3 frustumGrid_volumeToEye(vec3 vpos, mat4 projection, float rangeNear, float rangeFar) { + return frustumGrid_clipToEye(frustumGrid_volumeToClip(vpos, rangeNear, rangeFar), projection); + } + + + vec3 frustumGrid_eyeToVolume(vec3 epos, mat4 projection, float rangeNear, float rangeFar) { + vec4 clipPos = vec4(epos.x * projection[0][0] + epos.z * projection[2][0], + epos.y * projection[1][1] + epos.z * projection[2][1], + epos.z * projection[2][2] + projection[2][3], + -epos.z); + vec4 ndcPos = clipPos / clipPos.w; + + vec3 volumePos = vec3(0.5 * (ndcPos.x + 1.0), 0.5 * (ndcPos.y + 1.0), (clipPos.w - rangeNear) / (rangeFar - rangeNear)); + return volumePos; + } + + + vec3 frustumGrid_clusterPosToEye(ivec3 clusterPos, vec3 offset = vec3(0.5)) { + + vec3 cvpos = vec3(clusterPos) + offset; + + + vec3 volumePos = frustumGrid_gridToVolume(cvpos, frustumGrid.dims); + + vec3 eyePos = frustumGrid_volumeToEye(volumePos, frustumGrid.eyeToGridProj, frustumGrid.rangeNear, frustumGrid.rangeFar); + + return eyePos; + } + + + ivec3 frustumGrid_eyeToClusterPos(vec3 eyePos) { + + vec3 volumePos = frustumGrid_eyeToVolume(eyePos, frustumGrid.eyeToGridProj, frustumGrid.rangeNear, frustumGrid.rangeFar); + + vec3 gridPos = frustumGrid_volumeToGrid(volumePos, frustumGrid.dims); + + + return ivec3(gridPos); + } + + vec4 frustumGrid_eyeToWorld(vec4 eyePos) { + return frustumGrid.eyeToWorldMat * eyePos; + } + + // <@if 1@> + // Trigger Scribe include + // <@endif@> End C++ compatible \ No newline at end of file diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index 77fe7fa798..faf50129c5 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -12,11 +12,25 @@ #include + +#include + #include "lightClusters_drawGrid_vert.h" #include "lightClusters_drawGrid_frag.h" +//#include "lightClusters_drawClusterFromDepth_vert.h" +#include "lightClusters_drawClusterFromDepth_frag.h" + +enum LightClusterGridShader_MapSlot { + DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 7, +}; + enum LightClusterGridShader_BufferSlot { LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 0, + DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, + CAMERA_CORRECTION_BUFFER_SLOT, + LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, + LIGHT_INDEX_GPU_SLOT, }; #include "DeferredLightingEffect.h" @@ -27,7 +41,7 @@ LightClusters::LightClusters() { void LightClusters::updateFrustum(const ViewFrustum& frustum) { _frustum = frustum; - _frustrumGridBuffer.edit().updateFrustrum(frustum); + _frustumGridBuffer.edit().updateFrustum(frustum); } void LightClusters::updateLightStage(const LightStagePointer& lightStage) { @@ -57,7 +71,7 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("frustrumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); @@ -75,26 +89,49 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { return _drawClusterGrid; } -void DebugLightClusters::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { +const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() { + if (!_drawClusterFromDepth) { + // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(lightClusters_drawClusterFromDepth_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); + slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); + + gpu::Shader::makeProgram(*program, slotBindings); + + + auto state = std::make_shared(); + + // state->setDepthTest(true, false, gpu::LESS_EQUAL); + + // Blend on transparent + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + + // Good to go add the brand new pipeline + _drawClusterFromDepth = gpu::Pipeline::create(program, state); + } + return _drawClusterFromDepth; +} + +void DebugLightClusters::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs) { auto deferredLightingEffect = DependencyManager::get(); auto lightClusters = deferredLightingEffect->getLightClusters(); - /* auto deferredTransform = inputs.get0(); + auto deferredTransform = inputs.get0(); auto deferredFramebuffer = inputs.get1(); auto lightingModel = inputs.get2(); auto surfaceGeometryFramebuffer = inputs.get3(); - auto ssaoFramebuffer = inputs.get4(); - auto subsurfaceScatteringResource = inputs.get5(); - */ + auto args = renderContext->args; - - - auto drawPipeline = getDrawClusterGridPipeline(); - + gpu::Batch batch; - // Assign the camera transform batch.setViewportTransform(args->_viewport); glm::mat4 projMat; @@ -108,15 +145,34 @@ void DebugLightClusters::run(const render::SceneContextPointer& sceneContext, co // Then the actual ClusterGrid attributes batch.setModelTransform(Transform()); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustrumGridBuffer); + batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer); + - // bind the one gpu::Pipeline we need - batch.setPipeline(drawPipeline); if (true) { - batch.draw(gpu::LINES, 24, 0); + // bind the one gpu::Pipeline we need + batch.setPipeline(getDrawClusterGridPipeline()); + + auto dims = lightClusters->_frustumGridBuffer->dims; + glm::ivec3 summedDims(dims.x*dims.y * dims.z, dims.x*dims.y, dims.x); + batch.drawInstanced(summedDims.x, gpu::LINES, 24, 0); } + if (true) { + batch.setPipeline(getDrawClusterFromDepthPipeline()); + batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, deferredTransform->getFrameTransformBuffer()); + + if (surfaceGeometryFramebuffer) { + batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, surfaceGeometryFramebuffer->getLinearDepthTexture()); + } + + batch.draw(gpu::TRIANGLE_STRIP, 4, 0); + + // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target + + batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr); + batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); + } args->_context->appendFrameBatch(batch); diff --git a/libraries/render-utils/src/LightClusters.h b/libraries/render-utils/src/LightClusters.h index 9cec0786ed..4491f5459d 100644 --- a/libraries/render-utils/src/LightClusters.h +++ b/libraries/render-utils/src/LightClusters.h @@ -20,65 +20,33 @@ class FrustumGrid { public: float _near { 0.1f }; - float _nearPrime { 1.0f }; - float _farPrime { 400.0f }; + float rangeNear { 1.0f }; + float rangeFar { 100.0f }; float _far { 10000.0f }; - glm::uvec3 _dims { 16, 16, 16 }; + glm::ivec3 dims { 8, 8, 8 }; float spare; - glm::mat4 _eyeToGridProj; - glm::mat4 _eyeToGridProjInv; + glm::mat4 eyeToGridProj; glm::mat4 _worldToEyeMat; - glm::mat4 _eyeToWorldMat; + glm::mat4 eyeToWorldMat; - float viewToLinearDepth(float depth) const { - float nDepth = -depth; - float ldepth = (nDepth - _nearPrime) / (_farPrime - _nearPrime); - - if (ldepth < 0.0f) { - return (nDepth - _near) / (_nearPrime - _near) - 1.0f; - } - if (ldepth > 1.0f) { - return (nDepth - _farPrime) / (_far - _farPrime) + 1.0f; - } - return ldepth; - } - - float linearToGridDepth(float depth) const { - return depth / (float) _dims.z; - } - - int gridDepthToLayer(float gridDepth) const { - return (int) gridDepth; - } - - glm::vec2 ndcToGridXY(const glm::vec3& ncpos) const { - return 0.5f * glm::vec2((ncpos.x + 1.0f) / (float)_dims.x, (ncpos.y + 1.0f) / (float)_dims.y); - } - - glm::ivec3 viewToGridPos(const glm::vec3& pos) const { - float z = linearToGridDepth(viewToLinearDepth(pos.z)); - - auto cpos = _eyeToGridProj * glm::vec4(pos, 1.0f); - - glm::vec3 ncpos(cpos); - ncpos /= cpos.w; - - - return glm::ivec3(ndcToGridXY(ncpos), (int) linearToGridDepth(z)); - } - - void updateFrustrum(const ViewFrustum& frustum) { - _eyeToGridProj = frustum.evalProjectionMatrixRange(_nearPrime, _farPrime); - _eyeToGridProjInv = glm::inverse(_eyeToGridProj); + void updateFrustum(const ViewFrustum& frustum) { + eyeToGridProj = frustum.evalProjectionMatrixRange(rangeNear, rangeFar); Transform view; frustum.evalViewTransform(view); - _eyeToWorldMat = view.getMatrix(); + eyeToWorldMat = view.getMatrix(); _worldToEyeMat = view.getInverseMatrix(); } + // Copy paste of the slh functions + using vec3 = glm::vec3; + using ivec3 = glm::ivec3; + using mat4 = glm::mat4; +#define frustumGrid (*this) +#include "LightClusterGrid_shared.slh" + }; class LightClusters { @@ -93,15 +61,13 @@ public: void updateVisibleLights(const LightStage::LightIndices& visibleLights); - // FrustumGrid _grid; - ViewFrustum _frustum; LightStagePointer _lightStage; - gpu::StructBuffer _frustrumGridBuffer; + gpu::StructBuffer _frustumGridBuffer; gpu::BufferPointer _lightIndicesBuffer; @@ -131,22 +97,30 @@ protected: int numDrawn { 0 }; }; + +#include "DeferredFrameTransform.h" +#include "DeferredFramebuffer.h" +#include "LightingModel.h" +#include "SurfaceGeometryPass.h" + class DebugLightClusters { public: - // using Inputs = render::VaryingSet6 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, SubsurfaceScatteringResourcePointer>; + using Inputs = render::VaryingSet4 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer>; using Config = DebugLightClustersConfig; - using JobModel = render::Job::Model; + using JobModel = render::Job::ModelI; DebugLightClusters(); void configure(const Config& config); - void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); + void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: gpu::BufferPointer _gridBuffer; gpu::PipelinePointer _drawClusterGrid; + gpu::PipelinePointer _drawClusterFromDepth; const gpu::PipelinePointer getDrawClusterGridPipeline(); + const gpu::PipelinePointer getDrawClusterFromDepthPipeline(); }; #endif diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 6076cf697d..f0438088ad 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -192,7 +192,8 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) { // LIght Cluster Grid Debuging job { - addJob("DebugLightClusters"); + const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, linearDepthTarget).hasVarying(); + addJob("DebugLightClusters", debugLightClustersInputs); } // Status icon rendering job diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf new file mode 100644 index 0000000000..a30fa78680 --- /dev/null +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -0,0 +1,61 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// lightClusters_drawClusterFro Depth.slf +// +// Created by Sam Gateau on 9/8/2016. +// Copyright 2015 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 +// + + +// Everything about deferred buffer +<@include DeferredBufferRead.slh@> + + +<@include LightClusterGrid.slh@> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + + +in vec4 varTexCoord0; +out vec4 _fragColor; + +void main(void) { + + // Grab the fragment data from the uv + vec2 texCoord = varTexCoord0.st; + + float Zeye = texture(linearZeyeMap, texCoord).x; + _fragColor = vec4(vec3(0.1 * (10 - Zeye)), 1.0); + // return; + + vec4 fragPosition = unpackDeferredPositionFromZeye(texCoord); + + +// return; + + ivec3 dims = frustumGrid.dims.xyz; + + ivec3 summedDims = ivec3(dims.x * dims.y, dims.x, 1); + + + vec3 eyePos = fragPosition.xyz; + + // vec4 worldPos = frustumGrid_eyeToWorld(vec4(eyePos.xyz, 1.0)); + + ivec3 clusterPos = frustumGrid_eyeToClusterPos(eyePos); + + + // standard transform + // TransformCamera cam = getTransformCamera(); + // <$transformWorldToClipPos(cam, worldPos, gl_Position)$> + + _fragColor = vec4(colorWheel(fract(float(clusterPos.z * summedDims.x + clusterPos.y * summedDims.y + clusterPos.x) / float(frustumGrid_numClusters()))), 0.9); + _fragColor = vec4(abs(fract(eyePos * 0.5)), 0.9); + +} + diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv new file mode 100644 index 0000000000..fa082d880f --- /dev/null +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv @@ -0,0 +1,74 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// lightClusters_drawClusterFrom Depth.slv +// Vertex shader +// +// Created by Sam Gateau on 9/8/2016 +// Copyright 2015 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 gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include LightClusterGrid.slh@> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + + + + +out vec4 varColor; + + +void main(void) { + const vec4 UNIT_BOX[8] = vec4[8]( + vec4(0.0, 0.0, 0.0, 1.0), + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(1.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0), + vec4(1.0, 0.0, 1.0, 1.0), + vec4(0.0, 1.0, 1.0, 1.0), + vec4(1.0, 1.0, 1.0, 1.0) + ); + const int UNIT_BOX_LINE_INDICES[24] = int[24]( + 0, 1, + 1, 3, + 3, 2, + 2, 0, + 4, 5, + 5, 7, + 7, 6, + 6, 4, + 2, 6, + 3, 7, + 0, 4, + 1, 5 + ); + vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]]; + + ivec3 dims = frustumGrid.dims.xyz; + + ivec3 summedDims = ivec3(dims.x * dims.y, dims.x, 1); + + int layer = gl_InstanceID / summedDims.x; + int offsetInLayer = gl_InstanceID % summedDims.x; + ivec3 clusterPos = ivec3(offsetInLayer % summedDims.y, offsetInLayer / summedDims.y, layer); + + + vec3 eyePos = frustumGrid_clusterPosToEye(clusterPos, vec3(0.05) + 0.9 * pos.xyz); + vec4 worldPos = frustumGrid_eyeToWorld(vec4(eyePos.xyz, 1.0)); + + + // standard transform + TransformCamera cam = getTransformCamera(); + <$transformWorldToClipPos(cam, worldPos, gl_Position)$> + + varColor = vec4(colorWheel(fract(float(gl_InstanceID) / float(frustumGrid_numClusters()))), 0.9); +} \ No newline at end of file diff --git a/libraries/render-utils/src/lightClusters_drawGrid.slv b/libraries/render-utils/src/lightClusters_drawGrid.slv index 4c32be6d5c..9eb80cb28e 100644 --- a/libraries/render-utils/src/lightClusters_drawGrid.slv +++ b/libraries/render-utils/src/lightClusters_drawGrid.slv @@ -13,39 +13,15 @@ // <@include gpu/Transform.slh@> - <$declareStandardTransform()$> +<@include LightClusterGrid.slh@> + <@include gpu/Color.slh@> <$declareColorWheel()$> -struct FrustrumGrid { - vec4 nearFarRange; - ivec4 dims; - mat4 eyeToGridProj; - mat4 eyeToGridProjInv; - mat4 worldToEyeMat; - mat4 eyeToWorldMat; -}; -uniform frustrumGridBuffer { - FrustrumGrid frustrumGrid; -}; - -float getProjectionNear(mat4 projection) { - float planeC = projection[2][3] + projection[2][2]; - float planeD = projection[3][2]; - return planeD / planeC; -} -float getProjectionFar(mat4 projection) { - //float planeA = projection[0][3] - projection[0][2]; - //float planeB = projection[1][3] - projection[1][2]; - float planeC = projection[2][3] - projection[2][2]; - float planeD = /*projection[3][3]*/ - projection[3][2]; - return planeD / planeC; -} -//uniform ivec4 inClusterLocation; out vec4 varColor; @@ -77,35 +53,22 @@ void main(void) { ); vec4 pos = UNIT_BOX[UNIT_BOX_LINE_INDICES[gl_VertexID]]; - // pos.z -= 10.0f; - vec3 cpos = vec3(1, 0, 1); + ivec3 dims = frustumGrid.dims.xyz; - // float z = frustrumGrid.nearFarRange.y * (1 - pos.z) + frustrumGrid.nearFarRange.z * (pos.z); - vec3 gridScale = vec3(0.5, 0.5, 1.0) / vec3(frustrumGrid.dims.xyz); + ivec3 summedDims = ivec3(dims.x * dims.y, dims.x, 1); - vec3 cvpos = cpos + pos.xyz; - - vec3 clusterStart = vec3(-1.0 + cvpos.x * gridScale.x, -1.0 + cvpos.y * gridScale.y, cvpos.z * gridScale.z); - - float z = getProjectionNear(frustrumGrid.eyeToGridProj) * (1 - clusterStart.z) + getProjectionFar(frustrumGrid.eyeToGridProj) * (clusterStart.z); - - vec4 eyePos = frustrumGrid.eyeToGridProjInv * (vec4(clusterStart.xy, 1.0, 1.0) * z); - - eyePos.xyz *= eyePos.w; - - vec4 worldPos = frustrumGrid.eyeToWorldMat * vec4(eyePos.xyz, 1.0); + int layer = gl_InstanceID / summedDims.x; + int offsetInLayer = gl_InstanceID % summedDims.x; + ivec3 clusterPos = ivec3(offsetInLayer % summedDims.y, offsetInLayer / summedDims.y, layer); - /* - int cellIsEmpty = sign(inClusterLocation.w); - ivec4 cellLocation = ivec4(inClusterLocation.xyz, (inClusterLocation.w < 0 ? -inClusterLocation.w : inClusterLocation.w)); - vec4 cellBound = evalBound(cellLocation); + vec3 eyePos = frustumGrid_clusterPosToEye(clusterPos, vec3(0.05) + 0.9 * pos.xyz); + vec4 worldPos = frustumGrid_eyeToWorld(vec4(eyePos.xyz, 1.0)); + - pos.xyz = cellBound.xyz + vec3(cellBound.w) * pos.xyz; - */ // standard transform TransformCamera cam = getTransformCamera(); <$transformWorldToClipPos(cam, worldPos, gl_Position)$> - varColor = vec4(colorWheel(fract(float(gl_VertexID) / 24.0)), 0.9); + varColor = vec4(colorWheel(fract(float(gl_InstanceID) / float(frustumGrid_numClusters()))), 0.9); } \ No newline at end of file diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index abb3f5afbd..f1915a9d6a 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -189,6 +189,38 @@ protected: float _step { 0.1f }; }; + +class IntPreference : public TypedPreference { + Q_OBJECT + Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(float min READ getMin CONSTANT) + Q_PROPERTY(float max READ getMax CONSTANT) + Q_PROPERTY(float step READ getStep CONSTANT) + +public: + IntPreference(const QString& category, const QString& name, Getter getter, Setter setter) + : TypedPreference(category, name, getter, setter) { } + + float getMin() const { return _min; } + void setMin(float min) { _min = min; }; + + float getMax() const { return _max; } + void setMax(float max) { _max = max; }; + + float getStep() const { return _step; } + void setStep(float step) { _step = step; }; + +signals: + void valueChanged(); + +protected: + void emitValueChanged() override { emit valueChanged(); } + + int _min { std::numeric_limits::min() }; + int _max { std::numeric_limits::max() }; + int _step { 1 }; +}; + class StringPreference : public TypedPreference { Q_OBJECT Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged) @@ -222,6 +254,15 @@ public: Type getType() override { return Spinner; } }; +class IntSpinnerPreference : public IntPreference { + Q_OBJECT +public: + IntSpinnerPreference(const QString& category, const QString& name, Getter getter, Setter setter) + : IntPreference(category, name, getter, setter) { } + + Type getType() override { return Spinner; } +}; + class EditPreference : public StringPreference { Q_OBJECT Q_PROPERTY(QString placeholderText READ getPlaceholderText CONSTANT) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 28488c9f3b..a413602ac7 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2271,6 +2271,7 @@ function MyController(hand) { }; } else { pointerEvent = this.touchingEnterPointerEvent; + pointerEvent.type = "Release"; pointerEvent.button = "Primary"; pointerEvent.isPrimaryHeld = false; } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index adbcd78381..a255b7a494 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -11,7 +11,7 @@ var RAD_TO_DEG = 180 / Math.PI; var X_AXIS = {x: 1, y: 0, z: 0}; var Y_AXIS = {x: 0, y: 1, z: 0}; -var DEFAULT_DPI = 30; +var DEFAULT_DPI = 32; var DEFAULT_WIDTH = 0.5; var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx"; diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index d1eaba5f25..d7e37313db 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -497,13 +497,17 @@ protected: _postUpdateLambdas[key] = func; } + bool isHMDMode() const override { + return false; + } + public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" static void setup() { DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::set(); - DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index fd6885c381..04bce72757 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -295,7 +295,7 @@ public: DependencyManager::registerInheritance(); //DependencyManager::registerInheritance(); DependencyManager::set(); - DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); DependencyManager::set();