diff --git a/examples/edit.js b/examples/edit.js index eb853fd359..e22bb28cd7 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -1484,10 +1484,10 @@ PropertiesTool = function(opts) { selections.push(entity); } data.selections = selections; - webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + webView.emitScriptEvent(JSON.stringify(data)); }); - webView.eventBridge.webEventReceived.connect(function(data) { + webView.webEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type == "print") { if (data.message) { @@ -1802,7 +1802,7 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); propertiesTool = PropertiesTool(); var particleExplorerTool = ParticleExplorerTool(); var selectedParticleEntity = 0; -entityListTool.webView.eventBridge.webEventReceived.connect(function(data) { +entityListTool.webView.webEventReceived.connect(function(data) { var data = JSON.parse(data); if (data.type == "selectionUpdate") { var ids = data.entityIds; @@ -1823,10 +1823,10 @@ entityListTool.webView.eventBridge.webEventReceived.connect(function(data) { selectedParticleEntity = ids[0]; particleExplorerTool.setActiveParticleEntity(ids[0]); - particleExplorerTool.webView.eventBridge.webEventReceived.connect(function(data) { + particleExplorerTool.webView.webEventReceived.connect(function(data) { var data = JSON.parse(data); if (data.messageType === "page_loaded") { - particleExplorerTool.webView.eventBridge.emitScriptEvent(JSON.stringify(particleData)); + particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); } }); } else { diff --git a/examples/entityScripts/virtualBaton/batonSimpleEntityScript.js b/examples/entityScripts/virtualBaton/batonSimpleEntityScript.js new file mode 100644 index 0000000000..4074113a46 --- /dev/null +++ b/examples/entityScripts/virtualBaton/batonSimpleEntityScript.js @@ -0,0 +1,96 @@ +(function() { + Script.include("../../libraries/virtualBaton.js"); + Script.include("../../libraries/utils.js"); + + var _this = this; + + + this.startUpdate = function() { + print("EBL START UPDATE"); + Entities.editEntity(_this.batonOwnerIndicator, { + visible: true + }); + + // Change color of box + Entities.editEntity(_this.entityID, { + color: randomColor() + }); + + _this.position = Entities.getEntityProperties(_this.entityID, "position").position; + _this.debugLightProperties.position = Vec3.sum(_this.position, {x: 0, y: 1, z: 0}); + _this.debugLightProperties.color = randomColor(); + var debugLight = Entities.addEntity(_this.debugLightProperties); + Script.setTimeout(function() { + Entities.deleteEntity(debugLight); + }, 500); + + } + + this.maybeClaim = function() { + print("EBL MAYBE CLAIM"); + if (_this.isBatonOwner === true) { + _this.isBatonOwner = false; + } + Entities.editEntity(_this.batonOwnerIndicator, { + visible: false + }); + baton.claim(_this.startUpdate, _this.maybeClaim); + } + + this.unload = function() { + print("EBL UNLOAD"); + baton.unload(); + Entities.deleteEntity(_this.batonOwnerIndicator); + } + + + this.preload = function(entityID) { + print("EBL Preload!!"); + _this.entityID = entityID; + _this.setupDebugEntities(); + + baton = virtualBaton({ + batonName: "batonSimpleEntityScript:" + _this.entityID + }); + _this.isBatonOwner = false; + _this.maybeClaim(); + + } + + + this.setupDebugEntities = function() { + _this.batonOwnerIndicator = Entities.addEntity({ + type: "Box", + color: { + red: 200, + green: 10, + blue: 200 + }, + position: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 0.5, + y: 1, + z: 0 + }, + parentID: MyAvatar.sessionUUID, + visible: false + }); + } + + _this.debugLightProperties = { + type: "Light", + name: "hifi-baton-light", + dimensions: { + x: 10, + y: 10, + z: 10 + }, + falloffRadius: 3, + intensity: 20, + } + +}); \ No newline at end of file diff --git a/examples/entityScripts/virtualBaton/batonSimpleEntitySpawner.js b/examples/entityScripts/virtualBaton/batonSimpleEntitySpawner.js new file mode 100644 index 0000000000..0a3db8f000 --- /dev/null +++ b/examples/entityScripts/virtualBaton/batonSimpleEntitySpawner.js @@ -0,0 +1,33 @@ + var orientation = Camera.getOrientation(); + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); + + // Math.random ensures no caching of script + var SCRIPT_URL = Script.resolvePath("batonSimpleEntityScript.js"); + + var batonBox = Entities.addEntity({ + type: "Box", + name: "hifi-baton-entity", + color: { + red: 200, + green: 200, + blue: 200 + }, + position: center, + dimensions: { + x: 0.1, + y: 0.1, + z: 0.1 + }, + script: SCRIPT_URL + }); + + + + function cleanup() { + Entities.deleteEntity(batonBox); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/html/eventBridgeLoader.js b/examples/html/eventBridgeLoader.js index ebfb6dc740..0e95345b40 100644 --- a/examples/html/eventBridgeLoader.js +++ b/examples/html/eventBridgeLoader.js @@ -9,51 +9,11 @@ // var EventBridge; - -EventBridgeConnectionProxy = function(parent) { - this.parent = parent; - this.realSignal = this.parent.realBridge.scriptEventReceived - this.webWindowId = this.parent.webWindow.windowId; -} - -EventBridgeConnectionProxy.prototype.connect = function(callback) { - var that = this; - this.realSignal.connect(function(id, message) { - if (id === that.webWindowId) { callback(message); } - }); -} - -EventBridgeProxy = function(webWindow) { - this.webWindow = webWindow; - this.realBridge = this.webWindow.eventBridge; - this.scriptEventReceived = new EventBridgeConnectionProxy(this); -} - -EventBridgeProxy.prototype.emitWebEvent = function(data) { - this.realBridge.emitWebEvent(data); -} +var WebChannel; openEventBridge = function(callback) { - EVENT_BRIDGE_URI = "ws://localhost:51016"; - socket = new WebSocket(this.EVENT_BRIDGE_URI); - - socket.onclose = function() { - console.error("web channel closed"); - }; - - socket.onerror = function(error) { - console.error("web channel error: " + error); - }; - - socket.onopen = function() { - channel = new QWebChannel(socket, function(channel) { - console.log("Document url is " + document.URL); - var webWindow = channel.objects[document.URL.toLowerCase()]; - console.log("WebWindow is " + webWindow) - eventBridgeProxy = new EventBridgeProxy(webWindow); - EventBridge = eventBridgeProxy; - if (callback) { callback(eventBridgeProxy); } - }); - } + WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge; + callback(EventBridge); + }); } - diff --git a/examples/html/qmlWebTest.html b/examples/html/qmlWebTest.html index e59535701d..553ce83417 100644 --- a/examples/html/qmlWebTest.html +++ b/examples/html/qmlWebTest.html @@ -4,21 +4,17 @@ - diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index 6c6c0aaecb..06f6f0e88f 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -38,14 +38,14 @@ EntityListTool = function(opts) { type: 'selectionUpdate', selectedIDs: selectedIDs, }; - webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + webView.emitScriptEvent(JSON.stringify(data)); }); that.clearEntityList = function () { var data = { type: 'clearEntityList' } - webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + webView.emitScriptEvent(JSON.stringify(data)); }; that.sendUpdate = function() { @@ -72,11 +72,11 @@ EntityListTool = function(opts) { entities: entities, selectedIDs: selectedIDs, }; - webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + webView.emitScriptEvent(JSON.stringify(data)); } - webView.eventBridge.webEventReceived.connect(function(data) { + webView.webEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type == "selectionUpdate") { var ids = data.entityIds; diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js index fbd2ec7ea0..c002aec3b1 100644 --- a/examples/libraries/gridTool.js +++ b/examples/libraries/gridTool.js @@ -234,11 +234,11 @@ GridTool = function(opts) { }); horizontalGrid.addListener(function(data) { - webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + webView.emitScriptEvent(JSON.stringify(data)); selectionDisplay.updateHandles(); }); - webView.eventBridge.webEventReceived.connect(function(data) { + webView.webEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type == "init") { horizontalGrid.emitUpdate(); diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index f4a431a657..f39f4d7913 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -28,7 +28,6 @@ colorMix = function(colorA, colorB, mix) { } return result; } - scaleLine = function (start, end, scale) { var v = Vec3.subtract(end, start); var length = Vec3.length(v); @@ -262,6 +261,16 @@ randInt = function(low, high) { return Math.floor(randFloat(low, high)); } + +randomColor = function() { + return { + red: randInt(0, 255), + green: randInt(0, 255), + blue: randInt(0, 255) + } +} + + hexToRgb = function(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { diff --git a/examples/particle_explorer/particleExplorerTool.js b/examples/particle_explorer/particleExplorerTool.js index 007eb717ec..8a28445c33 100644 --- a/examples/particle_explorer/particleExplorerTool.js +++ b/examples/particle_explorer/particleExplorerTool.js @@ -26,7 +26,7 @@ ParticleExplorerTool = function() { }); that.webView.setVisible(true); - that.webView.eventBridge.webEventReceived.connect(that.webEventReceived); + that.webView.webEventReceived.connect(that.webEventReceived); } diff --git a/examples/tests/basicEntityTest/entitySpawner.js b/examples/tests/basicEntityTest/entitySpawner.js new file mode 100644 index 0000000000..a2f38f59eb --- /dev/null +++ b/examples/tests/basicEntityTest/entitySpawner.js @@ -0,0 +1,31 @@ + var orientation = Camera.getOrientation(); + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); + + // Math.random ensures no caching of script + var SCRIPT_URL = Script.resolvePath("myEntityScript.js") + + var myEntity = Entities.addEntity({ + type: "Sphere", + color: { + red: 200, + green: 10, + blue: 200 + }, + position: center, + dimensions: { + x: 1, + y: 1, + z: 1 + }, + script: SCRIPT_URL + }) + + + function cleanup() { + // Entities.deleteEntity(myEntity); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/tests/basicEntityTest/myEntityScript.js b/examples/tests/basicEntityTest/myEntityScript.js new file mode 100644 index 0000000000..b4a8885c70 --- /dev/null +++ b/examples/tests/basicEntityTest/myEntityScript.js @@ -0,0 +1,24 @@ + +(function() { + var _this; + MyEntity = function() { + _this = this; + + }; + + MyEntity.prototype = { + + + preload: function(entityID) { + this.entityID = entityID; + var randNum = Math.random().toFixed(3); + print("EBL PRELOAD ENTITY SCRIPT!!!", randNum) + + }, + + + }; + + // entity scripts always need to return a newly constructed object of our type + return new MyEntity(); +}); \ No newline at end of file diff --git a/examples/tests/batonSoundEntityTest/batonSoundTestEntityScript.js b/examples/tests/batonSoundEntityTest/batonSoundTestEntityScript.js new file mode 100644 index 0000000000..910378e04e --- /dev/null +++ b/examples/tests/batonSoundEntityTest/batonSoundTestEntityScript.js @@ -0,0 +1,85 @@ + +(function() { + Script.include("../../libraries/virtualBaton.js"); + + var baton; + + var _this; + BatonSoundEntity = function() { + _this = this; + _this.drumSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Drums/deepdrum1.wav"); + _this.injectorOptions = {position: MyAvatar.position, loop: false, volume: 1}; + _this.soundIntervalConnected = false; + _this.batonDebugModel = Entities.addEntity({ + type: "Box", + color: {red: 200, green: 10, blue: 200}, + position: Vec3.sum(MyAvatar.position, {x: 0, y: 1, z: 0}), + dimensions: {x: 0.5, y: 1, z: 0}, + parentID: MyAvatar.sessionUUID, + visible: false + }); + }; + + function startUpdate() { + // We are claiming the baton! So start our clip + if (!_this.soundInjector) { + // This client hasn't created their injector yet so create one + _this.soundInjector = Audio.playSound(_this.drumSound, _this.injectorOptions); + } else { + // We already have our injector so just restart it + _this.soundInjector.restart(); + } + print("EBL START UPDATE"); + Entities.editEntity(_this.batonDebugModel, {visible: true}); + _this.playSoundInterval = Script.setInterval(function() { + _this.soundInjector.restart(); + }, _this.drumSound.duration * 1000); // Duration is in seconds so convert to ms + _this.soundIntervalConnected = true; + } + + function stopUpdateAndReclaim() { + print("EBL STOP UPDATE AND RECLAIM") + // when the baton is release + if (_this.soundIntervalConnected === true) { + Script.clearInterval(_this.playSoundInterval); + _this.soundIntervalConnected = false; + print("EBL CLEAR INTERVAL") + } + Entities.editEntity(_this.batonDebugModel, {visible: false}); + // hook up callbacks to the baton + baton.claim(startUpdate, stopUpdateAndReclaim); + } + + BatonSoundEntity.prototype = { + + + preload: function(entityID) { + _this.entityID = entityID; + print("EBL PRELOAD ENTITY SCRIPT!!!"); + baton = virtualBaton({ + // One winner for each entity + batonName: "io.highfidelity.soundEntityBatonTest:" + _this.entityID, + // debugFlow: true + }); + stopUpdateAndReclaim(); + }, + + unload: function() { + print("EBL UNLOAD"); + // baton.release(); + baton.unload(); + Entities.deleteEntity(_this.batonDebugModel); + if (_this.soundIntervalConnected === true) { + Script.clearInterval(_this.playSoundInterval); + _this.soundIntervalConnected = false; + _this.soundInjector.stop(); + delete _this.soundInjector; + } + } + + + }; + + // entity scripts always need to return a newly constructed object of our type + return new BatonSoundEntity(); +}); \ No newline at end of file diff --git a/examples/tests/batonSoundEntityTest/batonSoundTestEntitySpawner.js b/examples/tests/batonSoundEntityTest/batonSoundTestEntitySpawner.js new file mode 100644 index 0000000000..fdcef8d32c --- /dev/null +++ b/examples/tests/batonSoundEntityTest/batonSoundTestEntitySpawner.js @@ -0,0 +1,31 @@ + var orientation = Camera.getOrientation(); + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); + + // Math.random ensures no caching of script + var SCRIPT_URL = Script.resolvePath("batonSoundTestEntityScript.js") + + var soundEntity = Entities.addEntity({ + type: "Box", + color: { + red: 200, + green: 10, + blue: 10 + }, + position: center, + dimensions: { + x: 0.1, + y: 0.1, + z: 0.1 + }, + script: SCRIPT_URL + }); + + + function cleanup() { + // Entities.deleteEntity(soundEntity); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js index 5faa68668d..ac40e54b85 100644 --- a/examples/tests/qmlWebTest.js +++ b/examples/tests/qmlWebTest.js @@ -2,32 +2,18 @@ print("Launching web window"); var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html") webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false); -print("JS Side window: " + webWindow); -print("JS Side bridge: " + webWindow.eventBridge); -webWindow.eventBridge.webEventReceived.connect(function(data) { +webWindow.webEventReceived.connect(function(data) { print("JS Side event received: " + data); }); -var titles = ["A", "B", "C"]; -var titleIndex = 0; - Script.setInterval(function() { - webWindow.eventBridge.emitScriptEvent("JS Event sent"); - var size = webWindow.size; - var position = webWindow.position; - print("Window url: " + webWindow.url) - print("Window visible: " + webWindow.visible) - print("Window size: " + size.x + "x" + size.y) - print("Window pos: " + position.x + "x" + position.y) - webWindow.setVisible(!webWindow.visible); - webWindow.setTitle(titles[titleIndex]); - webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100); - titleIndex += 1; - titleIndex %= titles.length; -}, 2 * 1000); + var message = [ Math.random(), Math.random() ]; + print("JS Side sending: " + message); + webWindow.emitScriptEvent(message); +}, 5 * 1000); -Script.setTimeout(function() { - print("Closing script"); +Script.scriptEnding.connect(function(){ webWindow.close(); - Script.stop(); -}, 15 * 1000) + webWindow.deleteLater(); +}); + diff --git a/examples/utilities/tools/render/PlotPerf.qml b/examples/utilities/tools/render/PlotPerf.qml new file mode 100644 index 0000000000..0e100e4e72 --- /dev/null +++ b/examples/utilities/tools/render/PlotPerf.qml @@ -0,0 +1,186 @@ +// +// PlotPerf.qml +// examples/utilities/tools/render +// +// Created by Sam Gateau on 3//2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: root + width: parent.width + height: 100 + property string title + property var config + property string parameters + + // THis is my hack to get the name of the first property and assign it to a trigger var in order to get + // a signal called whenever the value changed + property var trigger: config[parameters.split(":")[3].split("-")[0]] + + property var inputs: parameters.split(":") + property var valueScale: +inputs[0] + property var valueUnit: inputs[1] + property var valueNumDigits: inputs[2] + property var input_VALUE_OFFSET: 3 + property var valueMax : 1 + + property var _values : new Array() + property var tick : 0 + + function createValues() { + if (inputs.length > input_VALUE_OFFSET) { + for (var i = input_VALUE_OFFSET; i < inputs.length; i++) { + var varProps = inputs[i].split("-") + _values.push( { + value: varProps[0], + valueMax: 1, + numSamplesConstantMax: 0, + valueHistory: new Array(), + label: varProps[1], + color: varProps[2], + scale: (varProps.length > 3 ? varProps[3] : 1), + unit: (varProps.length > 4 ? varProps[4] : valueUnit) + }) + } + } + print("in creator" + JSON.stringify(_values)); + + } + + Component.onCompleted: { + createValues(); + print(JSON.stringify(_values)); + + } + + function pullFreshValues() { + //print("pullFreshValues"); + var VALUE_HISTORY_SIZE = 100; + var UPDATE_CANVAS_RATE = 20; + tick++; + + + var currentValueMax = 0 + for (var i = 0; i < _values.length; i++) { + + var currentVal = config[_values[i].value] * _values[i].scale; + _values[i].valueHistory.push(currentVal) + _values[i].numSamplesConstantMax++; + + if (_values[i].valueHistory.length > VALUE_HISTORY_SIZE) { + var lostValue = _values[i].valueHistory.shift(); + if (lostValue >= _values[i].valueMax) { + _values[i].valueMax *= 0.99 + _values[i].numSamplesConstantMax = 0 + } + } + + if (_values[i].valueMax < currentVal) { + _values[i].valueMax = currentVal; + _values[i].numSamplesConstantMax = 0 + } + + if (_values[i].numSamplesConstantMax > VALUE_HISTORY_SIZE) { + _values[i].numSamplesConstantMax = 0 + _values[i].valueMax *= 0.95 // lower slowly the current max if no new above max since a while + } + + if (currentValueMax < _values[i].valueMax) { + currentValueMax = _values[i].valueMax + } + } + + if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { + valueMax = currentValueMax; + } + + if (tick % UPDATE_CANVAS_RATE == 0) { + mycanvas.requestPaint() + } + } + onTriggerChanged: pullFreshValues() + + Canvas { + id: mycanvas + anchors.fill:parent + onPaint: { + var lineHeight = 12; + + function displayValue(val, unit) { + return (val / root.valueScale).toFixed(root.valueNumDigits) + " " + unit + } + + function pixelFromVal(val, valScale) { + return lineHeight + (height - lineHeight) * (1 - (0.9) * val / valueMax); + } + function valueFromPixel(pixY) { + return ((pixY - lineHeight) / (height - lineHeight) - 1) * valueMax / (-0.9); + } + function plotValueHistory(ctx, valHistory, color) { + var widthStep= width / (valHistory.length - 1); + + ctx.beginPath(); + ctx.strokeStyle= color; // Green path + ctx.lineWidth="2"; + ctx.moveTo(0, pixelFromVal(valHistory[0])); + + for (var i = 1; i < valHistory.length; i++) { + ctx.lineTo(i * widthStep, pixelFromVal(valHistory[i])); + } + + ctx.stroke(); + } + function displayValueLegend(ctx, val, num) { + ctx.fillStyle = val.color; + var bestValue = val.valueHistory[val.valueHistory.length -1]; + ctx.textAlign = "right"; + ctx.fillText(displayValue(bestValue, val.unit), width, (num + 2) * lineHeight * 1.5); + ctx.textAlign = "left"; + ctx.fillText(val.label, 0, (num + 2) * lineHeight * 1.5); + } + + function displayTitle(ctx, text, maxVal) { + ctx.fillStyle = "grey"; + ctx.textAlign = "right"; + ctx.fillText(displayValue(valueFromPixel(lineHeight), root.valueUnit), width, lineHeight); + + ctx.fillStyle = "white"; + ctx.textAlign = "left"; + ctx.fillText(text, 0, lineHeight); + } + function displayBackground(ctx) { + ctx.fillStyle = Qt.rgba(0, 0, 0, 0.6); + ctx.fillRect(0, 0, width, height); + + ctx.strokeStyle= "grey"; + ctx.lineWidth="2"; + + ctx.beginPath(); + ctx.moveTo(0, lineHeight + 1); + ctx.lineTo(width, lineHeight + 1); + ctx.moveTo(0, height); + ctx.lineTo(width, height); + ctx.stroke(); + } + + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + ctx.font="12px Verdana"; + + displayBackground(ctx); + + for (var i = 0; i < _values.length; i++) { + plotValueHistory(ctx, _values[i].valueHistory, _values[i].color) + displayValueLegend(ctx, _values[i], i) + } + + displayTitle(ctx, title, valueMax) + } + } +} diff --git a/examples/utilities/tools/render/renderStats.js b/examples/utilities/tools/render/renderStats.js new file mode 100644 index 0000000000..2e8487ca34 --- /dev/null +++ b/examples/utilities/tools/render/renderStats.js @@ -0,0 +1,21 @@ +// +// renderStats.js +// examples/utilities/tools/render +// +// Sam Gateau, created on 3/22/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 +// + +// Set up the qml ui +var qml = Script.resolvePath('stats.qml'); +var window = new OverlayWindow({ + title: 'Render Stats', + source: qml, + width: 300, + height: 200 +}); +window.setPosition(500, 50); +window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/examples/utilities/tools/render/stats.qml b/examples/utilities/tools/render/stats.qml new file mode 100644 index 0000000000..142b5e25e2 --- /dev/null +++ b/examples/utilities/tools/render/stats.qml @@ -0,0 +1,69 @@ +// +// stats.qml +// examples/utilities/tools/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + + +Item { + id: statsUI + anchors.fill:parent + + Column { + id: stats + spacing: 8 + anchors.fill:parent + + property var config: Render.getConfig("Stats") + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + return (height - spacing * (children.length - 1)) / children.length + } + + PlotPerf { + title: "Num Buffers" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:bufferCPUCount-CPU-#00B4EF:bufferGPUCount-GPU-#1AC567" + } + PlotPerf { + title: "gpu::Buffer Memory" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1048576:Mb:1:bufferCPUMemoryUsage-CPU-#00B4EF:bufferGPUMemoryUsage-GPU-#1AC567" + } + + PlotPerf { + title: "Num Textures" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:textureCPUCount-CPU-#00B4EF:textureGPUCount-GPU-#1AC567:frameTextureCount-Frame-#E2334D" + } + PlotPerf { + title: "gpu::Texture Memory" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1048576:Mb:1:textureCPUMemoryUsage-CPU-#00B4EF:textureGPUMemoryUsage-GPU-#1AC567" + } + PlotPerf { + title: "Drawcalls" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:frameDrawcallCount-frame-#E2334D:frameDrawcallRate-rate-#1AC567-0.001-K/s" + } + PlotPerf { + title: "Triangles" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1000:K:0:frameTriangleCount-frame-#E2334D:frameTriangleRate-rate-#1AC567-0.001-MT/s" + } + } +} diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index f38923b873..d7fba12f26 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -11,7 +11,6 @@ #include "IceServer.h" -#include #include #include @@ -68,7 +67,9 @@ bool IceServer::packetVersionMatch(const udt::Packet& packet) { } void IceServer::processPacket(std::unique_ptr packet) { - + + _lastPacketTimestamp = QDateTime::currentMSecsSinceEpoch(); + auto nlPacket = NLPacket::fromBase(std::move(packet)); // make sure that this packet at least looks like something we can read @@ -161,15 +162,12 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { } bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) { - // check if we have a private key for this domain ID - if we do not then fire off the request for it + // check if we have a public key for this domain ID - if we do not then fire off the request for it auto it = _domainPublicKeys.find(domainID); if (it != _domainPublicKeys.end()) { // attempt to verify the signature for this heartbeat - const unsigned char* publicKeyData = reinterpret_cast(it->second.constData()); - - // first load up the public key into an RSA struct - RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size()); + const auto rsaPublicKey = it->second.get(); if (rsaPublicKey) { auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256); @@ -180,9 +178,6 @@ bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& pla signature.size(), rsaPublicKey); - // free up the public key and remove connection token before we return - RSA_free(rsaPublicKey); - if (verificationResult == 1) { // this is the only success case - we return true here to indicate that the heartbeat is verified return true; @@ -192,7 +187,7 @@ bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& pla } else { // we can't let this user in since we couldn't convert their public key to an RSA key we could use - qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key."; + qWarning() << "Public key for" << domainID << "is not a usable RSA* public key."; qWarning() << "Re-requesting public key from API"; } } @@ -240,7 +235,22 @@ void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) { auto dataObject = responseObject[DATA_KEY].toObject(); if (dataObject.contains(PUBLIC_KEY_KEY)) { - _domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8()); + + // grab the base 64 public key from the API response + auto apiPublicKey = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8()); + + // convert the downloaded public key to an RSA struct, if possible + const unsigned char* publicKeyData = reinterpret_cast(apiPublicKey.constData()); + + RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, apiPublicKey.size()); + + if (rsaPublicKey) { + _domainPublicKeys[domainID] = { rsaPublicKey, RSA_free }; + } else { + qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key."; + qWarning() << "Public key will be re-requested on next heartbeat."; + } + } else { qWarning() << "There was no public key present in response for domain with ID" << domainID; } @@ -254,6 +264,8 @@ void IceServer::publicKeyReplyFinished(QNetworkReply* reply) { qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString(); } + + reply->deleteLater(); } void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { @@ -274,6 +286,11 @@ void IceServer::clearInactivePeers() { if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) { qDebug() << "Removing peer from memory for inactivity -" << *peer; + + // if we had a public key for this domain, remove it now + _domainPublicKeys.erase(peer->getUUID()); + + // remove the peer object peerItem = _activePeers.erase(peerItem); } else { // we didn't kill this peer, push the iterator forwards @@ -288,7 +305,14 @@ bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, b if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (url.path() == "/status") { - connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size())); + // figure out if we respond with 0 (we're good) or 1 (we think we're in trouble) + + const quint64 MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET = 10 * 1000; + + int statusNumber = (QDateTime::currentMSecsSinceEpoch() - _lastPacketTimestamp > MAX_PACKET_GAP_MS_FOR_STUCK_SOCKET) + ? 1 : 0; + + connection->respond(HTTPConnection::StatusCode200, QByteArray::number(statusNumber)); } } return true; diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 81234b2c3c..6cc33fd8fc 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -16,6 +16,8 @@ #include #include +#include + #include #include @@ -52,8 +54,11 @@ private: HTTPManager _httpManager; - using DomainPublicKeyHash = std::unordered_map; + using RSAUniquePtr = std::unique_ptr>; + using DomainPublicKeyHash = std::unordered_map; DomainPublicKeyHash _domainPublicKeys; + + quint64 _lastPacketTimestamp; }; #endif // hifi_IceServer_h diff --git a/interface/icon/interface-beta.icns b/interface/icon/interface-beta.icns index af4f7cf499..1e2a4baeaa 100644 Binary files a/interface/icon/interface-beta.icns and b/interface/icon/interface-beta.icns differ diff --git a/interface/icon/interface-beta.ico b/interface/icon/interface-beta.ico index 4d59dab12e..1ed1ebddb9 100644 Binary files a/interface/icon/interface-beta.ico and b/interface/icon/interface-beta.ico differ diff --git a/interface/icon/interface.icns b/interface/icon/interface.icns index bc2eff31e5..332539af2a 100644 Binary files a/interface/icon/interface.icns and b/interface/icon/interface.icns differ diff --git a/interface/icon/interface.ico b/interface/icon/interface.ico index 4799dc7c0d..e3d852cb41 100644 Binary files a/interface/icon/interface.ico and b/interface/icon/interface.ico differ diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index fd4e629568..70c8afd298 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -1,6 +1,7 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 +import QtWebChannel 1.0 import "windows" as Windows import "controls" as Controls @@ -15,11 +16,24 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url + property alias eventBridge: eventBridgeWrapper.eventBridge; + + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; + } + + // This is for JS/QML communication, which is unused in a WebWindow, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); Controls.WebView { id: webview url: "about:blank" anchors.fill: parent focus: true + webChannel.registeredObjects: [eventBridgeWrapper] } } // dialog diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index ed8ff93f19..0420cd2e88 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -20,6 +20,7 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property var source; + property var eventBridge; property var component; property var dynamicContent; onSourceChanged: { diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 75aa50aa34..7398057722 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1 - +import QtWebChannel 1.0 import Qt.labs.settings 1.0 import "windows" as Windows @@ -37,14 +37,26 @@ Windows.Window { Repeater { model: 4 Tab { + // Force loading of the content even if the tab is not visible + // (required for letting the C++ code access the webview) active: true - enabled: false; - // we need to store the original url here for future identification + enabled: false property string originalUrl: ""; - onEnabledChanged: toolWindow.updateVisiblity(); + Controls.WebView { id: webView; anchors.fill: parent + enabled: false + property alias eventBridgeWrapper: eventBridgeWrapper + + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; + } + + webChannel.registeredObjects: [eventBridgeWrapper] + onEnabledChanged: toolWindow.updateVisiblity(); } } } @@ -113,20 +125,23 @@ Windows.Window { var tab = tabView.getTab(index); tab.title = ""; - tab.originalUrl = ""; tab.enabled = false; + tab.originalUrl = ""; + tab.item.url = "about:blank"; + tab.item.enabled = false; } function addWebTab(properties) { if (!properties.source) { - console.warn("Attempted to open Web Tool Pane without URL") + console.warn("Attempted to open Web Tool Pane without URL"); return; } var existingTabIndex = findIndexForUrl(properties.source); if (existingTabIndex >= 0) { - console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source) - return tabView.getTab(existingTabIndex); + console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source); + var tab = tabView.getTab(existingTabIndex); + return tab.item; } var freeTabIndex = findFreeTab(); @@ -135,25 +150,28 @@ Windows.Window { return; } - var newTab = tabView.getTab(freeTabIndex); - newTab.title = properties.title || "Unknown"; - newTab.originalUrl = properties.source; - newTab.item.url = properties.source; - newTab.active = true; - if (properties.width) { - tabView.width = Math.min(Math.max(tabView.width, properties.width), - toolWindow.maxSize.x); + tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x); } if (properties.height) { - tabView.height = Math.min(Math.max(tabView.height, properties.height), - toolWindow.maxSize.y); + tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y); } - console.log("Updating visibility based on child tab added"); - newTab.enabledChanged.connect(updateVisiblity) - updateVisiblity(); - return newTab + var tab = tabView.getTab(freeTabIndex); + tab.title = properties.title || "Unknown"; + tab.enabled = true; + console.log("New tab URL: " + properties.source) + tab.originalUrl = properties.source; + + var eventBridge = properties.eventBridge; + console.log("Event bridge: " + eventBridge); + + var result = tab.item; + result.enabled = true; + console.log("Setting event bridge: " + eventBridge); + result.eventBridgeWrapper.eventBridge = eventBridge; + result.url = properties.source; + return result; } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 18080cd448..1361e6e322 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -59,6 +59,7 @@ WebEngineView { request.openIn(newWindow.webView) } - - profile: desktop.browserProfile + // 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/src/Application.cpp b/interface/src/Application.cpp index b9effa444b..7eb19557e5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -240,25 +240,26 @@ class DeadlockWatchdogThread : public QThread { public: static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; - static const unsigned long MAX_HEARTBEAT_AGE_USECS = 15 * USECS_PER_SECOND; - + static const unsigned long HEARTBEAT_REPORT_INTERVAL_USECS = 5 * USECS_PER_SECOND; + static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND; + static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large + static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples + // Set the heartbeat on launch DeadlockWatchdogThread() { setObjectName("Deadlock Watchdog"); - QTimer* heartbeatTimer = new QTimer(); // Give the heartbeat an initial value - updateHeartbeat(); - connect(heartbeatTimer, &QTimer::timeout, [this] { - updateHeartbeat(); - }); - heartbeatTimer->start(HEARTBEAT_UPDATE_INTERVAL_SECS * MSECS_PER_SECOND); + _heartbeat = usecTimestampNow(); connect(qApp, &QCoreApplication::aboutToQuit, [this] { _quit = true; }); } void updateHeartbeat() { - _heartbeat = usecTimestampNow(); + auto now = usecTimestampNow(); + auto elapsed = now - _heartbeat; + _movingAverage.addSample(elapsed); + _heartbeat = now; } void deadlockDetectionCrash() { @@ -269,10 +270,52 @@ public: void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); + + uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us + uint64_t now = usecTimestampNow(); + auto lastHeartbeatAge = (now > lastHeartbeat) ? now - lastHeartbeat : 0; + auto sinceLastReport = (now > _lastReport) ? now - _lastReport : 0; + auto elapsedMovingAverage = _movingAverage.getAverage(); + + if (elapsedMovingAverage > _maxElapsedAverage) { + qDebug() << "DEADLOCK WATCHDOG NEW maxElapsedAverage:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage + << "NEW maxElapsedAverage:" << elapsedMovingAverage + << "samples:" << _movingAverage.getSamples(); + _maxElapsedAverage = elapsedMovingAverage; + } + if (lastHeartbeatAge > _maxElapsed) { + qDebug() << "DEADLOCK WATCHDOG NEW maxElapsed:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "PREVIOUS maxElapsed:" << _maxElapsed + << "NEW maxElapsed:" << lastHeartbeatAge + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); + _maxElapsed = lastHeartbeatAge; + } + if ((sinceLastReport > HEARTBEAT_REPORT_INTERVAL_USECS) || (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT)) { + qDebug() << "DEADLOCK WATCHDOG STATUS -- lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); + _lastReport = now; + } + #ifdef NDEBUG - auto now = usecTimestampNow(); - auto lastHeartbeatAge = now - _heartbeat; if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { + qDebug() << "DEADLOCK DETECTED -- " + << "lastHeartbeatAge:" << lastHeartbeatAge + << "[ lastHeartbeat :" << lastHeartbeat + << "now:" << now << " ]" + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); deadlockDetectionCrash(); } #endif @@ -280,10 +323,19 @@ public: } static std::atomic _heartbeat; + static std::atomic _lastReport; + static std::atomic _maxElapsed; + static std::atomic _maxElapsedAverage; + static ThreadSafeMovingAverage _movingAverage; + bool _quit { false }; }; std::atomic DeadlockWatchdogThread::_heartbeat; +std::atomic DeadlockWatchdogThread::_lastReport; +std::atomic DeadlockWatchdogThread::_maxElapsed; +std::atomic DeadlockWatchdogThread::_maxElapsedAverage; +ThreadSafeMovingAverage DeadlockWatchdogThread::_movingAverage; #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { @@ -1381,6 +1433,8 @@ void Application::initializeUi() { void Application::paintGL() { + updateHeartbeat(); + // Some plugins process message events, potentially leading to // re-entering a paint event. don't allow further processing if this // happens @@ -2502,6 +2556,8 @@ static uint32_t _renderedFrameIndex { INVALID_FRAME }; void Application::idle(uint64_t now) { + updateHeartbeat(); + if (_aboutToQuit || _inPaint) { return; // bail early, nothing to do here. } @@ -4830,13 +4886,39 @@ void Application::updateDisplayMode() { { std::unique_lock lock(_displayPluginLock); + auto oldDisplayPlugin = _displayPlugin; if (_displayPlugin) { _displayPlugin->deactivate(); } // FIXME probably excessive and useless context switching _offscreenContext->makeCurrent(); - newDisplayPlugin->activate(); + + bool active = newDisplayPlugin->activate(); + + if (!active) { + // If the new plugin fails to activate, fallback to last display + qWarning() << "Failed to activate display: " << newDisplayPlugin->getName(); + newDisplayPlugin = oldDisplayPlugin; + + if (newDisplayPlugin) { + qWarning() << "Falling back to last display: " << newDisplayPlugin->getName(); + active = newDisplayPlugin->activate(); + } + + // If there is no last display, or + // If the last display fails to activate, fallback to desktop + if (!active) { + newDisplayPlugin = displayPlugins.at(0); + qWarning() << "Falling back to display: " << newDisplayPlugin->getName(); + active = newDisplayPlugin->activate(); + } + + if (!active) { + qFatal("Failed to activate fallback plugin"); + } + } + _offscreenContext->makeCurrent(); offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); _offscreenContext->makeCurrent(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 6db53de0d8..e67f95e2e3 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -23,6 +23,7 @@ #include "AddressManager.h" #include "Application.h" #include "InterfaceLogging.h" +#include "UserActivityLogger.h" #include "MainWindow.h" #ifdef HAS_BUGSPLAT @@ -102,11 +103,19 @@ int main(int argc, const char* argv[]) { // Check OpenGL version. // This is done separately from the main Application so that start-up and shut-down logic within the main Application is // not made more complicated than it already is. + bool override = false; + QString glVersion; { OpenGLVersionChecker openGLVersionChecker(argc, const_cast(argv)); - if (!openGLVersionChecker.isValidVersion()) { - qCDebug(interfaceapp, "Early exit due to OpenGL version."); - return 0; + bool valid = true; + glVersion = openGLVersionChecker.checkVersion(valid, override); + if (!valid) { + if (override) { + qCDebug(interfaceapp, "Running on insufficient OpenGL version: %s.", glVersion.toStdString().c_str()); + } else { + qCDebug(interfaceapp, "Early exit due to OpenGL version."); + return 0; + } } } @@ -134,6 +143,22 @@ int main(int argc, const char* argv[]) { QSettings::setDefaultFormat(QSettings::IniFormat); Application app(argc, const_cast(argv), startupTime); + // If we failed the OpenGLVersion check, log it. + if (override) { + auto& accountManager = AccountManager::getInstance(); + if (accountManager.isLoggedIn()) { + UserActivityLogger::getInstance().insufficientGLVersion(glVersion); + } else { + QObject::connect(&AccountManager::getInstance(), &AccountManager::loginComplete, [glVersion](){ + static bool loggedInsufficientGL = false; + if (!loggedInsufficientGL) { + UserActivityLogger::getInstance().insufficientGLVersion(glVersion); + loggedInsufficientGL = true; + } + }); + } + } + // Setup local server QLocalServer server { &app }; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 63cb1b80a4..3e3efec4a2 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -19,9 +19,7 @@ const QString Basic2DWindowOpenGLDisplayPlugin::NAME("Desktop"); static const QString FULLSCREEN = "Fullscreen"; -void Basic2DWindowOpenGLDisplayPlugin::internalActivate() { - Parent::internalActivate(); - +bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { _framerateActions.clear(); _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), FULLSCREEN, [this](bool clicked) { @@ -33,6 +31,8 @@ void Basic2DWindowOpenGLDisplayPlugin::internalActivate() { }, true, false); updateFramerate(); + + return Parent::internalActivate(); } void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) { diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 90b32a1924..40c6eb4381 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -22,7 +22,7 @@ public: virtual float getTargetFrameRate() override { return _framerateTarget ? (float) _framerateTarget : TARGET_FRAMERATE_Basic2DWindowOpenGL; } - virtual void internalActivate() override; + virtual bool internalActivate() override; virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d842dc553b..8049e2d5a5 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -219,7 +219,7 @@ void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& scen } -void OpenGLDisplayPlugin::activate() { +bool OpenGLDisplayPlugin::activate() { if (!_cursorsData.size()) { auto& cursorManager = Cursor::Manager::instance(); for (const auto iconId : cursorManager.registeredIcons()) { @@ -238,7 +238,9 @@ void OpenGLDisplayPlugin::activate() { // Child classes may override this in order to do things like initialize // libraries, etc - internalActivate(); + if (!internalActivate()) { + return false; + } #if THREADED_PRESENT @@ -263,7 +265,8 @@ void OpenGLDisplayPlugin::activate() { customizeContext(); _container->makeRenderingContextCurrent(); #endif - DisplayPlugin::activate(); + + return DisplayPlugin::activate(); } void OpenGLDisplayPlugin::deactivate() { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 7295b07ad3..b9628deb6c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -32,7 +32,7 @@ public: // These must be final to ensure proper ordering of operations // between the main thread and the presentation thread - void activate() override final; + bool activate() override final; void deactivate() override final; bool eventFilter(QObject* receiver, QEvent* event) override; @@ -77,7 +77,8 @@ protected: virtual void customizeContext(); virtual void uncustomizeContext(); - virtual void internalActivate() {} + // Returns true on successful activation + virtual bool internalActivate() { return true; } virtual void internalDeactivate() {} virtual void cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture); // Plugin specific functionality to send the composed scene to the output window or device diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index b022b10887..5be3f0d96a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -32,7 +32,7 @@ glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; } -void HmdDisplayPlugin::internalActivate() { +bool HmdDisplayPlugin::internalActivate() { _monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW); _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), MONO_PREVIEW, @@ -41,7 +41,8 @@ void HmdDisplayPlugin::internalActivate() { _container->setBoolSetting("monoPreview", _monoPreview); }, true, _monoPreview); _container->removeMenu(FRAMERATE); - Parent::internalActivate(); + + return Parent::internalActivate(); } void HmdDisplayPlugin::customizeContext() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index fede16c3a5..080a44bc66 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -33,7 +33,7 @@ protected: virtual bool isHmdMounted() const = 0; virtual void postPreview() {}; - void internalActivate() override; + bool internalActivate() override; void compositeOverlay() override; void compositePointer() override; void internalPresent() override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index ae9f18e20d..66a4ba4835 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -58,7 +58,7 @@ glm::mat4 StereoDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& basePr static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; std::vector _screenActions; -void StereoDisplayPlugin::internalActivate() { +bool StereoDisplayPlugin::internalActivate() { auto screens = qApp->screens(); _screenActions.resize(screens.size()); for (int i = 0; i < screens.size(); ++i) { @@ -77,7 +77,8 @@ void StereoDisplayPlugin::internalActivate() { _screen = qApp->primaryScreen(); _container->setFullscreen(_screen); - Parent::internalActivate(); + + return Parent::internalActivate(); } void StereoDisplayPlugin::updateScreen() { diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 6563c2ae89..3b481dce97 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -29,7 +29,7 @@ public: // virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; protected: - virtual void internalActivate() override; + virtual bool internalActivate() override; virtual void internalDeactivate() override; void updateScreen(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d6835502d0..ae29d1faf7 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -333,7 +333,9 @@ void RenderableModelEntityItem::updateModelBounds() { bool movingOrAnimating = isMovingRelativeToParent() || isAnimatingSomething(); if ((movingOrAnimating || _needsInitialSimulation || + _needsJointSimulation || _model->getTranslation() != getPosition() || + _model->getScaleToFitDimensions() != getDimensions() || _model->getRotation() != getRotation() || _model->getRegistrationPoint() != getRegistrationPoint()) && _model->isActive() && _dimensionsInitialized) { @@ -349,6 +351,7 @@ void RenderableModelEntityItem::updateModelBounds() { } _needsInitialSimulation = false; + _needsJointSimulation = false; } } @@ -738,6 +741,7 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, _absoluteJointRotationsInObjectFrameSet[index] = true; _absoluteJointRotationsInObjectFrameDirty[index] = true; result = true; + _needsJointSimulation = true; } }); return result; @@ -753,11 +757,33 @@ bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int ind _absoluteJointTranslationsInObjectFrameSet[index] = true; _absoluteJointTranslationsInObjectFrameDirty[index] = true; result = true; + _needsJointSimulation = true; } }); return result; } +void RenderableModelEntityItem::setJointRotations(const QVector& rotations) { + ModelEntityItem::setJointRotations(rotations); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { + ModelEntityItem::setJointRotationsSet(rotationsSet); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointTranslations(const QVector& translations) { + ModelEntityItem::setJointTranslations(translations); + _needsJointSimulation = true; +} + +void RenderableModelEntityItem::setJointTranslationsSet(const QVector& translationsSet) { + ModelEntityItem::setJointTranslationsSet(translationsSet); + _needsJointSimulation = true; +} + + void RenderableModelEntityItem::locationChanged() { EntityItem::locationChanged(); if (_model && _model->isActive()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 03aa9124d5..e88b239e41 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -69,6 +69,11 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override; virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override; + virtual void setJointRotations(const QVector& rotations) override; + virtual void setJointRotationsSet(const QVector& rotationsSet) override; + virtual void setJointTranslations(const QVector& translations) override; + virtual void setJointTranslationsSet(const QVector& translationsSet) override; + virtual void loader() override; virtual void locationChanged() override; @@ -96,6 +101,8 @@ private: bool _showCollisionHull = false; bool getAnimationFrame(); + + bool _needsJointSimulation { false }; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 5118664268..c9c4c8503a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -55,9 +55,58 @@ gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr; const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; + +/* + A PolyVoxEntity has several interdependent parts: + + _voxelData -- compressed QByteArray representation of which voxels have which values + _volData -- datastructure from the PolyVox library which holds which voxels have which values + _mesh -- renderable representation of the voxels + _shape -- used for bullet collisions + + Each one depends on the one before it, except that _voxelData is set from _volData if a script edits the voxels. + + There are booleans to indicate that something has been updated and the dependents now need to be updated. + + _voxelDataDirty + _volDataDirty + _meshDirty + + In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain. + decompressVolumeData() is called to decompress _voxelData into _volData. getMesh() is called to invoke the + polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE + is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on + the surface style. + + When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to + send a packet to the entity-server. + + decompressVolumeData, getMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive + to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread + finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step. + + polyvoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox, + the resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the + mesh. + + If a polyvox entity is "edged", the voxel space is wrapped in an extra layer of zero-valued voxels. This avoids the + previously mentioned gaps along the edges. + + Non-edged polyvox entities can be told about their neighbors in all 6 cardinal directions. On the positive + edges of the polyvox, the values are set from the (negative edge of) relevant neighbor so that their meshes + knit together. This is handled by bonkNeighbors and copyUpperEdgesFromNeighbors. In these functions, variable + names have XP for x-positive, XN x-negative, etc. + + */ + + + + + EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderablePolyVoxEntityItem(entityID) }; entity->setProperties(properties); + std::static_pointer_cast(entity)->initializePolyVox(); return entity; } @@ -68,13 +117,19 @@ RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& ent _xTexture(nullptr), _yTexture(nullptr), _zTexture(nullptr) { - setVoxelVolumeSize(_voxelVolumeSize); - getMeshAsync(); } RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { + withWriteLock([&] { + if (_volData) { + delete _volData; + } + }); } +void RenderablePolyVoxEntityItem::initializePolyVox() { + setVoxelVolumeSize(_voxelVolumeSize); +} bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { switch (surfaceStyle) { @@ -88,59 +143,70 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) { return false; } - void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - _voxelDataLock.lockForWrite(); - if (_voxelData == voxelData) { - _voxelDataLock.unlock(); - return; - } - - _voxelData = voxelData; - _voxelDataDirty = true; - _voxelDataLock.unlock(); - decompressVolumeData(); + // compressed voxel information from the entity-server + withWriteLock([&] { + if (_voxelData != voxelData) { + _voxelData = voxelData; + _voxelDataDirty = true; + } + }); } - void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { - if (_voxelSurfaceStyle == voxelSurfaceStyle) { - return; - } + // this controls whether the polyvox surface extractor does marching-cubes or makes a cubic mesh. It + // also determines if the extra "edged" layer is used. + bool volSizeChanged = false; - // if we are switching to or from "edged" we need to force a resize of _volData. - bool wasEdged = isEdged(_voxelSurfaceStyle); - bool willBeEdged = isEdged(voxelSurfaceStyle); - - if (wasEdged != willBeEdged) { - _volDataLock.lockForWrite(); - _volDataDirty = true; - if (_volData) { - delete _volData; + withWriteLock([&] { + if (_voxelSurfaceStyle == voxelSurfaceStyle) { + return; } - _volData = nullptr; - _voxelSurfaceStyle = voxelSurfaceStyle; - _volDataLock.unlock(); + + // if we are switching to or from "edged" we need to force a resize of _volData. + bool wasEdged = isEdged(_voxelSurfaceStyle); + bool willBeEdged = isEdged(voxelSurfaceStyle); + + if (wasEdged != willBeEdged) { + _volDataDirty = true; + if (_volData) { + delete _volData; + } + _volData = nullptr; + _voxelSurfaceStyle = voxelSurfaceStyle; + _voxelDataDirty = true; + volSizeChanged = true; + } else { + _volDataDirty = true; + _voxelSurfaceStyle = voxelSurfaceStyle; + } + }); + + if (volSizeChanged) { + // setVoxelVolumeSize will re-alloc _volData with the right size setVoxelVolumeSize(_voxelVolumeSize); - decompressVolumeData(); - } else { - _voxelSurfaceStyle = voxelSurfaceStyle; - getMesh(); } } - glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units - if (isEdged(_voxelSurfaceStyle)) { - return scale / -2.0f; - } - return scale / 2.0f; + glm::vec3 result; + withReadLock([&] { + glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + if (isEdged(_voxelSurfaceStyle)) { + result = scale / -2.0f; + } + return scale / 2.0f; + }); + return result; } - glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { - glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + + glm::vec3 scale = getDimensions() / voxelVolumeSize; // meters / voxel-units bool success; // TODO -- Does this actually have to happen in world space? glm::vec3 center = getCenterPosition(success); // this handles registrationPoint changes glm::vec3 position = getPosition(success); @@ -168,18 +234,15 @@ glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { return worldToModelMatrix; } - bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) { if (_locked) { return false; } - _volDataLock.lockForWrite(); - bool result = setVoxelInternal(x, y, z, toValue); - if (result) { - _volDataDirty = true; - } - _volDataLock.unlock(); + bool result = false; + withWriteLock([&] { + result = setVoxelInternal(x, y, z, toValue); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -187,6 +250,21 @@ bool RenderablePolyVoxEntityItem::setVoxel(int x, int y, int z, uint8_t toValue) return result; } +void RenderablePolyVoxEntityItem::forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk) { + // a thread-safe way for code outside this class to iterate over a range of voxels + withReadLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + uint8_t uVoxelValue = getVoxelInternal(x, y, z); + thunk(x, y, z, uVoxelValue); + } + } + } + }); +} + bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { bool result = false; @@ -194,16 +272,15 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) { return result; } - _volDataLock.lockForWrite(); - _volDataDirty = true; - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -224,17 +301,15 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const int yHigh = std::max(std::min(yLow + (int)roundf(cuboidSize.y), (int)roundf(_voxelVolumeSize.y)), yLow); int zHigh = std::max(std::min(zLow + (int)roundf(cuboidSize.z), (int)roundf(_voxelVolumeSize.z)), zLow); - _volDataLock.lockForWrite(); - _volDataDirty = true; - - for (int x = xLow; x < xHigh; x++) { - for (int y = yLow; y < yHigh; y++) { - for (int z = zLow; z < zHigh; z++) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int x = xLow; x < xHigh; x++) { + for (int y = yLow; y < yHigh; y++) { + for (int z = zLow; z < zHigh; z++) { + result |= setVoxelInternal(x, y, z, toValue); + } } } - } - _volDataLock.unlock(); + }); if (result) { compressVolumeDataAndSendEditPacket(); } @@ -259,28 +334,25 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi } // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates - // And compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(pos, center); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radius) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec3 pos(x + 0.5f, y + 0.5f, z + 0.5f); // consider voxels cenetered on their coordinates + // And compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(pos, center); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radius) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -294,30 +366,27 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r glm::mat4 vtwMatrix = voxelToWorldMatrix(); // This three-level for loop iterates over every voxel in the volume - _volDataLock.lockForWrite(); - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - // Store our current position as a vector... - glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates - // convert to world coordinates - glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); - // compute how far the current position is from the center of the volume - float fDistToCenter = glm::distance(worldPos, centerWorldCoords); - // If the current voxel is less than 'radius' units from the center then we set its value - if (fDistToCenter <= radiusWorldCoords) { - result |= setVoxelInternal(x, y, z, toValue); + withWriteLock([&] { + for (int z = 0; z < _voxelVolumeSize.z; z++) { + for (int y = 0; y < _voxelVolumeSize.y; y++) { + for (int x = 0; x < _voxelVolumeSize.x; x++) { + // Store our current position as a vector... + glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates + // convert to world coordinates + glm::vec3 worldPos = glm::vec3(vtwMatrix * pos); + // compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(worldPos, centerWorldCoords); + // If the current voxel is less than 'radius' units from the center then we set its value + if (fDistToCenter <= radiusWorldCoords) { + result |= setVoxelInternal(x, y, z, toValue); + } } } } - } + }); if (result) { - _volDataDirty = true; - _volDataLock.unlock(); compressVolumeDataAndSendEditPacket(); - } else { - _volDataLock.unlock(); } return result; } @@ -399,7 +468,8 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); + bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), + voxelDistance, face, surfaceNormal); glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0); glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint; @@ -414,13 +484,15 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn PolyVox::Vector3DFloat startPoint(originInVoxel.x, originInVoxel.y, originInVoxel.z); PolyVox::Vector3DFloat endPoint(farInVoxel.x, farInVoxel.y, farInVoxel.z); - _volDataLock.lockForRead(); - RaycastFunctor callback(_volData); - PolyVox::RaycastResult raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); - _volDataLock.unlock(); + PolyVox::RaycastResult raycastResult; + withReadLock([&] { + RaycastFunctor callback(_volData); + raycastResult = PolyVox::raycastWithEndpoints(_volData, startPoint, endPoint, callback); + + // result is in voxel-space coordinates. + result = callback._result; + }); - // result is in voxel-space coordinates. - result = callback._result; return raycastResult; } @@ -438,19 +510,22 @@ void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value } bool RenderablePolyVoxEntityItem::isReadyToComputeShape() { - _meshLock.lockForRead(); - if (_meshDirty) { - _meshLock.unlock(); + // we determine if we are ready to compute the physics shape by actually doing so. + // if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their + // threads to finish before creating the collision shape. + if (_meshDirty && !_voxelDataDirty && !_volDataDirty) { + _meshDirty = false; computeShapeInfoWorker(); return false; } - _meshLock.unlock(); return true; } void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { - QReadLocker(&this->_shapeInfoLock); - info = _shapeInfo; + // the shape was actually computed in isReadyToComputeShape. Just hand it off, here. + withWriteLock([&] { + info = _shapeInfo; + }); } void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) { @@ -479,18 +554,29 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::PolyVox); Q_ASSERT(args->_batch); - _volDataLock.lockForRead(); - if (_volDataDirty) { - _volDataLock.unlock(); + bool voxelDataDirty; + bool volDataDirty; + withWriteLock([&] { + voxelDataDirty = _voxelDataDirty; + volDataDirty = _volDataDirty; + if (_voxelDataDirty) { + _voxelDataDirty = false; + } else if (_volDataDirty) { + _volDataDirty = false; + } + }); + if (voxelDataDirty) { + decompressVolumeData(); + } else if (volDataDirty) { getMesh(); - } else { - _volDataLock.unlock(); } - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - _meshLock.unlock(); + model::MeshPointer mesh; + glm::vec3 voxelVolumeSize; + withReadLock([&] { + mesh = _mesh; + voxelVolumeSize = _voxelVolumeSize; + }); if (!_pipeline) { gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); @@ -552,7 +638,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { } int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); - batch._glUniform3f(voxelVolumeSizeLocation, _voxelVolumeSize.x, _voxelVolumeSize.y, _voxelVolumeSize.z); + batch._glUniform3f(voxelVolumeSizeLocation, voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)mesh->getNumIndices(), 0); } @@ -636,49 +722,52 @@ glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& local void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - if (_volData && _voxelVolumeSize == voxelVolumeSize) { - return; - } + // This controls how many individual voxels are in the entity. This is unrelated to + // the dimentions of the entity -- it defines the size of the arrays that hold voxel values. + // In addition to setting the number of voxels, this is used in a few places for its + // side-effect of allocating _volData to be the correct size. + withWriteLock([&] { + if (_volData && _voxelVolumeSize == voxelVolumeSize) { + return; + } - _volDataLock.lockForWrite(); - _volDataDirty = true; - _voxelVolumeSize = voxelVolumeSize; + _voxelDataDirty = true; + _voxelVolumeSize = voxelVolumeSize; - if (_volData) { - delete _volData; - } - _onCount = 0; + if (_volData) { + delete _volData; + } + _onCount = 0; - if (isEdged(_voxelSurfaceStyle)) { - // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This - // changes how the surface extractor acts -- mainly it becomes impossible to have holes in the - // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the - // voxel space. - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive - _voxelVolumeSize.y + 1, - _voxelVolumeSize.z + 1); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } else { - PolyVox::Vector3DInt32 lowCorner(0, 0, 0); - // these should each have -1 after them, but if we leave layers on the upper-axis faces, - // they act more like I expect. - PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, - _voxelVolumeSize.y, - _voxelVolumeSize.z); - _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); - } + if (isEdged(_voxelSurfaceStyle)) { + // with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This + // changes how the surface extractor acts -- it becomes impossible to have holes in the + // generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the + // voxel space. + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x + 1, // corners are inclusive + _voxelVolumeSize.y + 1, + _voxelVolumeSize.z + 1); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } else { + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + // these should each have -1 after them, but if we leave layers on the upper-axis faces, + // they act more like I expect. + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x, + _voxelVolumeSize.y, + _voxelVolumeSize.z); + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); + } - // having the "outside of voxel-space" value be 255 has helped me notice some problems. - _volData->setBorderValue(255); - _volDataLock.unlock(); - decompressVolumeData(); + // having the "outside of voxel-space" value be 255 has helped me notice some problems. + _volData->setBorderValue(255); + }); } -bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume* vol, - PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const { +bool inUserBounds(const PolyVox::SimpleVolume* vol, + PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z) { // x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords. if (isEdged(surfaceStyle)) { if (x < 0 || y < 0 || z < 0 || @@ -697,8 +786,11 @@ bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume_volDataLock); - return getVoxelInternal(x, y, z); + uint8_t result; + withReadLock([&] { + result = getVoxelInternal(x, y, z); + }); + return result; } @@ -718,7 +810,8 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) { bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t toValue) { - // set a voxel without recompressing the voxel data + // set a voxel without recompressing the voxel data. This assumes that the caller has + // write-locked the entity. bool result = false; if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) { return result; @@ -732,6 +825,12 @@ bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t _volData->setVoxelAt(x, y, z, toValue); } + if (x == 0 || y == 0 || z == 0) { + _neighborsNeedUpdate = true; + } + + _volDataDirty |= result; + return result; } @@ -760,214 +859,176 @@ bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toV } void RenderablePolyVoxEntityItem::decompressVolumeData() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync); + // take compressed data and expand it into _volData. + QByteArray voxelData; + auto entity = std::static_pointer_cast(getThisPointer()); + + withReadLock([&] { + voxelData = _voxelData; + }); + + QtConcurrent::run([=] { + QDataStream reader(voxelData); + quint16 voxelXSize, voxelYSize, voxelZSize; + reader >> voxelXSize; + reader >> voxelYSize; + reader >> voxelZSize; + + if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || + voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { + qDebug() << "voxelSize is not reasonable, skipping decompressions." + << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + int rawSize = voxelXSize * voxelYSize * voxelZSize; + + QByteArray compressedData; + reader >> compressedData; + + QByteArray uncompressedData = qUncompress(compressedData); + + if (uncompressedData.size() != rawSize) { + qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" + << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() + << getName() << getID(); + entity->setVoxelDataDirty(false); + return; + } + + entity->setVoxelsFromData(uncompressedData, voxelXSize, voxelYSize, voxelZSize); + }); } -// take compressed data and expand it into _volData. -void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() { - _voxelDataLock.lockForRead(); - QDataStream reader(_voxelData); - quint16 voxelXSize, voxelYSize, voxelZSize; - reader >> voxelXSize; - reader >> voxelYSize; - reader >> voxelZSize; - - if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || - voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { - qDebug() << "voxelSize is not reasonable, skipping decompressions." - << voxelXSize << voxelYSize << voxelZSize << getName() << getID(); - _voxelDataDirty = false; - _voxelDataLock.unlock(); - _threadRunning.release(); - return; - } - - int rawSize = voxelXSize * voxelYSize * voxelZSize; - - QByteArray compressedData; - reader >> compressedData; - _voxelDataDirty = false; - _voxelDataLock.unlock(); - QByteArray uncompressedData = qUncompress(compressedData); - - if (uncompressedData.size() != rawSize) { - qDebug() << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")" - << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size() - << getName() << getID(); - _threadRunning.release(); - return; - } - - _volDataLock.lockForWrite(); - if (!_volData) { - _volDataLock.unlock(); - _threadRunning.release(); - return; - } - _volDataDirty = true; - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; - setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); +void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData, + quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) { + // this accepts the payload from decompressVolumeData + withWriteLock([&] { + for (int z = 0; z < voxelZSize; z++) { + for (int y = 0; y < voxelYSize; y++) { + for (int x = 0; x < voxelXSize; x++) { + int uncompressedIndex = (z * voxelYSize * voxelXSize) + (y * voxelZSize) + x; + setVoxelInternal(x, y, z, uncompressedData[uncompressedIndex]); + } } } - } - _volDataLock.unlock(); - _threadRunning.release(); + _volDataDirty = true; + }); } void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync); -} + // compress the data in _volData and save the results. The compressed form is used during + // saves to disk and for transmission over the wire to the entity-server -// compress the data in _volData and save the results. The compressed form is used during -// saves to disk and for transmission over the wire -void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() { - quint16 voxelXSize = _voxelVolumeSize.x; - quint16 voxelYSize = _voxelVolumeSize.y; - quint16 voxelZSize = _voxelVolumeSize.z; - int rawSize = voxelXSize * voxelYSize * voxelZSize; + EntityItemPointer entity = getThisPointer(); - QByteArray uncompressedData = QByteArray(rawSize, '\0'); - - _volDataLock.lockForRead(); - for (int z = 0; z < voxelZSize; z++) { - for (int y = 0; y < voxelYSize; y++) { - for (int x = 0; x < voxelXSize; x++) { - uint8_t uVoxelValue = getVoxelInternal(x, y, z); - int uncompressedIndex = - z * voxelYSize * voxelXSize + - y * voxelXSize + - x; - uncompressedData[uncompressedIndex] = uVoxelValue; - } - } - } - _volDataLock.unlock(); - - QByteArray newVoxelData; - QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); - - writer << voxelXSize << voxelYSize << voxelZSize; - - QByteArray compressedData = qCompress(uncompressedData, 9); - writer << compressedData; - - // make sure the compressed data can be sent over the wire-protocol - if (newVoxelData.size() > 1150) { - // HACK -- until we have a way to allow for properties larger than MTU, don't update. - // revert the active voxel-space to the last version that fit. - // XXX - qDebug() << "compressed voxel data is too large" << getName() << getID(); - _threadRunning.release(); - return; - } - - auto now = usecTimestampNow(); - setLastEdited(now); - setLastBroadcast(now); - - _voxelDataLock.lockForWrite(); - _voxelDataDirty = true; - _voxelData = newVoxelData; - _voxelDataLock.unlock(); - - EntityItemProperties properties = getProperties(); - properties.setVoxelDataDirty(); - properties.setLastEdited(now); + quint16 voxelXSize; + quint16 voxelYSize; + quint16 voxelZSize; + withReadLock([&] { + voxelXSize = _voxelVolumeSize.x; + voxelYSize = _voxelVolumeSize.y; + voxelZSize = _voxelVolumeSize.z; + }); EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; - EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; - PhysicalEntitySimulation* peSimulation = static_cast(simulation); - EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; - if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, _id, properties); - } - _threadRunning.release(); + + QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] { + int rawSize = voxelXSize * voxelYSize * voxelZSize; + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + auto polyVoxEntity = std::static_pointer_cast(entity); + polyVoxEntity->forEachVoxelValue(voxelXSize, voxelYSize, voxelZSize, [&] (int x, int y, int z, uint8_t uVoxelValue) { + int uncompressedIndex = + z * voxelYSize * voxelXSize + + y * voxelXSize + + x; + uncompressedData[uncompressedIndex] = uVoxelValue; + }); + + QByteArray newVoxelData; + QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate); + + writer << voxelXSize << voxelYSize << voxelZSize; + + QByteArray compressedData = qCompress(uncompressedData, 9); + writer << compressedData; + + // make sure the compressed data can be sent over the wire-protocol + if (newVoxelData.size() > 1150) { + // HACK -- until we have a way to allow for properties larger than MTU, don't update. + // revert the active voxel-space to the last version that fit. + qDebug() << "compressed voxel data is too large" << entity->getName() << entity->getID(); + return; + } + + auto now = usecTimestampNow(); + entity->setLastEdited(now); + entity->setLastBroadcast(now); + + std::static_pointer_cast(entity)->setVoxelData(newVoxelData); + + tree->withReadLock([&] { + EntityItemProperties properties = entity->getProperties(); + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulation* peSimulation = static_cast(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + if (packetSender) { + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + } + }); + }); } -void RenderablePolyVoxEntityItem::getMesh() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync); -} +EntityItemPointer lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) { + EntityItemPointer current = currentWP.lock(); -void RenderablePolyVoxEntityItem::clearOutOfDateNeighbors() { - if (_xNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getID() != _xNNeighborID) { - _xNNeighbor.reset(); - } - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getID() != _yNNeighborID) { - _yNNeighbor.reset(); - } - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZNNeighbor = _zNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getID() != _zNNeighborID) { - _zNNeighbor.reset(); - } + if (!current && neighborID == UNKNOWN_ENTITY_ID) { + // no neighbor + return nullptr; } - if (_xPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentXPNeighbor = _xPNeighbor.lock(); - if (currentXPNeighbor && currentXPNeighbor->getID() != _xPNeighborID) { - _xPNeighbor.reset(); - } - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentYPNeighbor = _yPNeighbor.lock(); - if (currentYPNeighbor && currentYPNeighbor->getID() != _yPNeighborID) { - _yPNeighbor.reset(); - } - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID) { - EntityItemPointer currentZPNeighbor = _zPNeighbor.lock(); - if (currentZPNeighbor && currentZPNeighbor->getID() != _zPNeighborID) { - _zPNeighbor.reset(); - } + if (current && current->getID() == neighborID) { + // same neighbor + return current; } + if (neighborID == UNKNOWN_ENTITY_ID) { + currentWP.reset(); + return nullptr; + } + + current = tree->findEntityByID(neighborID); + if (!current) { + return nullptr; + } + + currentWP = current; + return current; } void RenderablePolyVoxEntityItem::cacheNeighbors() { - clearOutOfDateNeighbors(); + // this attempts to turn neighbor entityIDs into neighbor weak-pointers EntityTreeElementPointer element = getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return; } - - if (_xNNeighborID != UNKNOWN_ENTITY_ID && _xNNeighbor.expired()) { - _xNNeighbor = tree->findEntityByID(_xNNeighborID); - } - if (_yNNeighborID != UNKNOWN_ENTITY_ID && _yNNeighbor.expired()) { - _yNNeighbor = tree->findEntityByID(_yNNeighborID); - } - if (_zNNeighborID != UNKNOWN_ENTITY_ID && _zNNeighbor.expired()) { - _zNNeighbor = tree->findEntityByID(_zNNeighborID); - } - - if (_xPNeighborID != UNKNOWN_ENTITY_ID && _xPNeighbor.expired()) { - _xPNeighbor = tree->findEntityByID(_xPNeighborID); - } - if (_yPNeighborID != UNKNOWN_ENTITY_ID && _yPNeighbor.expired()) { - _yPNeighbor = tree->findEntityByID(_yPNeighborID); - } - if (_zPNeighborID != UNKNOWN_ENTITY_ID && _zPNeighbor.expired()) { - _zPNeighbor = tree->findEntityByID(_zPNeighborID); - } - + lookUpNeighbor(tree, _xNNeighborID, _xNNeighbor); + lookUpNeighbor(tree, _yNNeighborID, _yNNeighbor); + lookUpNeighbor(tree, _zNNeighborID, _zNNeighbor); + lookUpNeighbor(tree, _xPNeighborID, _xPNeighbor); + lookUpNeighbor(tree, _yPNeighborID, _yPNeighbor); + lookUpNeighbor(tree, _zPNeighborID, _zPNeighbor); } void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { + // fill in our upper edges with a copy of our neighbors lower edges so that the meshes knit together if (_voxelSurfaceStyle != PolyVoxEntityItem::SURFACE_MARCHING_CUBES) { return; } @@ -979,347 +1040,360 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { if (currentXPNeighbor) { auto polyVoxXPNeighbor = std::dynamic_pointer_cast(currentXPNeighbor); if (polyVoxXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int y = 0; y < _volData->getHeight(); y++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); - _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + withWriteLock([&] { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); + _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + } } - } + }); } } if (currentYPNeighbor) { auto polyVoxYPNeighbor = std::dynamic_pointer_cast(currentYPNeighbor); if (polyVoxYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); - _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); + _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); + } } - } + }); } } if (currentZPNeighbor) { auto polyVoxZPNeighbor = std::dynamic_pointer_cast(currentZPNeighbor); if (polyVoxZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int y = 0; y < _volData->getHeight(); y++) { - uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); - _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int y = 0; y < _volData->getHeight(); y++) { + uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); + _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + } } - } + }); } } } -void RenderablePolyVoxEntityItem::getMeshAsync() { - model::MeshPointer mesh(new model::Mesh()); +void RenderablePolyVoxEntityItem::getMesh() { + // use _volData to make a renderable mesh + PolyVoxSurfaceStyle voxelSurfaceStyle; + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + }); cacheNeighbors(); - - // A mesh object to hold the result of surface extraction - PolyVox::SurfaceMesh polyVoxMesh; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } copyUpperEdgesFromNeighbors(); - switch (_voxelSurfaceStyle) { - case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { - PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; - } - case PolyVoxEntityItem::SURFACE_CUBIC: { - PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor - (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); - surfaceExtractor.execute(); - break; + auto entity = std::static_pointer_cast(getThisPointer()); + + + QtConcurrent::run([entity, voxelSurfaceStyle] { + model::MeshPointer mesh(new model::Mesh()); + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + entity->withReadLock([&] { + PolyVox::SimpleVolume* volData = entity->getVolData(); + switch (voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (volData, volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + } + }); + + // convert PolyVox mesh to a Sam mesh + const std::vector& vecIndices = polyVoxMesh.getIndices(); + auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), + (gpu::Byte*)vecIndices.data()); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); + mesh->setIndexBuffer(*indexBufferView); + + const std::vector& vecVertices = polyVoxMesh.getVertices(); + auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), + (gpu::Byte*)vecVertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + gpu::Resource::Size vertexBufferSize = 0; + if (vertexBufferPtr->getSize() > sizeof(float) * 3) { + vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; } + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + mesh->setVertexBuffer(*vertexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + entity->setMesh(mesh); + }); +} + +void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) { + // this catches the payload from getMesh + bool neighborsNeedUpdate; + withWriteLock([&] { + _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; + _mesh = mesh; + _meshDirty = true; + _meshInitialized = true; + neighborsNeedUpdate = _neighborsNeedUpdate; + _neighborsNeedUpdate = false; + }); + if (neighborsNeedUpdate) { + bonkNeighbors(); } - - // convert PolyVox mesh to a Sam mesh - const std::vector& vecIndices = polyVoxMesh.getIndices(); - auto indexBuffer = std::make_shared(vecIndices.size() * sizeof(uint32_t), - (gpu::Byte*)vecIndices.data()); - auto indexBufferPtr = gpu::BufferPointer(indexBuffer); - auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW)); - mesh->setIndexBuffer(*indexBufferView); - - const std::vector& vecVertices = polyVoxMesh.getVertices(); - auto vertexBuffer = std::make_shared(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), - (gpu::Byte*)vecVertices.data()); - auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); - gpu::Resource::Size vertexBufferSize = 0; - if (vertexBufferPtr->getSize() > sizeof(float) * 3) { - vertexBufferSize = vertexBufferPtr->getSize() - sizeof(float) * 3; - } - auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, 0, vertexBufferSize, sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); - mesh->setVertexBuffer(*vertexBufferView); - mesh->addAttribute(gpu::Stream::NORMAL, - gpu::BufferView(vertexBufferPtr, - sizeof(float) * 3, - vertexBufferPtr->getSize() - sizeof(float) * 3, - sizeof(PolyVox::PositionMaterialNormal), - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); - - _meshLock.lockForWrite(); - _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - _mesh = mesh; - _meshDirty = true; - _meshLock.unlock(); - _volDataDirty = false; - _volDataLock.unlock(); - bonkNeighbors(); - _threadRunning.release(); } void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { - _threadRunning.acquire(); - QtConcurrent::run(this, &RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync); -} - - -void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() { - QVector> points; - AABox box; - glm::mat4 vtoM = voxelToLocalMatrix(); - - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || - _voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { - // pull each triangle in the mesh into a polyhedron which can be collided with - unsigned int i = 0; - - _meshLock.lockForRead(); - model::MeshPointer mesh = _mesh; - const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); - const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); - _meshLock.unlock(); - - gpu::BufferView::Iterator it = indexBufferView.cbegin(); - while (it != indexBufferView.cend()) { - uint32_t p0Index = *(it++); - uint32_t p1Index = *(it++); - uint32_t p2Index = *(it++); - - const glm::vec3& p0 = vertexBufferView.get(p0Index); - const glm::vec3& p1 = vertexBufferView.get(p1Index); - const glm::vec3& p2 = vertexBufferView.get(p2Index); - - glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face - glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); - glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; - - glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); - glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); - glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); - glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); - - box += p0Model; - box += p1Model; - box += p2Model; - box += p3Model; - - QVector pointsInPart; - pointsInPart << p0Model; - pointsInPart << p1Model; - pointsInPart << p2Model; - pointsInPart << p3Model; - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } else { - unsigned int i = 0; - - _volDataLock.lockForRead(); - if (!_volData) { - _volDataLock.unlock(); - return; - } - - for (int z = 0; z < _voxelVolumeSize.z; z++) { - for (int y = 0; y < _voxelVolumeSize.y; y++) { - for (int x = 0; x < _voxelVolumeSize.x; x++) { - if (getVoxelInternal(x, y, z) > 0) { - - if ((x > 0 && getVoxel(x - 1, y, z) > 0) && - (y > 0 && getVoxel(x, y - 1, z) > 0) && - (z > 0 && getVoxel(x, y, z - 1) > 0) && - (x < _voxelVolumeSize.x - 1 && getVoxel(x + 1, y, z) > 0) && - (y < _voxelVolumeSize.y - 1 && getVoxel(x, y + 1, z) > 0) && - (z < _voxelVolumeSize.z - 1 && getVoxel(x, y, z + 1) > 0)) { - // this voxel has neighbors in every cardinal direction, so there's no need - // to include it in the collision hull. - continue; - } - - QVector pointsInPart; - - float offL = -0.5f; - float offH = 0.5f; - if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { - offL += 1.0f; - offH += 1.0f; - } - - glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); - glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); - glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); - glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); - glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); - glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); - glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); - glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); - - box += p000; - box += p001; - box += p010; - box += p011; - box += p100; - box += p101; - box += p110; - box += p111; - - pointsInPart << p000; - pointsInPart << p001; - pointsInPart << p010; - pointsInPart << p011; - pointsInPart << p100; - pointsInPart << p101; - pointsInPart << p110; - pointsInPart << p111; - - // add next convex hull - QVector newMeshPoints; - points << newMeshPoints; - // add points to the new convex hull - points[i++] << pointsInPart; - } - } - } - } - _volDataLock.unlock(); + // this creates a collision-shape for the physics engine. The shape comes from + // _volData for cubic extractors and from _mesh for marching-cube extractors + if (!_meshInitialized) { + return; } + EntityItemPointer entity = getThisPointer(); + + PolyVoxSurfaceStyle voxelSurfaceStyle; + glm::vec3 voxelVolumeSize; + model::MeshPointer mesh; + + withReadLock([&] { + voxelSurfaceStyle = _voxelSurfaceStyle; + voxelVolumeSize = _voxelVolumeSize; + mesh = _mesh; + }); + + QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { + auto polyVoxEntity = std::static_pointer_cast(entity); + QVector> points; + AABox box; + glm::mat4 vtoM = std::static_pointer_cast(entity)->voxelToLocalMatrix(); + + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES || + voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) { + // pull each triangle in the mesh into a polyhedron which can be collided with + unsigned int i = 0; + + const gpu::BufferView vertexBufferView = mesh->getVertexBuffer(); + const gpu::BufferView& indexBufferView = mesh->getIndexBuffer(); + + gpu::BufferView::Iterator it = indexBufferView.cbegin(); + while (it != indexBufferView.cend()) { + uint32_t p0Index = *(it++); + uint32_t p1Index = *(it++); + uint32_t p2Index = *(it++); + + const glm::vec3& p0 = vertexBufferView.get(p0Index); + const glm::vec3& p1 = vertexBufferView.get(p1Index); + const glm::vec3& p2 = vertexBufferView.get(p2Index); + + glm::vec3 av = (p0 + p1 + p2) / 3.0f; // center of the triangular face + glm::vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0)); + glm::vec3 p3 = av - normal * MARCHING_CUBE_COLLISION_HULL_OFFSET; + + glm::vec3 p0Model = glm::vec3(vtoM * glm::vec4(p0, 1.0f)); + glm::vec3 p1Model = glm::vec3(vtoM * glm::vec4(p1, 1.0f)); + glm::vec3 p2Model = glm::vec3(vtoM * glm::vec4(p2, 1.0f)); + glm::vec3 p3Model = glm::vec3(vtoM * glm::vec4(p3, 1.0f)); + + box += p0Model; + box += p1Model; + box += p2Model; + box += p3Model; + + QVector pointsInPart; + pointsInPart << p0Model; + pointsInPart << p1Model; + pointsInPart << p2Model; + pointsInPart << p3Model; + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + } else { + unsigned int i = 0; + polyVoxEntity->forEachVoxelValue(voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z, + [&](int x, int y, int z, uint8_t value) { + if (value > 0) { + if ((x > 0 && polyVoxEntity->getVoxelInternal(x - 1, y, z) > 0) && + (y > 0 && polyVoxEntity->getVoxelInternal(x, y - 1, z) > 0) && + (z > 0 && polyVoxEntity->getVoxelInternal(x, y, z - 1) > 0) && + (x < voxelVolumeSize.x - 1 && polyVoxEntity->getVoxelInternal(x + 1, y, z) > 0) && + (y < voxelVolumeSize.y - 1 && polyVoxEntity->getVoxelInternal(x, y + 1, z) > 0) && + (z < voxelVolumeSize.z - 1 && polyVoxEntity->getVoxelInternal(x, y, z + 1) > 0)) { + // this voxel has neighbors in every cardinal direction, so there's no need + // to include it in the collision hull. + return; + } + + QVector pointsInPart; + + float offL = -0.5f; + float offH = 0.5f; + if (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC) { + offL += 1.0f; + offH += 1.0f; + } + + glm::vec3 p000 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offL, 1.0f)); + glm::vec3 p001 = glm::vec3(vtoM * glm::vec4(x + offL, y + offL, z + offH, 1.0f)); + glm::vec3 p010 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offL, 1.0f)); + glm::vec3 p011 = glm::vec3(vtoM * glm::vec4(x + offL, y + offH, z + offH, 1.0f)); + glm::vec3 p100 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offL, 1.0f)); + glm::vec3 p101 = glm::vec3(vtoM * glm::vec4(x + offH, y + offL, z + offH, 1.0f)); + glm::vec3 p110 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offL, 1.0f)); + glm::vec3 p111 = glm::vec3(vtoM * glm::vec4(x + offH, y + offH, z + offH, 1.0f)); + + box += p000; + box += p001; + box += p010; + box += p011; + box += p100; + box += p101; + box += p110; + box += p111; + + pointsInPart << p000; + pointsInPart << p001; + pointsInPart << p010; + pointsInPart << p011; + pointsInPart << p100; + pointsInPart << p101; + pointsInPart << p110; + pointsInPart << p111; + + // add next convex hull + QVector newMeshPoints; + points << newMeshPoints; + // add points to the new convex hull + points[i++] << pointsInPart; + } + }); + } + polyVoxEntity->setCollisionPoints(points, box); + }); +} + +void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector> points, AABox box) { + // this catches the payload from computeShapeInfoWorker if (points.isEmpty()) { - _shapeInfoLock.lockForWrite(); EntityItem::computeShapeInfo(_shapeInfo); - _shapeInfoLock.unlock(); - _threadRunning.release(); return; } glm::vec3 collisionModelDimensions = box.getDimensions(); // include the registrationPoint in the shape key, because the offset is already // included in the points and the shapeManager wont know that the shape has changed. - QString shapeKey = QString(_voxelData.toBase64()) + "," + - QString::number(_registrationPoint.x) + "," + - QString::number(_registrationPoint.y) + "," + - QString::number(_registrationPoint.z); - _shapeInfoLock.lockForWrite(); - _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); - _shapeInfo.setConvexHulls(points); - // adjustShapeInfoByRegistration(_shapeInfo); - _shapeInfoLock.unlock(); - - _meshLock.lockForWrite(); - _meshDirty = false; - _meshLock.unlock(); - _threadRunning.release(); - return; + withWriteLock([&] { + QString shapeKey = QString(_voxelData.toBase64()) + "," + + QString::number(_registrationPoint.x) + "," + + QString::number(_registrationPoint.y) + "," + + QString::number(_registrationPoint.z); + _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); + _shapeInfo.setConvexHulls(points); + _meshDirty = false; + }); } - void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighborID) { + if (xNNeighborID == _id) { // TODO loops are still possible + return; + } + if (xNNeighborID != _xNNeighborID) { PolyVoxEntityItem::setXNNeighborID(xNNeighborID); cacheNeighbors(); - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->setXPNeighborID(_id); - polyVoxXNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighborID) { + if (yNNeighborID == _id) { // TODO loops are still possible + return; + } + if (yNNeighborID != _yNNeighborID) { PolyVoxEntityItem::setYNNeighborID(yNNeighborID); cacheNeighbors(); - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->setYPNeighborID(_id); - polyVoxYNNeighbor->rebakeMesh(); - } } } void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighborID) { + if (zNNeighborID == _id) { // TODO loops are still possible + return; + } + if (zNNeighborID != _zNNeighborID) { PolyVoxEntityItem::setZNNeighborID(zNNeighborID); cacheNeighbors(); - EntityItemPointer currentZNNeighbor = _yNNeighbor.lock(); - if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->setZPNeighborID(_id); - polyVoxZNNeighbor->rebakeMesh(); - } } } - void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighborID) { + if (xPNeighborID == _id) { // TODO loops are still possible + return; + } if (xPNeighborID != _xPNeighborID) { PolyVoxEntityItem::setXPNeighborID(xPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighborID) { + if (yPNeighborID == _id) { // TODO loops are still possible + return; + } if (yPNeighborID != _yPNeighborID) { PolyVoxEntityItem::setYPNeighborID(yPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighborID) { + if (zPNeighborID == _id) { // TODO loops are still possible + return; + } if (zPNeighborID != _zPNeighborID) { PolyVoxEntityItem::setZPNeighborID(zPNeighborID); - rebakeMesh(); + _volDataDirty = true; } } -void RenderablePolyVoxEntityItem::rebakeMesh() { - QReadLocker(&this->_volDataLock); - _volDataDirty = true; -} - void RenderablePolyVoxEntityItem::bonkNeighbors() { - clearOutOfDateNeighbors(); + // flag neighbors to the negative of this entity as needing to rebake their meshes. cacheNeighbors(); EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); @@ -1328,14 +1402,14 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->rebakeMesh(); + polyVoxXNNeighbor->setVolDataDirty(); } if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->rebakeMesh(); + polyVoxYNNeighbor->setVolDataDirty(); } if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->rebakeMesh(); + polyVoxZNNeighbor->setVolDataDirty(); } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index b40507f36a..e5afb94afa 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -48,6 +48,8 @@ public: virtual ~RenderablePolyVoxEntityItem(); + void initializePolyVox(); + virtual void somethingChangedNotification() { // This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing // this entity comes from the entity-server. It gets called even if nothing has actually changed @@ -114,17 +116,28 @@ public: virtual void setYPNeighborID(const EntityItemID& yPNeighborID); virtual void setZPNeighborID(const EntityItemID& zPNeighborID); - virtual void rebakeMesh(); - virtual void updateRegistrationPoint(const glm::vec3& value); + void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize); + void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize, + std::function thunk); + + void setMesh(model::MeshPointer mesh); + void setCollisionPoints(const QVector> points, AABox box); + PolyVox::SimpleVolume* getVolData() { return _volData; } + + uint8_t getVoxelInternal(int x, int y, int z); + bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + + void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); } + private: // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // may not match _voxelVolumeSize. model::MeshPointer _mesh; - bool _meshDirty; // does collision-shape need to be recomputed? - mutable QReadWriteLock _meshLock{QReadWriteLock::Recursive}; + bool _meshDirty { true }; // does collision-shape need to be recomputed? + bool _meshInitialized { false }; NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; @@ -135,44 +148,35 @@ private: static gpu::PipelinePointer _pipeline; ShapeInfo _shapeInfo; - mutable QReadWriteLock _shapeInfoLock; PolyVox::SimpleVolume* _volData = nullptr; - mutable QReadWriteLock _volDataLock{QReadWriteLock::Recursive}; // lock for _volData bool _volDataDirty = false; // does getMesh need to be called? int _onCount; // how many non-zero voxels are in _volData - bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, - int x, int y, int z) const; - uint8_t getVoxelInternal(int x, int y, int z); - bool setVoxelInternal(int x, int y, int z, uint8_t toValue); + bool _neighborsNeedUpdate { false }; + bool updateOnCount(int x, int y, int z, uint8_t toValue); PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; // these are run off the main thread void decompressVolumeData(); - void decompressVolumeDataAsync(); void compressVolumeDataAndSendEditPacket(); - void compressVolumeDataAndSendEditPacketAsync(); - void getMesh(); - void getMeshAsync(); + virtual void getMesh(); // recompute mesh void computeShapeInfoWorker(); - void computeShapeInfoWorkerAsync(); - - QSemaphore _threadRunning{1}; // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID - EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis + EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis EntityItemWeakPointer _yNNeighbor; EntityItemWeakPointer _zNNeighbor; - EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis + EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis EntityItemWeakPointer _yPNeighbor; EntityItemWeakPointer _zPNeighbor; - void clearOutOfDateNeighbors(); void cacheNeighbors(); void copyUpperEdgesFromNeighbors(); void bonkNeighbors(); }; +bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, + int x, int y, int z); #endif // hifi_RenderablePolyVoxEntityItem_h diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 686fb1f72d..bce27f1cca 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -120,10 +120,10 @@ public: virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } - void setJointRotations(const QVector& rotations); - void setJointRotationsSet(const QVector& rotationsSet); - void setJointTranslations(const QVector& translations); - void setJointTranslationsSet(const QVector& translationsSet); + virtual void setJointRotations(const QVector& rotations); + virtual void setJointRotationsSet(const QVector& rotationsSet); + virtual void setJointTranslations(const QVector& translations); + virtual void setJointTranslationsSet(const QVector& translationsSet); QVector getJointRotations() const; QVector getJointRotationsSet() const; QVector getJointTranslations() const; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 9b85938a78..44bf940dae 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -64,44 +64,47 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID) : } void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { - QWriteLocker(&this->_voxelDataLock); + withWriteLock([&] { + assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); + assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); + assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); - assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x); - assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y); - assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z); + _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); + if (_voxelVolumeSize.x < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; + _voxelVolumeSize.x = 1; + } + if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; + _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; + } - _voxelVolumeSize = glm::vec3(roundf(voxelVolumeSize.x), roundf(voxelVolumeSize.y), roundf(voxelVolumeSize.z)); - if (_voxelVolumeSize.x < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to 1"; - _voxelVolumeSize.x = 1; - } - if (_voxelVolumeSize.x > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping x of" << _voxelVolumeSize.x << "to max"; - _voxelVolumeSize.x = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.y < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; + _voxelVolumeSize.y = 1; + } + if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; + _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; + } - if (_voxelVolumeSize.y < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to 1"; - _voxelVolumeSize.y = 1; - } - if (_voxelVolumeSize.y > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping y of" << _voxelVolumeSize.y << "to max"; - _voxelVolumeSize.y = MAX_VOXEL_DIMENSION; - } - - if (_voxelVolumeSize.z < 1) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; - _voxelVolumeSize.z = 1; - } - if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { - qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; - _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; - } + if (_voxelVolumeSize.z < 1) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to 1"; + _voxelVolumeSize.z = 1; + } + if (_voxelVolumeSize.z > MAX_VOXEL_DIMENSION) { + qDebug() << "PolyVoxEntityItem::setVoxelVolumeSize clamping z of" << _voxelVolumeSize.z << "to max"; + _voxelVolumeSize.z = MAX_VOXEL_DIMENSION; + } + }); } -const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const { - QWriteLocker locker(&this->_voxelDataLock); - return _voxelVolumeSize; +glm::vec3 PolyVoxEntityItem::getVoxelVolumeSize() const { + glm::vec3 voxelVolumeSize; + withReadLock([&] { + voxelVolumeSize = _voxelVolumeSize; + }); + return voxelVolumeSize; } @@ -226,12 +229,16 @@ void PolyVoxEntityItem::debugDump() const { } void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) { - QWriteLocker(&this->_voxelDataLock); - _voxelData = voxelData; - _voxelDataDirty = true; + withWriteLock([&] { + _voxelData = voxelData; + _voxelDataDirty = true; + }); } const QByteArray PolyVoxEntityItem::getVoxelData() const { - QReadLocker(&this->_voxelDataLock); - return _voxelData; + QByteArray voxelDataCopy; + withReadLock([&] { + voxelDataCopy = _voxelData; + }); + return voxelDataCopy; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 13e541d298..7441b34c9c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -52,7 +52,7 @@ class PolyVoxEntityItem : public EntityItem { virtual void debugDump() const; virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); - virtual const glm::vec3& getVoxelVolumeSize() const; + virtual glm::vec3 getVoxelVolumeSize() const; virtual void setVoxelData(QByteArray voxelData); virtual const QByteArray getVoxelData() const; @@ -128,12 +128,14 @@ class PolyVoxEntityItem : public EntityItem { virtual void rebakeMesh() {}; + void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); } + virtual void getMesh() {}; // recompute mesh + protected: glm::vec3 _voxelVolumeSize; // this is always 3 bytes - mutable QReadWriteLock _voxelDataLock; QByteArray _voxelData; - bool _voxelDataDirty; + bool _voxelDataDirty; // _voxelData has changed, things that depend on it should be updated PolyVoxSurfaceStyle _voxelSurfaceStyle; diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index c67dec1e51..f113be1cfb 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -50,7 +50,9 @@ void GLWidget::initializeGL() { // TODO: write the proper code for linux makeCurrent(); #if defined(Q_OS_WIN) - _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; + if (isValid() && context() && context()->contextHandle()) { + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; + } #endif } diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index e224adad07..818b3c6ca8 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -190,25 +190,21 @@ void OffscreenQmlRenderThread::setupFbo() { using namespace oglplus; _textures.setSize(_size); - // Before making any ogl calls, clear any outstanding errors - // FIXME: Something upstream is polluting the context with a GL_INVALID_ENUM, - // likely from glewExperimental = true - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - qDebug() << "Clearing outstanding GL error to set up QML FBO:" << glewGetErrorString(err); + try { + _depthStencil.reset(new Renderbuffer()); + Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil) + .Storage( + PixelDataInternalFormat::DepthComponent, + _size.x, _size.y); + + _fbo.reset(new Framebuffer()); + _fbo->Bind(Framebuffer::Target::Draw); + _fbo->AttachRenderbuffer(Framebuffer::Target::Draw, + FramebufferAttachment::Depth, *_depthStencil); + DefaultFramebuffer().Bind(Framebuffer::Target::Draw); + } catch (oglplus::Error& error) { + qWarning() << "OpenGL error in QML render setup: " << error.what(); } - - _depthStencil.reset(new Renderbuffer()); - Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil) - .Storage( - PixelDataInternalFormat::DepthComponent, - _size.x, _size.y); - - _fbo.reset(new Framebuffer()); - _fbo->Bind(Framebuffer::Target::Draw); - _fbo->AttachRenderbuffer(Framebuffer::Target::Draw, - FramebufferAttachment::Depth, *_depthStencil); - DefaultFramebuffer().Bind(Framebuffer::Target::Draw); } void OffscreenQmlRenderThread::init() { @@ -299,10 +295,21 @@ void OffscreenQmlRenderThread::render() { try { PROFILE_RANGE("qml_render") - TexturePtr texture = _textures.getNextTexture(); - _fbo->Bind(Framebuffer::Target::Draw); - _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); - _fbo->Complete(Framebuffer::Target::Draw); + + TexturePtr texture = _textures.getNextTexture(); + + try { + _fbo->Bind(Framebuffer::Target::Draw); + _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); + _fbo->Complete(Framebuffer::Target::Draw); + } catch (oglplus::Error& error) { + qWarning() << "OpenGL error in QML render: " << error.what(); + + // In case we are failing from a failed setupFbo, reset fbo before next render + setupFbo(); + throw; + } + { PROFILE_RANGE("qml_render->rendercontrol") _renderControl->render(); @@ -311,13 +318,14 @@ void OffscreenQmlRenderThread::render() { // for now just clear the errors glGetError(); } + // FIXME probably unecessary DefaultFramebuffer().Bind(Framebuffer::Target::Draw); _quickWindow->resetOpenGLState(); _escrow.submit(GetName(*texture)); _lastRenderTime = usecTimestampNow(); } catch (std::runtime_error& error) { - qWarning() << "Failed to render QML " << error.what(); + qWarning() << "Failed to render QML: " << error.what(); } } diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp index 761c27a302..9a8c9f7a20 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ b/libraries/gl/src/gl/OpenGLVersionChecker.cpp @@ -22,11 +22,26 @@ OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : { } -bool OpenGLVersionChecker::isValidVersion() { - bool valid = true; +QString OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { + valid = true; + override = false; - // Retrieve OpenGL version GLWidget* glWidget = new GLWidget(); + valid = glWidget->isValid(); + // Inform user if no OpenGL support + if (!valid) { + QMessageBox messageBox; + messageBox.setWindowTitle("Missing OpenGL Support"); + messageBox.setIcon(QMessageBox::Warning); + messageBox.setText(QString().sprintf("Your system does not support OpenGL, Interface cannot run.")); + messageBox.setInformativeText("Press OK to exit."); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.setDefaultButton(QMessageBox::Ok); + messageBox.exec(); + return QString(); + } + + // Retrieve OpenGL version glWidget->initializeGL(); QString glVersion = QString((const char*)glGetString(GL_VERSION)); delete glWidget; @@ -54,8 +69,8 @@ bool OpenGLVersionChecker::isValidVersion() { messageBox.setInformativeText("Press OK to exit; Ignore to continue."); messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore); messageBox.setDefaultButton(QMessageBox::Ok); - valid = messageBox.exec() == QMessageBox::Ignore; + override = messageBox.exec() == QMessageBox::Ignore; } - return valid; + return glVersion; } diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.h b/libraries/gl/src/gl/OpenGLVersionChecker.h index 3e16c3a32d..1e45052626 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.h +++ b/libraries/gl/src/gl/OpenGLVersionChecker.h @@ -19,7 +19,7 @@ class OpenGLVersionChecker : public QApplication { public: OpenGLVersionChecker(int& argc, char** argv); - static bool isValidVersion(); + static QString checkVersion(bool& valid, bool& override); }; #endif // hifi_OpenGLVersionChecker_h diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index dd26ab2823..b14c461bc5 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -74,6 +74,11 @@ void Context::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, cons _backend->downloadFramebuffer(srcFramebuffer, region, destImage); } + +void Context::getStats(ContextStats& stats) const { + _backend->getStats(stats); +} + const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Transform& xformView) const { _projectionInverse = glm::inverse(_projection); @@ -102,3 +107,68 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S return result; } + +// Counters for Buffer and Texture usage in GPU/Context +std::atomic Context::_bufferGPUCount{ 0 }; +std::atomic Context::_bufferGPUMemoryUsage{ 0 }; + +std::atomic Context::_textureGPUCount{ 0 }; +std::atomic Context::_textureGPUMemoryUsage{ 0 }; + +void Context::incrementBufferGPUCount() { + _bufferGPUCount++; +} +void Context::decrementBufferGPUCount() { + _bufferGPUCount--; +} +void Context::updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (newObjectSize > prevObjectSize) { + _bufferGPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } else { + _bufferGPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } +} + +void Context::incrementTextureGPUCount() { + _textureGPUCount++; +} +void Context::decrementTextureGPUCount() { + _textureGPUCount--; +} +void Context::updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (newObjectSize > prevObjectSize) { + _textureGPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } else { + _textureGPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } +} + +uint32_t Context::getBufferGPUCount() { + return _bufferGPUCount.load(); +} + +Context::Size Context::getBufferGPUMemoryUsage() { + return _bufferGPUMemoryUsage.load(); +} + +uint32_t Context::getTextureGPUCount() { + return _textureGPUCount.load(); +} + +Context::Size Context::getTextureGPUMemoryUsage() { + return _textureGPUMemoryUsage.load(); +} + +void Backend::incrementBufferGPUCount() { Context::incrementBufferGPUCount(); } +void Backend::decrementBufferGPUCount() { Context::decrementBufferGPUCount(); } +void Backend::updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateBufferGPUMemoryUsage(prevObjectSize, newObjectSize); } +void Backend::incrementTextureGPUCount() { Context::incrementTextureGPUCount(); } +void Backend::decrementTextureGPUCount() { Context::decrementTextureGPUCount(); } +void Backend::updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUMemoryUsage(prevObjectSize, newObjectSize); } + diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index d584f54acc..b898bddef9 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -27,6 +27,21 @@ class QImage; namespace gpu { +struct ContextStats { +public: + int _ISNumFormatChanges = 0; + int _ISNumInputBufferChanges = 0; + int _ISNumIndexBufferChanges = 0; + + int _RSNumTextureBounded = 0; + + int _DSNumDrawcalls = 0; + int _DSNumTriangles = 0; + + ContextStats() {} + ContextStats(const ContextStats& stats) = default; +}; + struct StereoState { bool _enable{ false }; bool _skybox{ false }; @@ -100,13 +115,27 @@ public: return reinterpret_cast(object.gpuObject.getGPUObject()); } + void getStats(ContextStats& stats) const { stats = _stats; } + + + + // These should only be accessed by Backend implementation to repport the buffer and texture allocations, + // they are NOT public calls + static void incrementBufferGPUCount(); + static void decrementBufferGPUCount(); + static void updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); + static void incrementTextureGPUCount(); + static void decrementTextureGPUCount(); + static void updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); protected: StereoState _stereo; + ContextStats _stats; }; class Context { public: + using Size = Resource::Size; typedef Backend* (*CreateBackend)(); typedef bool (*MakeProgram)(Shader& shader, const Shader::BindingSet& bindings); @@ -125,6 +154,7 @@ public: ~Context(); void render(Batch& batch); + void enableStereo(bool enable = true); bool isStereo(); void setStereoProjections(const mat4 eyeProjections[2]); @@ -137,6 +167,16 @@ public: // It s here for convenience to easily capture a snapshot void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage); + // Repporting stats of the context + void getStats(ContextStats& stats) const; + + + static uint32_t getBufferGPUCount(); + static Size getBufferGPUMemoryUsage(); + + static uint32_t getTextureGPUCount(); + static Size getTextureGPUMemoryUsage(); + protected: Context(const Context& context); @@ -153,6 +193,23 @@ protected: static std::once_flag _initialized; friend class Shader; + + // These should only be accessed by the Backend, they are NOT public calls + static void incrementBufferGPUCount(); + static void decrementBufferGPUCount(); + static void updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + static void incrementTextureGPUCount(); + static void decrementTextureGPUCount(); + static void updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + + // Buffer and Texture Counters + static std::atomic _bufferGPUCount; + static std::atomic _bufferGPUMemoryUsage; + + static std::atomic _textureGPUCount; + static std::atomic _textureGPUMemoryUsage; + + friend class Backend; }; typedef std::shared_ptr ContextPointer; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 2c25255a80..e847ad1a42 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -324,7 +324,10 @@ void GLBackend::do_draw(Batch& batch, size_t paramOffset) { uint32 numVertices = batch._params[paramOffset + 1]._uint; uint32 startVertex = batch._params[paramOffset + 0]._uint; glDrawArrays(mode, startVertex, numVertices); - (void) CHECK_GL_ERROR(); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + + (void)CHECK_GL_ERROR(); } void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { @@ -339,6 +342,9 @@ void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + _stats._DSNumTriangles += numIndices / 3; + _stats._DSNumDrawcalls++; + (void) CHECK_GL_ERROR(); } @@ -350,6 +356,9 @@ void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { uint32 startVertex = batch._params[paramOffset + 1]._uint; glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + _stats._DSNumTriangles += (numInstances * numVertices) / 3; + _stats._DSNumDrawcalls += numInstances; + (void) CHECK_GL_ERROR(); } @@ -372,6 +381,9 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { glDrawElementsInstanced(mode, numIndices, glType, indexBufferByteOffset, numInstances); Q_UNUSED(startInstance); #endif + _stats._DSNumTriangles += (numInstances * numIndices) / 3; + _stats._DSNumDrawcalls += numInstances; + (void)CHECK_GL_ERROR(); } @@ -382,6 +394,7 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; #else // FIXME implement the slow path #endif @@ -396,6 +409,8 @@ void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { GLenum indexType = _elementTypeToGLType[_input._indexBufferType]; glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + #else // FIXME implement the slow path #endif diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 39b54c109b..d4efe7fe99 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -67,6 +67,8 @@ public: GLBuffer(); ~GLBuffer(); + + void setSize(GLuint size); }; static GLBuffer* syncGPUObject(const Buffer& buffer); static GLuint getBufferID(const Buffer& buffer); @@ -77,10 +79,15 @@ public: Stamp _contentStamp; GLuint _texture; GLenum _target; - GLuint _size; GLTexture(); ~GLTexture(); + + void setSize(GLuint size); + GLuint size() const { return _size; } + + private: + GLuint _size; }; static GLTexture* syncGPUObject(const Texture& texture); static GLuint getTextureID(const TexturePointer& texture, bool sync = true); @@ -230,26 +237,11 @@ public: void do_setStateBlend(State::BlendFunction blendFunction); void do_setStateColorWriteMask(uint32 mask); - - // Repporting stats of the context - class Stats { - public: - int _ISNumFormatChanges = 0; - int _ISNumInputBufferChanges = 0; - int _ISNumIndexBufferChanges = 0; - - Stats() {} - Stats(const Stats& stats) = default; - }; - - void getStats(Stats& stats) const { stats = _stats; } - + protected: void renderPassTransfer(Batch& batch); void renderPassDraw(Batch& batch); - Stats _stats; - // Draw Stage void do_draw(Batch& batch, size_t paramOffset); void do_drawIndexed(Batch& batch, size_t paramOffset); diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp index 49aeeca38e..080d743104 100755 --- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp +++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp @@ -16,12 +16,21 @@ GLBackend::GLBuffer::GLBuffer() : _stamp(0), _buffer(0), _size(0) -{} +{ + Backend::incrementBufferGPUCount(); +} GLBackend::GLBuffer::~GLBuffer() { if (_buffer != 0) { glDeleteBuffers(1, &_buffer); } + Backend::updateBufferGPUMemoryUsage(_size, 0); + Backend::decrementBufferGPUCount(); +} + +void GLBackend::GLBuffer::setSize(GLuint size) { + Backend::updateBufferGPUMemoryUsage(_size, size); + _size = size; } GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { @@ -46,7 +55,7 @@ GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); object->_stamp = buffer.getSysmem().getStamp(); - object->_size = (GLuint)buffer.getSysmem().getSize(); + object->setSize((GLuint)buffer.getSysmem().getSize()); //} (void) CHECK_GL_ERROR(); diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index 8c9647e0f2..046f1ff0e5 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -251,6 +251,9 @@ void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { return; } + // One more True texture bound + _stats._RSNumTextureBounded++; + // Always make sure the GLObject is in sync GLTexture* object = GLBackend::syncGPUObject(*resourceTexture); if (object) { diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index a70904a4bf..09714b5542 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -19,12 +19,21 @@ GLBackend::GLTexture::GLTexture() : _texture(0), _target(GL_TEXTURE_2D), _size(0) -{} +{ + Backend::incrementTextureGPUCount(); +} GLBackend::GLTexture::~GLTexture() { if (_texture != 0) { glDeleteTextures(1, &_texture); } + Backend::updateTextureGPUMemoryUsage(_size, 0); + Backend::decrementTextureGPUCount(); +} + +void GLBackend::GLTexture::setSize(GLuint size) { + Backend::updateTextureGPUMemoryUsage(_size, size); + _size = size; } class GLTexelFormat { @@ -427,8 +436,8 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { if (needUpdate) { if (texture.isStoredMipFaceAvailable(0)) { Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - const GLvoid* bytes = mip->_sysmem.read(); - Element srcFormat = mip->_format; + const GLvoid* bytes = mip->readData(); + Element srcFormat = mip->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); @@ -458,8 +467,8 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { if (texture.isStoredMipFaceAvailable(0)) { Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - bytes = mip->_sysmem.read(); - srcFormat = mip->_format; + bytes = mip->readData(); + srcFormat = mip->getFormat(); object->_contentStamp = texture.getDataStamp(); } @@ -483,7 +492,7 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { object->_storageStamp = texture.getStamp(); object->_contentStamp = texture.getDataStamp(); - object->_size = (GLuint)texture.getSize(); + object->setSize((GLuint)texture.getSize()); } glBindTexture(GL_TEXTURE_2D, boundTex); @@ -507,11 +516,11 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { for (int f = 0; f < NUM_FACES; f++) { if (texture.isStoredMipFaceAvailable(0, f)) { Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->_format; + Element srcFormat = mipFace->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); glTexSubImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->_sysmem.read())); + texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); // At this point the mip pixels have been loaded, we can notify texture.notifyMipFaceGPULoaded(0, f); @@ -536,11 +545,11 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { for (int f = 0; f < NUM_FACES; f++) { if (texture.isStoredMipFaceAvailable(0, f)) { Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->_format; + Element srcFormat = mipFace->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); glTexImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->_sysmem.read())); + texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); // At this point the mip pixels have been loaded, we can notify texture.notifyMipFaceGPULoaded(0, f); @@ -561,7 +570,7 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { object->_storageStamp = texture.getStamp(); object->_contentStamp = texture.getDataStamp(); - object->_size = (GLuint)texture.getSize(); + object->setSize((GLuint)texture.getSize()); } glBindTexture(GL_TEXTURE_CUBE_MAP, boundTex); diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp index 197263f392..c793a92b72 100644 --- a/libraries/gpu/src/gpu/Resource.cpp +++ b/libraries/gpu/src/gpu/Resource.cpp @@ -16,6 +16,8 @@ #include #include +#include "Context.h" + using namespace gpu; class AllocationDebugger { @@ -232,19 +234,55 @@ Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) { return 0; } +std::atomic Buffer::_bufferCPUCount{ 0 }; +std::atomic Buffer::_bufferCPUMemoryUsage{ 0 }; + +void Buffer::updateBufferCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (prevObjectSize > newObjectSize) { + _bufferCPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } else { + _bufferCPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } +} + +uint32_t Buffer::getBufferCPUCount() { + return _bufferCPUCount.load(); +} + +Buffer::Size Buffer::getBufferCPUMemoryUsage() { + return _bufferCPUMemoryUsage.load(); +} + +uint32_t Buffer::getBufferGPUCount() { + return Context::getBufferGPUCount(); +} + +Buffer::Size Buffer::getBufferGPUMemoryUsage() { + return Context::getBufferGPUMemoryUsage(); +} + Buffer::Buffer() : Resource(), _sysmem(new Sysmem()) { + _bufferCPUCount++; + } Buffer::Buffer(Size size, const Byte* bytes) : Resource(), _sysmem(new Sysmem(size, bytes)) { + _bufferCPUCount++; + Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize()); } Buffer::Buffer(const Buffer& buf) : Resource(), _sysmem(new Sysmem(buf.getSysmem())) { + _bufferCPUCount++; + Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize()); } Buffer& Buffer::operator=(const Buffer& buf) { @@ -253,18 +291,27 @@ Buffer& Buffer::operator=(const Buffer& buf) { } Buffer::~Buffer() { + _bufferCPUCount--; + if (_sysmem) { + Buffer::updateBufferCPUMemoryUsage(_sysmem->getSize(), 0); delete _sysmem; _sysmem = NULL; } } Buffer::Size Buffer::resize(Size size) { - return editSysmem().resize(size); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().resize(size); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } Buffer::Size Buffer::setData(Size size, const Byte* data) { - return editSysmem().setData(size, data); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().setData(size, data); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { @@ -272,6 +319,9 @@ Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { } Buffer::Size Buffer::append(Size size, const Byte* data) { - return editSysmem().append( size, data); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().append( size, data); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 3517b67203..98ad0a2d28 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -16,6 +16,7 @@ #include "Format.h" #include +#include #include #ifdef _DEBUG @@ -109,7 +110,15 @@ protected: }; class Buffer : public Resource { + static std::atomic _bufferCPUCount; + static std::atomic _bufferCPUMemoryUsage; + static void updateBufferCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + public: + static uint32_t getBufferCPUCount(); + static Size getBufferCPUMemoryUsage(); + static uint32_t getBufferGPUCount(); + static Size getBufferGPUMemoryUsage(); Buffer(); Buffer(Size size, const Byte* bytes); diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 3f2415d240..df93cd76a5 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -12,20 +12,77 @@ #include "Texture.h" #include - -#include +#include "GPULogging.h" +#include "Context.h" using namespace gpu; + +std::atomic Texture::_textureCPUCount{ 0 }; +std::atomic Texture::_textureCPUMemoryUsage{ 0 }; + +void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (prevObjectSize > newObjectSize) { + _textureCPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } else { + _textureCPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } +} + +uint32_t Texture::getTextureCPUCount() { + return _textureCPUCount.load(); +} + +Texture::Size Texture::getTextureCPUMemoryUsage() { + return _textureCPUMemoryUsage.load(); +} + +uint32_t Texture::getTextureGPUCount() { + return Context::getTextureGPUCount(); +} + +Texture::Size Texture::getTextureGPUMemoryUsage() { + return Context::getTextureGPUMemoryUsage(); + +} + uint8 Texture::NUM_FACES_PER_TYPE[NUM_TYPES] = {1, 1, 1, 6}; Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : - _sysmem(size, bytes), _format(format), + _sysmem(size, bytes), _isGPULoaded(false) { + Texture::updateTextureCPUMemoryUsage(0, _sysmem.getSize()); } Texture::Pixels::~Pixels() { + Texture::updateTextureCPUMemoryUsage(_sysmem.getSize(), 0); +} + +Texture::Size Texture::Pixels::resize(Size pSize) { + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.resize(pSize); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); + return newSize; +} + +Texture::Size Texture::Pixels::setData(const Element& format, Size size, const Byte* bytes ) { + _format = format; + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.setData(size, bytes); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); + _isGPULoaded = false; + return newSize; +} + +void Texture::Pixels::notifyGPULoaded() { + _isGPULoaded = true; + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.resize(0); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); } void Texture::Storage::assignTexture(Texture* texture) { @@ -59,15 +116,15 @@ const Texture::PixelsPointer Texture::Storage::getMipFace(uint16 level, uint8 fa void Texture::Storage::notifyMipFaceGPULoaded(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); - if (mipFace && (_type != TEX_CUBE)) { - mipFace->_isGPULoaded = true; - mipFace->_sysmem.resize(0); + // Free the mips + if (mipFace) { + mipFace->notifyGPULoaded(); } } bool Texture::Storage::isMipAvailable(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); - return (mipFace && mipFace->_sysmem.getSize()); + return (mipFace && mipFace->getSize()); } bool Texture::Storage::allocateMip(uint16 level) { @@ -103,9 +160,7 @@ bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size s auto faceBytes = bytes; Size allocated = 0; for (auto& face : mip) { - face->_format = format; - allocated += face->_sysmem.setData(sizePerFace, faceBytes); - face->_isGPULoaded = false; + allocated += face->setData(format, sizePerFace, faceBytes); faceBytes += sizePerFace; } @@ -122,9 +177,7 @@ bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Si Size allocated = 0; if (face < mip.size()) { auto mipFace = mip[face]; - mipFace->_format = format; - allocated += mipFace->_sysmem.setData(size, bytes); - mipFace->_isGPULoaded = false; + allocated += mipFace->setData(format, size, bytes); bumpStamp(); } @@ -171,10 +224,12 @@ Texture* Texture::createFromStorage(Storage* storage) { Texture::Texture(): Resource() { + _textureCPUCount++; } Texture::~Texture() { + _textureCPUCount--; } Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) { @@ -292,7 +347,7 @@ bool Texture::assignStoredMip(uint16 level, const Element& format, Size size, co } } - // THen check that the mem buffer passed make sense with its format + // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipSize(level, format); if (size == expectedSize) { _storage->assignMipData(level, format, size, bytes); @@ -323,7 +378,7 @@ bool Texture::assignStoredMipFace(uint16 level, const Element& format, Size size } } - // THen check that the mem buffer passed make sense with its format + // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipFaceSize(level, format); if (size == expectedSize) { _storage->assignMipFaceData(level, format, size, bytes, face); @@ -364,7 +419,7 @@ uint16 Texture::autoGenerateMips(uint16 maxMip) { uint16 Texture::getStoredMipWidth(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level); } return 0; @@ -372,7 +427,7 @@ uint16 Texture::getStoredMipWidth(uint16 level) const { uint16 Texture::getStoredMipHeight(uint16 level) const { PixelsPointer mip = accessStoredMipFace(level); - if (mip && mip->_sysmem.getSize()) { + if (mip && mip->getSize()) { return evalMipHeight(level); } return 0; @@ -380,7 +435,7 @@ uint16 Texture::getStoredMipHeight(uint16 level) const { uint16 Texture::getStoredMipDepth(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipDepth(level); } return 0; @@ -388,7 +443,7 @@ uint16 Texture::getStoredMipDepth(uint16 level) const { uint32 Texture::getStoredMipNumTexels(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); } return 0; @@ -396,7 +451,7 @@ uint32 Texture::getStoredMipNumTexels(uint16 level) const { uint32 Texture::getStoredMipSize(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level) * getTexelFormat().getSize(); } return 0; @@ -642,8 +697,8 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // for each face of cube texture for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { - auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getScalarCount(); - auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + auto numComponents = cubeTexture.accessStoredMipFace(0,face)->getFormat().getScalarCount(); + auto data = cubeTexture.accessStoredMipFace(0,face)->readData(); if (data == nullptr) { continue; } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index e05dc84c25..80fbc867e3 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -138,7 +138,14 @@ protected: }; class Texture : public Resource { + static std::atomic _textureCPUCount; + static std::atomic _textureCPUMemoryUsage; + static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: + static uint32_t getTextureCPUCount(); + static Size getTextureCPUMemoryUsage(); + static uint32_t getTextureGPUCount(); + static Size getTextureGPUMemoryUsage(); class Usage { public: @@ -194,9 +201,21 @@ public: Pixels(const Element& format, Size size, const Byte* bytes); ~Pixels(); - Sysmem _sysmem; + const Byte* readData() const { return _sysmem.readData(); } + Size getSize() const { return _sysmem.getSize(); } + Size resize(Size pSize); + Size setData(const Element& format, Size size, const Byte* bytes ); + + const Element& getFormat() const { return _format; } + + void notifyGPULoaded(); + + protected: Element _format; + Sysmem _sysmem; bool _isGPULoaded; + + friend class Texture; }; typedef std::shared_ptr< Pixels > PixelsPointer; @@ -448,7 +467,7 @@ typedef std::shared_ptr TexturePointer; typedef std::vector< TexturePointer > Textures; - // TODO: For now TextureView works with Buffer as a place holder for the Texture. + // TODO: For now TextureView works with Texture as a place holder for the Texture. // The overall logic should be about the same except that the Texture will be a real GL Texture under the hood class TextureView { public: diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index af30132b70..e97b077f24 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -81,6 +81,8 @@ AccountManager::AccountManager() : qRegisterMetaType("QHttpMultiPart*"); + qRegisterMetaType(); + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } @@ -215,12 +217,13 @@ void AccountManager::sendRequest(const QString& path, if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendRequest", Q_ARG(const QString&, path), - Q_ARG(AccountManagerAuth::Type, AccountManagerAuth::Required), + Q_ARG(AccountManagerAuth::Type, authType), Q_ARG(QNetworkAccessManager::Operation, operation), Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), Q_ARG(QHttpMultiPart*, dataMultiPart), Q_ARG(QVariantMap, propertyMap)); + return; } QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 2e74a5166c..5fcc8a40c3 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -58,8 +58,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall // if no callbacks specified, call our owns if (params.isEmpty()) { - params.jsonCallbackReceiver = this; - params.jsonCallbackMethod = "requestFinished"; params.errorCallbackReceiver = this; params.errorCallbackMethod = "requestError"; } @@ -70,10 +68,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params, NULL, multipart); } -void UserActivityLogger::requestFinished(QNetworkReply& requestReply) { - // qCDebug(networking) << object; -} - void UserActivityLogger::requestError(QNetworkReply& errorReply) { qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); } @@ -91,6 +85,15 @@ void UserActivityLogger::launch(QString applicationVersion, bool previousSession logAction(ACTION_NAME, actionDetails); } +void UserActivityLogger::insufficientGLVersion(QString glVersion) { + const QString ACTION_NAME = "insufficient_gl"; + QJsonObject actionDetails; + QString GL_VERSION = "glVersion"; + actionDetails.insert(GL_VERSION, glVersion); + + logAction(ACTION_NAME, actionDetails); +} + void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 8eda086521..acc3b685cd 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -30,6 +30,8 @@ public slots: void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); void launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime); + + void insufficientGLVersion(QString glVersion); void changedDisplayName(QString displayName); void changedModel(QString typeOfModel, QString modelURL); @@ -39,7 +41,6 @@ public slots: void wentTo(QString destinationType, QString destinationName); private slots: - void requestFinished(QNetworkReply& requestReply); void requestError(QNetworkReply& errorReply); private: diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index 088de49981..d46cae2404 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -15,6 +15,8 @@ #include +#include "Socket.h" + using namespace udt; static int packetMetaTypeId = qRegisterMetaType("Packet*"); @@ -28,15 +30,10 @@ static const std::array KEYS {{ }}; void xorHelper(char* start, int size, Key key) { - const auto end = start + size; - - auto p = start; - for (; p + sizeof(Key) < end; p += sizeof(Key)) { - *reinterpret_cast(p) ^= key; - } - - for (int i = 0; p < end; ++p || ++i) { - *p ^= *(reinterpret_cast(&key) + i); + auto current = start; + auto xorValue = reinterpret_cast(&key); + for (int i = 0; i < size; ++i) { + *(current++) ^= *(xorValue + (i % sizeof(Key))); } } diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 8ff2333a20..4b8c0b187c 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -51,7 +51,7 @@ PacketQueue::PacketPointer PacketQueue::takePacket() { --_currentIndex; } - return std::move(packet); + return packet; } unsigned int PacketQueue::nextIndex() { diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 2ffa42cb82..8933c984d5 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -27,6 +28,7 @@ #include "ControlPacket.h" #include "Packet.h" #include "PacketList.h" +#include "../UserActivityLogger.h" #include "Socket.h" using namespace udt; @@ -328,7 +330,39 @@ void SendQueue::run() { nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); // sleep as long as we need until next packet send, if we can - const auto timeToSleep = duration_cast(nextPacketTimestamp - p_high_resolution_clock::now()); + auto now = p_high_resolution_clock::now(); + auto timeToSleep = duration_cast(nextPacketTimestamp - now); + + // we're seeing SendQueues sleep for a long period of time here, + // which can lock the NodeList if it's attempting to clear connections + // for now we guard this by capping the time this thread and sleep for + + const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 }; + if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) { + qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds"; + qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count(); + qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta + << "NPT:" << nextPacketTimestamp.time_since_epoch().count() + << "NOW:" << now.time_since_epoch().count(); + + // alright, we're in a weird state + // we want to know why this is happening so we can implement a better fix than this guard + // send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep + static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep"; + + // setup a json object with the details we want + QJsonObject longSleepObject; + longSleepObject["timeToSleep"] = qint64(timeToSleep.count()); + longSleepObject["packetSendPeriod"] = _packetSendPeriod.load(); + longSleepObject["nextPacketDelta"] = nextPacketDelta; + longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count()); + longSleepObject["then"] = qint64(now.time_since_epoch().count()); + + // hopefully send this event using the user activity logger + UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject); + + timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS; + } std::this_thread::sleep_for(timeToSleep); } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 3860639614..c7fa5f5671 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -4,11 +4,11 @@ #include "PluginContainer.h" -void DisplayPlugin::activate() { - Parent::activate(); +bool DisplayPlugin::activate() { if (isHmd() && (getHmdScreen() >= 0)) { _container->showDisplayPluginsTools(); } + return Parent::activate(); } void DisplayPlugin::deactivate() { diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index e5ac78036e..f4f28176c7 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -59,7 +59,7 @@ class DisplayPlugin : public Plugin { Q_OBJECT using Parent = Plugin; public: - void activate() override; + bool activate() override; void deactivate() override; virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index ee10ce331c..fb5bf0ba55 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -38,8 +38,10 @@ public: virtual void deinit(); /// Called when a plugin is being activated for use. May be called multiple times. - virtual void activate() { + /// Returns true if plugin was successfully activated. + virtual bool activate() { _active = true; + return _active; } /// Called when a plugin is no longer being used. May be called multiple times. diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index 806c964ec0..b0329faa3e 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -17,12 +17,14 @@ #include +#include "EngineStats.h" using namespace render; Engine::Engine() : _sceneContext(std::make_shared()), _renderContext(std::make_shared()) { + addJob("Stats"); } void Engine::load() { @@ -57,4 +59,6 @@ void Engine::run() { for (auto job : _jobs) { job.run(_sceneContext, _renderContext); } + } + diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 1af0e6d76f..d2bb42e5ff 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -16,37 +16,37 @@ #include "Context.h" #include "Task.h" - namespace render { -// The render engine holds all render tasks, and is itself a render task. -// State flows through tasks to jobs via the render and scene contexts - -// the engine should not be known from its jobs. -class Engine : public Task { -public: - Engine(); - ~Engine() = default; + // The render engine holds all render tasks, and is itself a render task. + // State flows through tasks to jobs via the render and scene contexts - + // the engine should not be known from its jobs. + class Engine : public Task { + public: - // Load any persisted settings, and set up the presets - // This should be run after adding all jobs, and before building ui - void load(); + Engine(); + ~Engine() = default; - // Register the scene - void registerScene(const ScenePointer& scene) { _sceneContext->_scene = scene; } + // Load any persisted settings, and set up the presets + // This should be run after adding all jobs, and before building ui + void load(); - // Push a RenderContext - void setRenderContext(const RenderContext& renderContext) { (*_renderContext) = renderContext; } - RenderContextPointer getRenderContext() const { return _renderContext; } + // Register the scene + void registerScene(const ScenePointer& scene) { _sceneContext->_scene = scene; } - // Render a frame - // A frame must have a scene registered and a context set to render - void run(); + // Push a RenderContext + void setRenderContext(const RenderContext& renderContext) { (*_renderContext) = renderContext; } + RenderContextPointer getRenderContext() const { return _renderContext; } -protected: - SceneContextPointer _sceneContext; - RenderContextPointer _renderContext; -}; -using EnginePointer = std::shared_ptr; + // Render a frame + // A frame must have a scene registered and a context set to render + void run(); + + protected: + SceneContextPointer _sceneContext; + RenderContextPointer _renderContext; + }; + using EnginePointer = std::shared_ptr; } diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp new file mode 100644 index 0000000000..ee96e0cd86 --- /dev/null +++ b/libraries/render/src/render/EngineStats.cpp @@ -0,0 +1,49 @@ +// +// EngineStats.cpp +// render/src/render +// +// Created by Sam Gateau on 3/27/16. +// 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 +// +#include "EngineStats.h" + +#include + +using namespace render; + +void EngineStats::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { + // Tick time + + quint64 msecsElapsed = _frameTimer.restart(); + double frequency = 1000.0 / msecsElapsed; + + // Update the stats + auto config = std::static_pointer_cast(renderContext->jobConfig); + + config->bufferCPUCount = gpu::Buffer::getBufferCPUCount(); + config->bufferGPUCount = gpu::Buffer::getBufferGPUCount(); + config->bufferCPUMemoryUsage = gpu::Buffer::getBufferCPUMemoryUsage(); + config->bufferGPUMemoryUsage = gpu::Buffer::getBufferGPUMemoryUsage(); + + config->textureCPUCount = gpu::Texture::getTextureCPUCount(); + config->textureGPUCount = gpu::Texture::getTextureGPUCount(); + config->textureCPUMemoryUsage = gpu::Texture::getTextureCPUMemoryUsage(); + config->textureGPUMemoryUsage = gpu::Texture::getTextureGPUMemoryUsage(); + + gpu::ContextStats gpuStats(_gpuStats); + renderContext->args->_context->getStats(_gpuStats); + + config->frameDrawcallCount = _gpuStats._DSNumDrawcalls - gpuStats._DSNumDrawcalls; + config->frameDrawcallRate = config->frameDrawcallCount * frequency; + + config->frameTriangleCount = _gpuStats._DSNumTriangles - gpuStats._DSNumTriangles; + config->frameTriangleRate = config->frameTriangleCount * frequency; + + config->frameTextureCount = _gpuStats._RSNumTextureBounded - gpuStats._RSNumTextureBounded; + config->frameTextureRate = config->frameTextureCount * frequency; + + config->emitDirty(); +} diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h new file mode 100644 index 0000000000..478d94855e --- /dev/null +++ b/libraries/render/src/render/EngineStats.h @@ -0,0 +1,89 @@ +// +// EngineStats.h +// render/src/render +// +// Created by Sam Gateau on 3/27/16. +// 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 +// + +#ifndef hifi_render_EngineStats_h +#define hifi_render_EngineStats_h + +#include + +#include + +#include "Engine.h" + +namespace render { + + // A simple job collecting global stats on the Engine / Scene / GPU + class EngineStatsConfig : public Job::Config{ + Q_OBJECT + + Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY dirty) + Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY dirty) + Q_PROPERTY(qint64 bufferCPUMemoryUsage MEMBER bufferCPUMemoryUsage NOTIFY dirty) + Q_PROPERTY(qint64 bufferGPUMemoryUsage MEMBER bufferGPUMemoryUsage NOTIFY dirty) + + Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY dirty) + Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY dirty) + Q_PROPERTY(qint64 textureCPUMemoryUsage MEMBER textureCPUMemoryUsage NOTIFY dirty) + Q_PROPERTY(qint64 textureGPUMemoryUsage MEMBER textureGPUMemoryUsage NOTIFY dirty) + + Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty) + Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY dirty) + + Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY dirty) + Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY dirty) + + Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY dirty) + Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty) + + + public: + EngineStatsConfig() : Job::Config(true) {} + + quint32 bufferCPUCount{ 0 }; + quint32 bufferGPUCount{ 0 }; + qint64 bufferCPUMemoryUsage{ 0 }; + qint64 bufferGPUMemoryUsage{ 0 }; + + quint32 textureCPUCount{ 0 }; + quint32 textureGPUCount{ 0 }; + qint64 textureCPUMemoryUsage{ 0 }; + qint64 textureGPUMemoryUsage{ 0 }; + + quint32 frameDrawcallCount{ 0 }; + quint32 frameDrawcallRate{ 0 }; + + quint32 frameTriangleCount{ 0 }; + quint32 frameTriangleRate{ 0 }; + + quint32 frameTextureCount{ 0 }; + quint32 frameTextureRate{ 0 }; + + void emitDirty() { emit dirty(); } + + signals: + void dirty(); + }; + + class EngineStats { + gpu::ContextStats _gpuStats; + QElapsedTimer _frameTimer; + public: + using Config = EngineStatsConfig; + using JobModel = Job::Model; + + EngineStats() { _frameTimer.start(); } + + void configure(const Config& configuration) {} + void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext); + }; +} + +#endif \ No newline at end of file diff --git a/libraries/shared/src/GenericQueueThread.h b/libraries/shared/src/GenericQueueThread.h index 067d7a7989..28fcdb0ed6 100644 --- a/libraries/shared/src/GenericQueueThread.h +++ b/libraries/shared/src/GenericQueueThread.h @@ -45,18 +45,23 @@ protected: } virtual bool process() { + lock(); if (!_items.size()) { + unlock(); _hasItemsMutex.lock(); _hasItems.wait(&_hasItemsMutex, getMaxWait()); _hasItemsMutex.unlock(); + } else { + unlock(); } + lock(); if (!_items.size()) { + unlock(); return isStillRunning(); } Queue processItems; - lock(); processItems.swap(_items); unlock(); return processQueueItems(processItems); diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 53754ae241..296911ae3e 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -14,6 +14,7 @@ #ifndef hifi_SimpleMovingAverage_h #define hifi_SimpleMovingAverage_h +#include #include class SimpleMovingAverage { @@ -64,4 +65,45 @@ public: } }; +template class ThreadSafeMovingAverage { +public: + void clear() { + std::unique_lock lock(_lock); + _samples = 0; + } + + bool isAverageValid() const { + std::unique_lock lock(_lock); + return (_samples > 0); + } + + void addSample(T sample) { + std::unique_lock lock(_lock); + if (_samples > 0) { + _average = (sample * WEIGHTING) + (_average * ONE_MINUS_WEIGHTING); + } else { + _average = sample; + } + _samples++; + } + + T getAverage() const { + std::unique_lock lock(_lock); + return _average; + } + + size_t getSamples() const { + std::unique_lock lock(_lock); + return _samples; + } + +private: + const float WEIGHTING = 1.0f / (float)MAX_NUM_SAMPLES; + const float ONE_MINUS_WEIGHTING = 1.0f - WEIGHTING; + size_t _samples { 0 }; + T _average; + mutable std::mutex _lock; +}; + + #endif // hifi_SimpleMovingAverage_h diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 0228f77f4f..b964f305a4 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -8,35 +8,44 @@ #include "QmlWebWindowClass.h" -#include -#include #include -#include - #include #include -#include - -#include -#include -#include -#include - #include "OffscreenUi.h" static const char* const URL_PROPERTY = "source"; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, - [&](QObject* object) { return new QmlWebWindowClass(object); }); + auto properties = parseArguments(context); + QmlWebWindowClass* retVal { nullptr }; + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([&] { + retVal = new QmlWebWindowClass(); + retVal->initQml(properties); + }, true); + Q_ASSERT(retVal); + connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + return engine->newQObject(retVal); } -QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { +void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage)); + } else { + emit scriptEventReceived(scriptMessage); + } } +void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage)); + } else { + emit webEventReceived(webMessage); + } +} QString QmlWebWindowClass::getURL() const { QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant { diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 27c0e6996d..86d0e9b2c4 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -18,14 +18,21 @@ class QmlWebWindowClass : public QmlWindowClass { public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - QmlWebWindowClass(QObject* qmlWindow); -public slots: + public slots: QString getURL() const; void setURL(const QString& url); + void emitScriptEvent(const QVariant& scriptMessage); + void emitWebEvent(const QVariant& webMessage); + signals: void urlChanged(); + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); + +protected: + QString qmlSource() const override { return "QmlWebWindow.qml"; } }; #endif diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index b7fe330a4e..d18ada1f5a 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -20,203 +20,113 @@ #include #include -#include #include #include #include "OffscreenUi.h" -QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr }; -static QWebChannel webChannel; -static const uint16_t WEB_CHANNEL_PORT = 51016; -static std::atomic nextWindowId; static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; +static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge"; static const char* const WIDTH_PROPERTY = "width"; static const char* const HEIGHT_PROPERTY = "height"; static const char* const VISIBILE_PROPERTY = "visible"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; +static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 }; +static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 }; -void QmlScriptEventBridge::emitWebEvent(const QString& data) { - QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); -} - -void QmlScriptEventBridge::emitScriptEvent(const QString& data) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, - Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); -} - -class QmlWebTransport : public QWebChannelAbstractTransport { - Q_OBJECT -public: - QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { - // Translate from the websocket layer to the webchannel layer - connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { - QJsonParseError error; - QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error || !document.isObject()) { - qWarning() << "Unable to parse incoming JSON message" << message; - return; - } - emit messageReceived(document.object(), this); - }); - } - - virtual void sendMessage(const QJsonObject &message) override { - // Translate from the webchannel layer to the websocket layer - _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); - } - -private: - QWebSocket* const _webSocket; -}; - - -void QmlWindowClass::setupServer() { - if (!_webChannelServer) { - _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); - if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { - qFatal("Failed to open web socket server."); - } - - QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { - webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); - }); - } -} - -QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, - QScriptContext* context, QScriptEngine* engine, - std::function builder) -{ +QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { const auto argumentCount = context->argumentCount(); - QString url; QString title; - int width = -1, height = -1; - bool visible = true; - bool toolWindow = false; + QVariantMap properties; if (argumentCount > 1) { - if (!context->argument(0).isUndefined()) { - title = context->argument(0).toString(); + properties[TITLE_PROPERTY] = context->argument(0).toString(); } if (!context->argument(1).isUndefined()) { - url = context->argument(1).toString(); + properties[SOURCE_PROPERTY] = context->argument(1).toString(); } if (context->argument(2).isNumber()) { - width = context->argument(2).toInt32(); + properties[WIDTH_PROPERTY] = context->argument(2).toInt32(); } if (context->argument(3).isNumber()) { - height = context->argument(3).toInt32(); + properties[HEIGHT_PROPERTY] = context->argument(3).toInt32(); } if (context->argument(4).isBool()) { - toolWindow = context->argument(4).toBool(); + properties[TOOLWINDOW_PROPERTY] = context->argument(4).toBool(); } } else { - auto argumentObject = context->argument(0); - if (!argumentObject.property(TITLE_PROPERTY).isUndefined()) { - title = argumentObject.property(TITLE_PROPERTY).toString(); - } - if (!argumentObject.property(SOURCE_PROPERTY).isUndefined()) { - url = argumentObject.property(SOURCE_PROPERTY).toString(); - } - if (argumentObject.property(WIDTH_PROPERTY).isNumber()) { - width = argumentObject.property(WIDTH_PROPERTY).toInt32(); - } - if (argumentObject.property(HEIGHT_PROPERTY).isNumber()) { - height = argumentObject.property(HEIGHT_PROPERTY).toInt32(); - } - if (argumentObject.property(VISIBILE_PROPERTY).isBool()) { - visible = argumentObject.property(VISIBILE_PROPERTY).toBool(); - } - if (argumentObject.property(TOOLWINDOW_PROPERTY).isBool()) { - toolWindow = argumentObject.property(TOOLWINDOW_PROPERTY).toBool(); - } + properties = context->argument(0).toVariant().toMap(); } + QString url = properties[SOURCE_PROPERTY].toString(); if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { - url = QUrl::fromLocalFile(url).toString(); + properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url).toString(); } - if (width != -1 || height != -1) { - width = std::max(100, std::min(1280, width)); - height = std::max(100, std::min(720, height)); - } - - QmlWindowClass* retVal{ nullptr }; - auto offscreenUi = DependencyManager::get(); - - if (toolWindow) { - auto toolWindow = offscreenUi->getToolWindow(); - QVariantMap properties; - properties.insert(TITLE_PROPERTY, title); - properties.insert(SOURCE_PROPERTY, url); - if (width != -1 && height != -1) { - properties.insert(WIDTH_PROPERTY, width); - properties.insert(HEIGHT_PROPERTY, height); - } - - // Build the event bridge and wrapper on the main thread - QVariant newTabVar; - bool invokeResult = QMetaObject::invokeMethod(toolWindow, "addWebTab", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QVariant, newTabVar), - Q_ARG(QVariant, QVariant::fromValue(properties))); - - QQuickItem* newTab = qvariant_cast(newTabVar); - if (!invokeResult || !newTab) { - return QScriptValue(); - } - - offscreenUi->returnFromUiThread([&] { - setupServer(); - retVal = builder(newTab); - retVal->_toolWindow = true; - registerObject(url.toLower(), retVal); - return QVariant(); - }); - } else { - // Build the event bridge and wrapper on the main thread - QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, - Q_ARG(const QString&, qmlSource), - Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { - setupServer(); - retVal = builder(object); - context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); - registerObject(url.toLower(), retVal); - if (!title.isEmpty()) { - retVal->setTitle(title); - } - if (width != -1 && height != -1) { - retVal->setSize(width, height); - } - object->setProperty(SOURCE_PROPERTY, url); - if (visible) { - object->setProperty("visible", true); - } - })); - } - - retVal->_source = url; - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); - return engine->newQObject(retVal); + return properties; } + // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - return internalConstructor("QmlWindow.qml", context, engine, [&](QObject* object){ - return new QmlWindowClass(object); - }); + auto properties = parseArguments(context); + QmlWindowClass* retVal { nullptr }; + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([&] { + retVal = new QmlWindowClass(); + retVal->initQml(properties); + }, true); + Q_ASSERT(retVal); + connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + return engine->newQObject(retVal); } -QmlWindowClass::QmlWindowClass(QObject* qmlWindow) - : _windowId(++nextWindowId), _qmlWindow(qmlWindow) -{ - qDebug() << "Created window with ID " << _windowId; +QmlWindowClass::QmlWindowClass() { + +} + +void QmlWindowClass::initQml(QVariantMap properties) { + auto offscreenUi = DependencyManager::get(); + _toolWindow = properties.contains(TOOLWINDOW_PROPERTY) && properties[TOOLWINDOW_PROPERTY].toBool(); + _source = properties[SOURCE_PROPERTY].toString(); + + if (_toolWindow) { + // Build the event bridge and wrapper on the main thread + _qmlWindow = offscreenUi->getToolWindow(); + properties[EVENT_BRIDGE_PROPERTY] = QVariant::fromValue(this); + QVariant newTabVar; + bool invokeResult = QMetaObject::invokeMethod(_qmlWindow, "addWebTab", Qt::DirectConnection, + Q_RETURN_ARG(QVariant, newTabVar), + Q_ARG(QVariant, QVariant::fromValue(properties))); + Q_ASSERT(invokeResult); + } else { + // Build the event bridge and wrapper on the main thread + offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) { + _qmlWindow = object; + _qmlWindow->setProperty("eventBridge", QVariant::fromValue(this)); + context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); + if (properties.contains(TITLE_PROPERTY)) { + object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); + } + if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) { + uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() }; + requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE); + asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y)); + } + + bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); + object->setProperty(VISIBILE_PROPERTY, visible); + object->setProperty(SOURCE_PROPERTY, _source); + + // Forward messages received from QML on to the script + connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection); + }); + } Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); - // Forward messages received from QML on to the script - connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection); } void QmlWindowClass::sendToQml(const QVariant& message) { @@ -228,14 +138,6 @@ QmlWindowClass::~QmlWindowClass() { close(); } -void QmlWindowClass::registerObject(const QString& name, QObject* object) { - webChannel.registerObject(name, object); -} - -void QmlWindowClass::deregisterObject(QObject* object) { - webChannel.deregisterObject(object); -} - QQuickItem* QmlWindowClass::asQuickItem() const { if (_toolWindow) { return DependencyManager::get()->getToolWindow(); @@ -248,7 +150,6 @@ void QmlWindowClass::setVisible(bool visible) { if (_toolWindow) { // For tool window tabs we special case visibility as a function call on the tab parent // The tool window itself has special logic based on whether any tabs are visible - auto offscreenUi = DependencyManager::get(); QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible)); } else { DependencyManager::get()->executeOnUiThread([=] { @@ -359,5 +260,3 @@ void QmlWindowClass::raise() { } }); } - -#include "QmlWindowClass.moc" diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index fb7dbf1253..242f9b0dd4 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -13,45 +13,22 @@ #include #include #include -#include #include class QScriptEngine; class QScriptContext; -class QmlWindowClass; -class QWebSocketServer; -class QWebSocket; -class QmlScriptEventBridge : public QObject { - Q_OBJECT -public: - QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {} - -public slots : - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(int windowId, const QString& data); - -private: - const QmlWindowClass* _webWindow { nullptr }; - QWebSocket *_socket { nullptr }; -}; // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWindowClass : public QObject { Q_OBJECT - Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) - Q_PROPERTY(int windowId READ getWindowId CONSTANT) Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - QmlWindowClass(QObject* qmlWindow); + QmlWindowClass(); ~QmlWindowClass(); public slots: @@ -69,8 +46,7 @@ public slots: Q_INVOKABLE void raise(); Q_INVOKABLE void close(); - Q_INVOKABLE int getWindowId() const { return _windowId; }; - Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; + Q_INVOKABLE QObject* getEventBridge() { return this; }; // Scripts can use this to send a message to the QML object void sendToQml(const QVariant& message); @@ -89,21 +65,18 @@ protected slots: void hasClosed(); protected: - static QScriptValue internalConstructor(const QString& qmlSource, - QScriptContext* context, QScriptEngine* engine, - std::function function); - static void setupServer(); - static void registerObject(const QString& name, QObject* object); - static void deregisterObject(QObject* object); - static QWebSocketServer* _webChannelServer; + static QVariantMap parseArguments(QScriptContext* context); + static QScriptValue internalConstructor(QScriptContext* context, QScriptEngine* engine, + std::function function); + virtual QString qmlSource() const { return "QmlWindow.qml"; } + + virtual void initQml(QVariantMap properties); QQuickItem* asQuickItem() const; - QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; // FIXME needs to be initialized in the ctor once we have support // for tool window panes in QML bool _toolWindow { false }; - const int _windowId; QPointer _qmlWindow; QString _source; }; diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index e2143dbdcf..f2b8c04827 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -451,10 +451,10 @@ bool NeuronPlugin::isSupported() const { #endif } -void NeuronPlugin::activate() { -#ifdef HAVE_NEURON +bool NeuronPlugin::activate() { InputPlugin::activate(); +#ifdef HAVE_NEURON // register with userInputMapper auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -473,11 +473,15 @@ void NeuronPlugin::activate() { if (!_socketRef) { // error qCCritical(inputplugins) << "NeuronPlugin: error connecting to " << _serverAddress.c_str() << ":" << _serverPort << ", error = " << BRGetLastErrorMessage(); + return false; } else { qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort; BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); + return true; } +#else + return false; #endif } diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h index afe8530b07..99859dcacb 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.h +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -31,7 +31,7 @@ public: virtual const QString& getName() const override { return NAME; } const QString& getID() const override { return NEURON_ID_STRING; } - virtual void activate() override; + virtual bool activate() override; virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 58da2c5df0..7091b20d21 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -99,15 +99,19 @@ void SDL2Manager::deinit() { #endif } -void SDL2Manager::activate() { +bool SDL2Manager::activate() { + InputPlugin::activate(); + #ifdef HAVE_SDL2 auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); } + return true; +#else + return false; #endif - InputPlugin::activate(); } void SDL2Manager::deactivate() { diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index a88e41128d..f69e23ee98 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -35,7 +35,7 @@ public: virtual void deinit() override; /// Called when a plugin is being activated for use. May be called multiple times. - virtual void activate() override; + virtual bool activate() override; /// Called when a plugin is no longer being used. May be called multiple times. virtual void deactivate() override; diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 9fdce3add4..eb55d84664 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -79,7 +79,7 @@ bool SixenseManager::isSupported() const { #endif } -void SixenseManager::activate() { +bool SixenseManager::activate() { InputPlugin::activate(); #ifdef HAVE_SIXENSE @@ -101,6 +101,9 @@ void SixenseManager::activate() { loadSettings(); _sixenseLoaded = (sixenseInit() == SIXENSE_SUCCESS); + return _sixenseLoaded; +#else + return false; #endif } diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index 5106c87836..a46614b17a 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -32,7 +32,7 @@ public: virtual const QString& getName() const override { return NAME; } virtual const QString& getID() const override { return HYDRA_ID_STRING; } - virtual void activate() override; + virtual bool activate() override; virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 29fc014a64..e23d8cade6 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -34,8 +34,11 @@ void OculusBaseDisplayPlugin::customizeContext() { Parent::customizeContext(); } -void OculusBaseDisplayPlugin::internalActivate() { +bool OculusBaseDisplayPlugin::internalActivate() { _session = acquireOculusSession(); + if (!_session) { + return false; + } _hmdDesc = ovr_GetHmdDesc(_session); @@ -65,7 +68,7 @@ void OculusBaseDisplayPlugin::internalActivate() { if (!OVR_SUCCESS(ovr_ConfigureTracking(_session, ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { - qWarning() << "Could not attach to sensor device"; + logWarning("Failed to attach to sensor device"); } // Parent class relies on our _session intialization, so it must come after that. @@ -81,7 +84,7 @@ void OculusBaseDisplayPlugin::internalActivate() { // This must come after the initialization, so that the values calculated // above are available during the customizeContext call (when not running // in threaded present mode) - Parent::internalActivate(); + return Parent::internalActivate(); } void OculusBaseDisplayPlugin::internalDeactivate() { diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 5455b61a60..d21b0561bc 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -24,7 +24,7 @@ public: protected: void customizeContext() override; - void internalActivate() override; + bool internalActivate() override; void internalDeactivate() override; protected: diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 71a858e1e8..8c3a676c61 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -67,7 +67,7 @@ void OculusDisplayPlugin::hmdPresent() { ovrLayerHeader* layers = &_sceneLayer.Header; ovrResult result = ovr_SubmitFrame(_session, _currentRenderFrameIndex, &_viewScaleDesc, &layers, 1); if (!OVR_SUCCESS(result)) { - qDebug() << result; + logWarning("Failed to present"); } } _sceneFbo->Increment(); diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index db65c72aac..170be05952 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -10,16 +10,34 @@ #include #include + using Mutex = std::mutex; using Lock = std::unique_lock; - Q_DECLARE_LOGGING_CATEGORY(oculus) Q_LOGGING_CATEGORY(oculus, "hifi.plugins.oculus") static std::atomic refCount { 0 }; static ovrSession session { nullptr }; +inline ovrErrorInfo getError() { + ovrErrorInfo error; + ovr_GetLastErrorInfo(&error); + return error; +} + +void logWarning(const char* what) { + qWarning(oculus) << what << ":" << getError().ErrorString; +} + +void logFatal(const char* what) { + std::string error("[oculus] "); + error += what; + error += ": "; + error += getError().ErrorString; + qFatal(error.c_str()); +} + bool oculusAvailable() { ovrDetectResult detect = ovr_Detect(0); return (detect.IsOculusServiceRunning && detect.IsOculusHMDConnected); @@ -37,14 +55,14 @@ ovrSession acquireOculusSession() { init.ConnectionTimeoutMS = 0; init.LogCallback = nullptr; if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { - qCWarning(oculus) << "Failed to initialize Oculus SDK"; + logWarning("Failed to initialize Oculus SDK"); return session; } Q_ASSERT(0 == refCount); ovrGraphicsLuid luid; if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { - qCWarning(oculus) << "Failed to acquire Oculus session"; + logWarning("Failed to acquire Oculus session"); return session; } } @@ -105,7 +123,7 @@ void SwapFramebufferWrapper::initColor() { destroyColor(); if (!OVR_SUCCESS(ovr_CreateSwapTextureSetGL(_session, GL_SRGB8_ALPHA8, size.x, size.y, &color))) { - qFatal("Unable to create swap textures"); + logFatal("Failed to create swap textures"); } for (int i = 0; i < color->TextureCount; ++i) { diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index cd527b184b..e10e058ad2 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -14,6 +14,8 @@ #include +void logWarning(const char* what); +void logFatal(const char* what); bool oculusAvailable(); ovrSession acquireOculusSession(); void releaseOculusSession(); diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 73e18aa697..396f55b932 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -68,18 +68,20 @@ bool OculusLegacyDisplayPlugin::isSupported() const { return result; } -void OculusLegacyDisplayPlugin::internalActivate() { +bool OculusLegacyDisplayPlugin::internalActivate() { Parent::internalActivate(); if (!(ovr_Initialize(nullptr))) { Q_ASSERT(false); qFatal("Failed to Initialize SDK"); + return false; } _hswDismissed = false; _hmd = ovrHmd_Create(0); if (!_hmd) { qFatal("Failed to acquire HMD"); + return false; } _ipd = ovrHmd_GetFloat(_hmd, OVR_KEY_IPD, _ipd); @@ -107,6 +109,8 @@ void OculusLegacyDisplayPlugin::internalActivate() { ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0)) { qFatal("Could not attach to sensor device"); } + + return true; } void OculusLegacyDisplayPlugin::internalDeactivate() { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index bf4ae26205..187c0681e9 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -31,7 +31,7 @@ public: virtual float getTargetFrameRate() override; protected: - virtual void internalActivate() override; + virtual bool internalActivate() override; virtual void internalDeactivate() override; virtual void customizeContext() override; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 0e7541066e..c4d8b252f0 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -41,14 +41,18 @@ bool OpenVrDisplayPlugin::isSupported() const { return !isOculusPresent() && vr::VR_IsHmdPresent(); } -void OpenVrDisplayPlugin::internalActivate() { +bool OpenVrDisplayPlugin::internalActivate() { Parent::internalActivate(); + _container->setIsOptionChecked(StandingHMDSensorMode, true); if (!_system) { _system = acquireOpenVrSystem(); } - Q_ASSERT(_system); + if (!_system) { + qWarning() << "Failed to initialize OpenVR"; + return false; + } _system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); // Recommended render target size is per-eye, so double the X size for @@ -86,6 +90,8 @@ void OpenVrDisplayPlugin::internalActivate() { } else { qDebug() << "OpenVR: error could not get chaperone pointer"; } + + return true; } void OpenVrDisplayPlugin::internalDeactivate() { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 78b76cb78d..022af5b06d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -30,7 +30,7 @@ public: virtual void updateHeadPose(uint32_t frameIndex) override; protected: - void internalActivate() override; + bool internalActivate() override; void internalDeactivate() override; void hmdPresent() override; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 2e2c0face0..c1ed5aa880 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -53,8 +53,9 @@ bool ViveControllerManager::isSupported() const { return !isOculusPresent() && vr::VR_IsHmdPresent(); } -void ViveControllerManager::activate() { +bool ViveControllerManager::activate() { InputPlugin::activate(); + _container->addMenu(MENU_PATH); _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, [this] (bool clicked) { this->setRenderControllers(clicked); }, @@ -122,6 +123,8 @@ void ViveControllerManager::activate() { auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); _registeredWithInputMapper = true; + + return true; } void ViveControllerManager::deactivate() { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 480fbfeb90..d3645304c5 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -37,7 +37,7 @@ public: virtual bool isJointController() const override { return true; } const QString& getName() const override { return NAME; } - virtual void activate() override; + virtual bool activate() override; virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } @@ -111,8 +111,6 @@ private: std::shared_ptr _inputDevice { std::make_shared(_system) }; static const QString NAME; - - }; #endif // hifi__ViveControllerManager diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns index a0c2af881b..70a00698bd 100644 Binary files a/server-console/resources/console-beta.icns and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico index 90962c4871..57df32f878 100644 Binary files a/server-console/resources/console-beta.ico and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png index 4fa8e1ce80..c2df15832d 100644 Binary files a/server-console/resources/console-beta.png and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns index 45dd334a3e..8810f804c3 100644 Binary files a/server-console/resources/console.icns and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico index 872a4a7a68..c5a4bb0381 100644 Binary files a/server-console/resources/console.ico and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png index a8260f9687..9fa1b843ec 100644 Binary files a/server-console/resources/console.png and b/server-console/resources/console.png differ