From 5a771e3a168357708d646dcba70c79ceb7eb4cb4 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:08:53 -0500 Subject: [PATCH 1/7] [Case 10865] Ctrl/Cmd/Shift+Click Multi-Select for Asset Browser. * Removes manual selectionModel update from controls-uit/Tree.qml * This was interfering with the Key+Click functionality. It was introduced via Commit 99617600c47 to address a selection issue; however, it's unclear what that issue was. Selection within the Asset Browser works without it at present. * Amends the ContextMenu within the AssetBrowser to work in conjunction with the multi-selection changes. * ContextMenu will _only_ display when triggered within the current selection be it one or more items as opposed to previous behavior of selecting the item the menu was triggered on. * CopyURL is only enabled when the selection size is 1 * Rename is only enabled when the selection size is 1 * Delete is enabled when the selection size is greater than 0 Changes Committed: modified: interface/resources/qml/controls-uit/Tree.qml modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/controls-uit/Tree.qml | 4 -- interface/resources/qml/hifi/AssetServer.qml | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 6bd11295b1..5199a10a27 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -202,8 +202,4 @@ TreeView { } onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) - - onClicked: { - selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect); - } } diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 37c3c2adab..7c16b19865 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -694,7 +694,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -731,50 +731,59 @@ Windows.ScrollingWindow { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItems; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItems == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItems == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItems > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -885,7 +894,7 @@ Windows.ScrollingWindow { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.ContentSection { id: uploadSection @@ -945,7 +954,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( uploadSection ) } } From dfdf28f37e4572bb95b6354be850356ad8bffa27 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:34:05 -0500 Subject: [PATCH 2/7] [Case 10865] Some cleanup for multi-selection (details below). * Remove setCurrentIndex call when looping over current selection. * Changed selectedItems var name to selectedItemCount to clarify what it represents. * Change path arg name to paths to clarify that there can be more than a single path contained within it. Changes Committed: modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/hifi/AssetServer.qml | 49 ++++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 7c16b19865..30f76fecd6 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -37,7 +37,7 @@ Windows.ScrollingWindow { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -75,17 +75,17 @@ Windows.ScrollingWindow { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -145,7 +145,7 @@ Windows.ScrollingWindow { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -155,7 +155,7 @@ Windows.ScrollingWindow { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -333,29 +333,28 @@ Windows.ScrollingWindow { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = desktop.messageBox({ @@ -367,7 +366,7 @@ Windows.ScrollingWindow { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -743,7 +742,7 @@ Windows.ScrollingWindow { // the selection. var clickedIndex = treeView.indexAt(mouse.x, mouse.y); var displayContextMenu = false; - for ( var i = 0; i < selectedItems; ++i) { + for ( var i = 0; i < selectedItemCount; ++i) { var currentSelectedIndex = treeView.selection.selectedIndexes[i]; if (clickedIndex === currentSelectedIndex) { contextMenu.popup(); @@ -761,7 +760,7 @@ Windows.ScrollingWindow { MenuItem { text: "Copy URL" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { copyURLToClipboard(treeView.selection.currentIndex); } @@ -769,7 +768,7 @@ Windows.ScrollingWindow { MenuItem { text: "Rename" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { renameFile(treeView.selection.currentIndex); } @@ -777,7 +776,7 @@ Windows.ScrollingWindow { MenuItem { text: "Delete" - enabled: (selectedItems > 0) + enabled: (selectedItemCount > 0) onTriggered: { deleteFile(); } @@ -796,8 +795,8 @@ Windows.ScrollingWindow { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } From 70e23f3ce86f5837f24eaea39c422aeea23c64f0 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 30 Jan 2018 14:00:09 -0500 Subject: [PATCH 3/7] [Case 10865] Bringing in multi-select fix from Desktop AssetServer. Changes Committed: modified: interface/resources/qml/hifi/dialogs/TabletAssetServer.qml --- .../qml/hifi/dialogs/TabletAssetServer.qml | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index a02496a252..9c61206592 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -39,7 +39,7 @@ Rectangle { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -76,17 +76,17 @@ Rectangle { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -146,7 +146,7 @@ Rectangle { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -156,7 +156,7 @@ Rectangle { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -334,29 +334,28 @@ Rectangle { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = tabletRoot.messageBox({ @@ -368,7 +367,7 @@ Rectangle { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -693,7 +692,7 @@ Rectangle { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -730,50 +729,59 @@ Rectangle { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItemCount; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItemCount == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItemCount == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItemCount > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -786,8 +794,8 @@ Rectangle { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } @@ -884,7 +892,7 @@ Rectangle { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.TabletContentSection { id: uploadSection @@ -961,7 +969,7 @@ Rectangle { } } } - } + }// End_OF( uploadSection ) } } From b6ac3484284f7d05e9322658bd281d91bdd95d1e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 16:30:07 -0800 Subject: [PATCH 4/7] get children of animated entity joints to follow along, again --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 9 +++++++++ libraries/shared/src/SpatiallyNestable.h | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 137203f475..c8d22bb06c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -985,6 +985,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { return; } + bool changed { false }; // relay any inbound joint changes from scripts/animation/network to the model/rig _jointDataLock.withWriteLock([&] { for (int index = 0; index < _localJointData.size(); ++index) { @@ -992,13 +993,21 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { if (jointData.rotationDirty) { model->setJointRotation(index, true, jointData.joint.rotation, 1.0f); jointData.rotationDirty = false; + changed = true; } if (jointData.translationDirty) { model->setJointTranslation(index, true, jointData.joint.translation, 1.0f); jointData.translationDirty = false; + changed = true; } } }); + + if (changed) { + forEachChild([&](SpatiallyNestablePointer object) { + object->locationChanged(tellPhysics); + }); + } } using namespace render; diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 090ca4c266..5d4793ba4e 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -207,6 +207,10 @@ public: void dump(const QString& prefix = "") const; + virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed + virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed + virtual void parentDeleted() { } // called on children of a deleted parent + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; @@ -218,10 +222,6 @@ protected: mutable ReadWriteLockable _childrenLock; mutable QHash _children; - virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed - virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed - virtual void parentDeleted() { } // called on children of a deleted parent - // _queryAACube is used to decide where something lives in the octree mutable AACube _queryAACube; mutable bool _queryAACubeSet { false }; From 31c2b8fea92c3b01ca9d8b379ee705205a1aebc5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 16:31:38 -0800 Subject: [PATCH 5/7] oops --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c8d22bb06c..ca15382c71 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1005,7 +1005,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { if (changed) { forEachChild([&](SpatiallyNestablePointer object) { - object->locationChanged(tellPhysics); + object->locationChanged(false); }); } } From ddcee05b14637a342dd286c93482fba289300a0f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 13 Feb 2018 09:41:00 -0800 Subject: [PATCH 6/7] added control button for controller API --- interface/resources/controllers/keyboardMouse.json | 4 ++-- .../src/input-plugins/KeyboardMouseDevice.cpp | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 54d2467f78..b3f16a115e 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -88,8 +88,8 @@ ] }, - { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, - { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index e9ec6d8910..ef7f482e44 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -54,11 +54,9 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() { void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { auto input = _inputDevice->makeInput((Qt::Key) event->key()); - if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) { - auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); - if (result.second) { - // key pressed again ? without catching the release event ? - } + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); + if (result.second) { + // key pressed again ? without catching the release event ? } } @@ -237,6 +235,7 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString())); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control")); availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton")); availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton")); From 8853bd63884450383ed3a5aeed6f310c3d54ca49 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 14 Feb 2018 11:11:28 -0800 Subject: [PATCH 7/7] Fix spectator camera crash --- interface/src/Application.cpp | 1 + .../display-plugins/OpenGLDisplayPlugin.cpp | 43 +++++++++++-------- .../src/display-plugins/OpenGLDisplayPlugin.h | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 27 ++++++++++++ libraries/gl/src/gl/OffscreenGLCanvas.h | 3 ++ .../qml/src/qml/impl/RenderEventHandler.cpp | 1 + plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- 7 files changed, 59 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0eb36173c2..5a340f471e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2338,6 +2338,7 @@ void Application::initializeGL() { qFatal("Unable to make offscreen context current"); } _offscreenContext->doneCurrent(); + _offscreenContext->setThreadContext(); _renderEventHandler = new RenderEventHandler(_glWidget->qglContext()); // The UI can't be created until the primary OpenGL diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 40dcf1b8c7..9bd7d89634 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -130,14 +131,14 @@ public: CHECK_GL_ERROR(); _context->doneCurrent(); while (!_shutdown) { - if (_pendingMainThreadOperation) { + if (_pendingOtherThreadOperation) { PROFILE_RANGE(render, "MainThreadOp") { Lock lock(_mutex); _context->doneCurrent(); // Move the context to the main thread - _context->moveToThread(qApp->thread()); - _pendingMainThreadOperation = false; + _context->moveToThread(_targetOperationThread); + _pendingOtherThreadOperation = false; // Release the main thread to do it's action _condition.notify_one(); } @@ -146,7 +147,7 @@ public: { // Main thread does it's thing while we wait on the lock to release Lock lock(_mutex); - _condition.wait(lock, [&] { return _finishedMainThreadOperation; }); + _condition.wait(lock, [&] { return _finishedOtherThreadOperation; }); } } @@ -214,23 +215,25 @@ public: _condition.notify_one(); } - void withMainThreadContext(std::function f) { + void withOtherThreadContext(std::function f) { // Signal to the thread that there is work to be done on the main thread Lock lock(_mutex); - _pendingMainThreadOperation = true; - _finishedMainThreadOperation = false; - _condition.wait(lock, [&] { return !_pendingMainThreadOperation; }); + _targetOperationThread = QThread::currentThread(); + _pendingOtherThreadOperation = true; + _finishedOtherThreadOperation = false; + _condition.wait(lock, [&] { return !_pendingOtherThreadOperation; }); _context->makeCurrent(); f(); _context->doneCurrent(); + _targetOperationThread = nullptr; // Move the context back to the presentation thread _context->moveToThread(this); // restore control of the context to the presentation thread and signal // the end of the operation - _finishedMainThreadOperation = true; + _finishedOtherThreadOperation = true; lock.unlock(); _condition.notify_one(); } @@ -244,9 +247,11 @@ private: Mutex _mutex; // Used to allow the main thread to perform context operations Condition _condition; - bool _pendingMainThreadOperation { false }; - bool _finishedMainThreadOperation { false }; - QThread* _mainThread { nullptr }; + + + QThread* _targetOperationThread { nullptr }; + bool _pendingOtherThreadOperation { false }; + bool _finishedOtherThreadOperation { false }; std::queue _newPluginQueue; gl::Context* _context { nullptr }; }; @@ -744,10 +749,12 @@ void OpenGLDisplayPlugin::swapBuffers() { context->swapBuffers(); } -void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { +void OpenGLDisplayPlugin::withOtherThreadContext(std::function f) const { static auto presentThread = DependencyManager::get(); - presentThread->withMainThreadContext(f); - _container->makeRenderingContextCurrent(); + presentThread->withOtherThreadContext(f); + if (!OffscreenGLCanvas::restoreThreadContext()) { + qWarning("Unable to restore original OpenGL context"); + } } bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) { @@ -784,7 +791,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { } auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32); - withMainThreadContext([&] { + withOtherThreadContext([&] { glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); }); return screenshot.mirrored(false, true); @@ -797,7 +804,7 @@ QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const { auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(region.z, region.w, QImage::Format_ARGB32); - withMainThreadContext([&] { + withOtherThreadContext([&] { glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); }); return screenshot.mirrored(false, true); @@ -886,7 +893,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() { void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) { #if !defined(USE_GLES) auto glBackend = const_cast(*this).getGLBackend(); - withMainThreadContext([&] { + withOtherThreadContext([&] { GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture()); GLuint targetTexture = target->texture(); GLuint fbo[2] {0, 0}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 1176471b40..bf06486095 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -119,7 +119,7 @@ protected: void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); virtual void updateFrameData(); - void withMainThreadContext(std::function f) const; + void withOtherThreadContext(std::function f) const; void present(); virtual void swapBuffers(); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 380ba085cc..1bde9e289e 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -119,3 +120,29 @@ void OffscreenGLCanvas::moveToThreadWithContext(QThread* thread) { moveToThread(thread); _context->moveToThread(thread); } + +static const char* THREAD_CONTEXT_PROPERTY = "offscreenGlCanvas"; + +void OffscreenGLCanvas::setThreadContext() { + QThread::currentThread()->setProperty(THREAD_CONTEXT_PROPERTY, QVariant::fromValue(this)); +} + +bool OffscreenGLCanvas::restoreThreadContext() { + // Restore the rendering context for this thread + auto threadCanvasVariant = QThread::currentThread()->property(THREAD_CONTEXT_PROPERTY); + if (!threadCanvasVariant.isValid()) { + return false; + } + + auto threadCanvasObject = qvariant_cast(threadCanvasVariant); + auto threadCanvas = static_cast(threadCanvasObject); + if (!threadCanvas) { + return false; + } + + if (!threadCanvas->makeCurrent()) { + qFatal("Unable to restore Offscreen rendering context"); + } + + return true; +} diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index be0e0d9678..ed644b98fb 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -32,6 +32,9 @@ public: } QObject* getContextObject(); + void setThreadContext(); + static bool restoreThreadContext(); + private slots: void onMessageLogged(const QOpenGLDebugMessage &debugMessage); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index cce1b68da9..6b66ce9314 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -58,6 +58,7 @@ void RenderEventHandler::onInitalize() { return; } + _canvas.setThreadContext(); if (!_canvas.makeCurrent()) { qFatal("Unable to make QML rendering context current on render thread"); } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 4b5f0e6517..2949e72c74 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -485,7 +485,7 @@ bool OpenVrDisplayPlugin::internalActivate() { if (_threadedSubmit) { _submitThread = std::make_shared(*this); if (!_submitCanvas) { - withMainThreadContext([&] { + withOtherThreadContext([&] { _submitCanvas = std::make_shared(); _submitCanvas->create(); _submitCanvas->doneCurrent();