From cfa847ba546006168a2da807026dc6a9f754f5e2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 31 Mar 2016 09:45:28 -0700 Subject: [PATCH 01/14] don't put empty folders at end of scripts tree. allow /~/ to work from outside scripts --- libraries/script-engine/src/BatchLoader.cpp | 6 ++++-- libraries/script-engine/src/ScriptEngine.cpp | 5 ++--- libraries/script-engine/src/ScriptEngines.cpp | 2 +- libraries/script-engine/src/ScriptsModel.cpp | 3 +++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index ad3f422813..f3e6242216 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -18,6 +18,7 @@ #include #include #include "ResourceManager.h" +#include "ScriptEngines.h" BatchLoader::BatchLoader(const QList& urls) : QObject(), @@ -34,8 +35,9 @@ void BatchLoader::start() { } _started = true; - - for (const auto& url : _urls) { + + for (const auto& rawURL : _urls) { + QUrl url = expandScriptUrl(normalizeScriptURL(rawURL)); auto request = ResourceManager::createResourceRequest(this, url); if (!request) { _data.insert(url, QString()); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 94662308fd..c7a3396078 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -220,11 +220,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { return; } - _fileNameString = scriptURL.toString(); + QUrl url = expandScriptUrl(normalizeScriptURL(scriptURL)); + _fileNameString = url.toString(); _isReloading = reload; - QUrl url(scriptURL); - bool isPending; auto scriptCache = DependencyManager::get(); scriptCache->getScript(url, this, isPending, reload); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 16301dc890..f22d048661 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -429,7 +429,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &ScriptEngines::onScriptEngineError); // get the script engine object to load the script at the designated script URL - scriptEngine->loadURL(QUrl(expandScriptUrl(scriptUrl.toString())), reload); + scriptEngine->loadURL(scriptUrl, reload); } return scriptEngine; diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 9513a333bc..0c58966e81 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -309,6 +309,9 @@ void ScriptsModel::rebuildTree() { QString hash; QStringList pathList = script->getLocalPath().split(tr("/")); pathList.removeLast(); + if (pathList.isEmpty()) { + continue; + } QStringList::const_iterator pathIterator; for (pathIterator = pathList.constBegin(); pathIterator != pathList.constEnd(); ++pathIterator) { hash.append(*pathIterator + "/"); From b53968fd6465bce1e16cddf8b8009d3d9fea8cd1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Mar 2016 23:12:53 -0700 Subject: [PATCH 02/14] Add automatic switching the Oculus headphones and mic when activating the plugin --- libraries/plugins/src/plugins/DisplayPlugin.h | 3 + plugins/oculus/CMakeLists.txt | 5 +- plugins/oculus/src/OculusBaseDisplayPlugin.h | 1 + plugins/oculus/src/OculusDisplayPlugin.cpp | 105 ++++++++++++++++++ plugins/oculus/src/OculusDisplayPlugin.h | 11 +- 5 files changed, 118 insertions(+), 7 deletions(-) diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 77d984f924..b855e08ff1 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -74,6 +74,9 @@ public: /// whether the HMD is being worn virtual bool isDisplayVisible() const { return false; } + virtual QString getPreferredAudioInDevice() const { return QString(); } + virtual QString getPreferredAudioOutDevice() const { return QString(); } + // Rendering support /** diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index e5cbffda21..a91690ecdd 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -12,8 +12,8 @@ if (WIN32) add_definitions(-DGLEW_STATIC) set(TARGET_NAME oculus) - setup_hifi_plugin() - link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins) + setup_hifi_plugin(Multimedia) + link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking) include_hifi_library_headers(octree) @@ -21,5 +21,6 @@ if (WIN32) find_package(LibOVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) + target_link_libraries(${TARGET_NAME} Winmm.lib) endif() \ No newline at end of file diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 2259a4ca89..71f876a82a 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -21,6 +21,7 @@ public: // Stereo specific methods virtual void resetSensors() override final; virtual void beginFrameRender(uint32_t frameIndex) override; + float getTargetFrameRate() override { return _hmdDesc.DisplayRefreshRate; } protected: diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 8078e8d6ec..9e52bb80d3 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -6,7 +6,22 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusDisplayPlugin.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + #include +#include #include "OculusHelpers.h" const QString OculusDisplayPlugin::NAME("Oculus Rift"); @@ -18,9 +33,38 @@ bool OculusDisplayPlugin::internalActivate() { if (result && _session) { ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); } + + auto audioClient = DependencyManager::get(); + QString riftAudioIn = getPreferredAudioInDevice(); + if (riftAudioIn != QString()) { + _savedAudioIn = audioClient->getDeviceName(QAudio::Mode::AudioInput); + QMetaObject::invokeMethod(audioClient.data(), "switchInputToAudioDevice", Q_ARG(const QString&, riftAudioIn)); + } else { + _savedAudioIn.clear(); + } + + QString riftAudioOut = getPreferredAudioOutDevice(); + if (riftAudioOut != QString()) { + _savedAudioOut = audioClient->getDeviceName(QAudio::Mode::AudioOutput); + QMetaObject::invokeMethod(audioClient.data(), "switchOutputToAudioDevice", Q_ARG(const QString&, riftAudioOut)); + } else { + _savedAudioOut.clear(); + } + return result; } +void OculusDisplayPlugin::internalDeactivate() { + auto audioClient = DependencyManager::get(); + if (_savedAudioIn != QString()) { + QMetaObject::invokeMethod(audioClient.data(), "switchInputToAudioDevice", Q_ARG(const QString&, _savedAudioIn)); + } + if (_savedAudioOut != QString()) { + QMetaObject::invokeMethod(audioClient.data(), "switchOutputToAudioDevice", Q_ARG(const QString&, _savedAudioOut)); + } + Parent::internalDeactivate(); +} + void OculusDisplayPlugin::cycleDebugOutput() { if (_session) { currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); @@ -86,3 +130,64 @@ void OculusDisplayPlugin::hmdPresent() { } } } + +bool OculusDisplayPlugin::isHmdMounted() const { + ovrSessionStatus status; + return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) && + (ovrFalse != status.HmdMounted)); +} + +static QString audioDeviceFriendlyName(LPCWSTR device) { + QString deviceName; + + HRESULT hr = S_OK; + CoInitialize(NULL); + IMMDeviceEnumerator* pMMDeviceEnumerator = NULL; + CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator); + IMMDevice* pEndpoint; + hr = pMMDeviceEnumerator->GetDevice(device, &pEndpoint); + if (hr == E_NOTFOUND) { + printf("Audio Error: device not found\n"); + deviceName = QString("NONE"); + } else { + IPropertyStore* pPropertyStore; + pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); + pEndpoint->Release(); + pEndpoint = NULL; + PROPVARIANT pv; + PropVariantInit(&pv); + hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); + pPropertyStore->Release(); + pPropertyStore = NULL; + deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); + if (!IsWindows8OrGreater()) { + // Windows 7 provides only the 31 first characters of the device name. + const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; + deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); + } + qDebug() << " device:" << deviceName; + PropVariantClear(&pv); + } + pMMDeviceEnumerator->Release(); + pMMDeviceEnumerator = NULL; + CoUninitialize(); + return deviceName; +} + + +QString OculusDisplayPlugin::getPreferredAudioInDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) { + return QString(); + } + return audioDeviceFriendlyName(buffer); +} + +QString OculusDisplayPlugin::getPreferredAudioOutDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) { + return QString(); + } + return audioDeviceFriendlyName(buffer); +} + diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index e7d7791e7f..a5dd70c68d 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -12,20 +12,19 @@ struct SwapFramebufferWrapper; using SwapFboPtr = QSharedPointer; -const float TARGET_RATE_Oculus = 75.0f; - class OculusDisplayPlugin : public OculusBaseDisplayPlugin { using Parent = OculusBaseDisplayPlugin; public: const QString& getName() const override { return NAME; } - float getTargetFrameRate() override { return TARGET_RATE_Oculus; } + QString getPreferredAudioInDevice() const override; + QString getPreferredAudioOutDevice() const override; protected: bool internalActivate() override; + void internalDeactivate() override; void hmdPresent() override; - // FIXME update with Oculus API call once it's available in the SDK - bool isHmdMounted() const override { return true; } + bool isHmdMounted() const override; void customizeContext() override; void uncustomizeContext() override; void cycleDebugOutput() override; @@ -34,6 +33,8 @@ private: static const QString NAME; bool _enablePreview { false }; bool _monoPreview { true }; + QString _savedAudioIn; + QString _savedAudioOut; SwapFboPtr _sceneFbo; }; From 3de5f73a1faffa53dfb13f1526c54b1bde03dfa3 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Sun, 27 Mar 2016 17:29:11 -0700 Subject: [PATCH 03/14] Fixup model on tex load --- .../src/RenderableModelEntityItem.cpp | 6 ++---- .../src/model-networking/ModelCache.cpp | 7 +++---- .../src/model-networking/ModelCache.h | 4 ---- libraries/render-utils/src/Model.cpp | 21 ++++++++++--------- libraries/render-utils/src/Model.h | 6 ++++-- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e18f85211f..3909472f6c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -133,10 +133,8 @@ void RenderableModelEntityItem::remapTextures() { return; // nothing to do if the model has not yet loaded } - auto& geometry = _model->getGeometry()->getGeometry(); - if (!_originalTexturesRead) { - _originalTextures = geometry->getTextures(); + _originalTextures = _model->getTextures(); _originalTexturesRead = true; // Default to _originalTextures to avoid remapping immediately and lagging on load @@ -152,7 +150,7 @@ void RenderableModelEntityItem::remapTextures() { auto newTextures = parseTexturesToMap(textures); if (newTextures != _currentTextures) { - geometry->setTextures(newTextures); + _model->setTextures(newTextures); _currentTextures = newTextures; } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2a6f33b964..88b05ef5f3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -270,6 +270,9 @@ void Geometry::setTextures(const QVariantMap& textureMap) { material->setTextures(textureMap); _areTexturesLoaded = false; + + // If we only use cached textures, they should all be loaded + areTexturesLoaded(); } } } else { @@ -279,8 +282,6 @@ void Geometry::setTextures(const QVariantMap& textureMap) { bool Geometry::areTexturesLoaded() const { if (!_areTexturesLoaded) { - _hasTransparentTextures = false; - for (auto& material : _materials) { // Check if material textures are loaded if (std::any_of(material->_textures.cbegin(), material->_textures.cend(), @@ -293,8 +294,6 @@ bool Geometry::areTexturesLoaded() const { const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP]; if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) { material->resetOpacityMap(); - - _hasTransparentTextures |= material->getKey().isTranslucent(); } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index dad7883a6a..5598f15a62 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -74,9 +74,6 @@ public: void setTextures(const QVariantMap& textureMap); virtual bool areTexturesLoaded() const; - // Returns true if any albedo texture has a non-masking alpha channel. - // This can only be known after areTexturesLoaded(). - bool hasTransparentTextures() const { return _hasTransparentTextures; } protected: friend class GeometryMappingResource; @@ -91,7 +88,6 @@ protected: private: mutable bool _areTexturesLoaded { false }; - mutable bool _hasTransparentTextures { false }; }; /// A geometry loaded from the network. diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0bd2687169..39ffc9dd9a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -76,14 +76,9 @@ AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { if (readyToAddToScene()) { - // Once textures are loaded, fixup if they are now transparent - if (_needsUpdateTransparentTextures && _geometry->getGeometry()->areTexturesLoaded()) { - _needsUpdateTransparentTextures = false; - bool hasTransparentTextures = _geometry->getGeometry()->hasTransparentTextures(); - if (_hasTransparentTextures != hasTransparentTextures) { - _hasTransparentTextures = hasTransparentTextures; - return true; - } + if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) { + _needsUpdateTextures = false; + return true; } if (!_readyWhenAdded) { return true; @@ -791,6 +786,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } +void Model::setTextures(const QVariantMap& textures) { + if (isLoaded()) { + _needsUpdateTextures = true; + _geometry->getGeometry()->setTextures(textures); + } +} + void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { @@ -807,8 +809,7 @@ void Model::setURL(const QUrl& url) { } _needsReload = true; - _needsUpdateTransparentTextures = true; - _hasTransparentTextures = false; + _needsUpdateTextures = true; _meshGroupsKnown = false; invalidCalculatedMeshBoxes(); deleteGeometry(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index e4a8fa3b36..03760378f0 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -129,6 +129,9 @@ public: /// Returns a reference to the shared collision geometry. const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } + const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); } + void setTextures(const QVariantMap& textures); + /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); } @@ -385,9 +388,8 @@ protected: bool _readyWhenAdded { false }; bool _needsReload { true }; bool _needsUpdateClusterMatrices { true }; - mutable bool _needsUpdateTransparentTextures { true }; - mutable bool _hasTransparentTextures { false }; bool _showCollisionHull { false }; + mutable bool _needsUpdateTextures { true }; friend class ModelMeshPartPayload; RigPointer _rig; From 984bacdae3527a8e8bb5f3efe5cba15612aa6af8 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 30 Mar 2016 18:09:02 -0700 Subject: [PATCH 04/14] Rearrange model entity render to reduce flicker --- .../src/RenderableModelEntityItem.cpp | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3909472f6c..5b8e4a644a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -364,41 +364,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::Model); if (hasModel()) { - if (_model) { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - - // check to see if when we added our models to the scene they were ready, if they were not ready, then - // fix them up in the scene - bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; - if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { - _showCollisionHull = shouldShowCollisionHull; - render::PendingChanges pendingChanges; - - _model->removeFromScene(scene, pendingChanges); - - render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(getThisPointer(), statusGetters); - _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); - - scene->enqueuePendingChanges(pendingChanges); - } - - // FIXME: this seems like it could be optimized if we tracked our last known visible state in - // the renderable item. As it stands now the model checks it's visible/invisible state - // so most of the time we don't do anything in this function. - _model->setVisibleInScene(getVisible(), scene); - } - - - remapTextures(); + // Prepare the current frame { - // float alpha = getLocalRenderAlpha(); - if (!_model || _needsModelReload) { // TODO: this getModel() appears to be about 3% of model render time. We should optimize PerformanceTimer perfTimer("getModel"); EntityTreeRenderer* renderer = static_cast(args->_renderer); getModel(renderer); + + // Remap textures immediately after loading to avoid flicker + remapTextures(); } if (_model) { @@ -429,15 +404,42 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } }); updateModelBounds(); + } + } - // Check if the URL has changed - // Do this last as the getModel is queued for the next frame, - // and we need to keep state directing the model to reinitialize - auto& currentURL = getParsedModelURL(); - if (currentURL != _model->getURL()) { - // Defer setting the url to the render thread - getModel(_myRenderer); - } + // Enqueue updates for the next frame + if (_model) { + + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + // FIXME: this seems like it could be optimized if we tracked our last known visible state in + // the renderable item. As it stands now the model checks it's visible/invisible state + // so most of the time we don't do anything in this function. + _model->setVisibleInScene(getVisible(), scene); + + // Remap textures for the next frame to avoid flicker + remapTextures(); + + // check to see if when we added our models to the scene they were ready, if they were not ready, then + // fix them up in the scene + bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0; + if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { + _showCollisionHull = shouldShowCollisionHull; + render::PendingChanges pendingChanges; + + _model->removeFromScene(scene, pendingChanges); + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(getThisPointer(), statusGetters); + _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); + + scene->enqueuePendingChanges(pendingChanges); + } + + auto& currentURL = getParsedModelURL(); + if (currentURL != _model->getURL()) { + // Defer setting the url to the render thread + getModel(_myRenderer); } } } else { From 377a20e98304832b98657b2b923119acf2e2c0b8 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 30 Mar 2016 18:54:12 -0700 Subject: [PATCH 05/14] Use default texs for empty json --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 5b8e4a644a..b510f58b12 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -116,11 +116,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { QJsonParseError error; QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + // If textures are invalid, revert to original textures if (error.error != QJsonParseError::NoError) { qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures; return _originalTextures; } + QVariantMap texturesMap = texturesJson.toVariant().toMap(); + // If textures are unset, revert to original textures + if (texturesMap.isEmpty()) { + return _originalTextures; + } + return texturesJson.toVariant().toMap(); } From e4cba1433393ca903ac5db5b511935a9a70a713d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 30 Mar 2016 18:54:25 -0700 Subject: [PATCH 06/14] Avoid recreating model items when adding to scene --- .../src/RenderableModelEntityItem.cpp | 2 - libraries/render-utils/src/Model.cpp | 97 ++++++++----------- libraries/render-utils/src/Model.h | 5 +- 3 files changed, 44 insertions(+), 60 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index b510f58b12..a9dcb3883c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -434,8 +434,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { _showCollisionHull = shouldShowCollisionHull; render::PendingChanges pendingChanges; - _model->removeFromScene(scene, pendingChanges); - render::Item::Status::Getters statusGetters; makeEntityItemStatusGetters(getThisPointer(), statusGetters); _model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 39ffc9dd9a..3a5928f3d1 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -541,43 +541,6 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr scen } } - -bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, bool showCollisionHull) { - - if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { - _showCollisionHull = showCollisionHull; - segregateMeshGroups(); - } - - bool somethingAdded = false; - - foreach (auto renderItem, _modelMeshRenderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _modelMeshRenderItems.insert(item, renderPayload); - somethingAdded = true; - } - - foreach (auto renderItem, _collisionRenderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](MeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _collisionRenderItems.insert(item, renderPayload); - somethingAdded = true; - } - - _readyWhenAdded = readyToAddToScene(); - - return somethingAdded; -} - bool Model::addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, @@ -589,28 +552,48 @@ bool Model::addToScene(std::shared_ptr scene, bool somethingAdded = false; - foreach (auto renderItem, _modelMeshRenderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - renderPayload->addStatusGetters(statusGetters); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _modelMeshRenderItems.insert(item, renderPayload); - somethingAdded = true; + if (_modelMeshRenderItems.size()) { + for (auto item : _modelMeshRenderItems.keys()) { + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + } else { + for (auto renderItem : _modelMeshRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); + } + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](ModelMeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _modelMeshRenderItems.insert(item, renderPayload); + somethingAdded = true; + } } - foreach (auto renderItem, _collisionRenderItemsSet) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - renderPayload->addStatusGetters(statusGetters); - pendingChanges.resetItem(item, renderPayload); - pendingChanges.updateItem(item, [](MeshPartPayload& data) { - data.notifyLocationChanged(); - }); - _collisionRenderItems.insert(item, renderPayload); - somethingAdded = true; + if (_collisionRenderItems.size()) { + for (auto item : _collisionRenderItems.keys()) { + pendingChanges.updateItem(item, [](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + } + } else { + for (auto renderItem : _collisionRenderItemsSet) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); + } + pendingChanges.resetItem(item, renderPayload); + pendingChanges.updateItem(item, [](MeshPartPayload& data) { + data.notifyLocationChanged(); + }); + _collisionRenderItems.insert(item, renderPayload); + somethingAdded = true; + } } _readyWhenAdded = readyToAddToScene(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 03760378f0..239d0e7c28 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -87,7 +87,10 @@ public: bool initWhenReady(render::ScenePointer scene); bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, - bool showCollisionHull = false); + bool showCollisionHull = false) { + auto getters = render::Item::Status::Getters(0); + return addToScene(scene, pendingChanges, getters, showCollisionHull); + } bool addToScene(std::shared_ptr scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters, From bc2901aa966ba63c12d383c6e9719f2e8e5b6ea3 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 31 Mar 2016 12:54:23 -0700 Subject: [PATCH 07/14] Fix model fetch delay --- .../model-networking/src/model-networking/ModelCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2a6f33b964..9b5666742d 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -66,7 +66,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { GeometryExtra extra{ mapping, textureBaseUrl }; // Get the raw GeometryResource, not the wrapped NetworkGeometry - _geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast(); + _geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast(); if (_geometryResource->isLoaded()) { onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); @@ -226,7 +226,7 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar std::shared_ptr ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { GeometryExtra geometryExtra = { mapping, textureBaseUrl }; GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast(); - return std::make_shared(resource); + return resource ? std::make_shared(resource) : NetworkGeometry::Pointer(); } const QVariantMap Geometry::getTextures() const { From 97e96f7b6790531b51b1da100940e72e34fe2a34 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 31 Mar 2016 10:11:20 -0700 Subject: [PATCH 08/14] PR comments --- .../src/scripting/HMDScriptingInterface.cpp | 8 ++ .../src/scripting/HMDScriptingInterface.h | 2 + libraries/audio-client/src/AudioClient.cpp | 62 ++++++++++---- libraries/audio-client/src/AudioClient.h | 5 ++ plugins/oculus/src/OculusDisplayPlugin.cpp | 85 ++----------------- plugins/oculus/src/OculusDisplayPlugin.h | 3 - 6 files changed, 65 insertions(+), 100 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 5949b3318a..7bf1547a3c 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -97,3 +97,11 @@ bool HMDScriptingInterface::isMounted() const{ auto displayPlugin = qApp->getActiveDisplayPlugin(); return (displayPlugin->isHmd() && displayPlugin->isDisplayVisible()); } + +QString HMDScriptingInterface::preferredAudioInput() const { + return qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice(); +} + +QString HMDScriptingInterface::preferredAudioOutput() const { + return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 0057fe4eb9..d4c7b7cc0e 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -34,6 +34,8 @@ public: Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; + Q_INVOKABLE QString preferredAudioInput() const; + Q_INVOKABLE QString preferredAudioOutput() const; public: HMDScriptingInterface(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a81a6dab09..0c7a79e2a3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -175,6 +175,50 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio return (numSourceSamples * ratio) + 0.5f; } +#ifdef Q_OS_WIN +QString friendlyNameForAudioDevice(IMMDevice* pEndpoint) { + QString deviceName; + IPropertyStore* pPropertyStore; + pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); + pEndpoint->Release(); + pEndpoint = NULL; + PROPVARIANT pv; + PropVariantInit(&pv); + HRESULT hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); + pPropertyStore->Release(); + pPropertyStore = NULL; + deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); + if (!IsWindows8OrGreater()) { + // Windows 7 provides only the 31 first characters of the device name. + const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; + deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); + } + PropVariantClear(&pv); + return deviceName; +} + +QString AudioClient::friendlyNameForAudioDevice(wchar_t* guid) { + QString deviceName; + HRESULT hr = S_OK; + CoInitialize(NULL); + IMMDeviceEnumerator* pMMDeviceEnumerator = NULL; + CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator); + IMMDevice* pEndpoint; + hr = pMMDeviceEnumerator->GetDevice(guid, &pEndpoint); + if (hr == E_NOTFOUND) { + printf("Audio Error: device not found\n"); + deviceName = QString("NONE"); + } else { + deviceName = ::friendlyNameForAudioDevice(pEndpoint); + } + pMMDeviceEnumerator->Release(); + pMMDeviceEnumerator = NULL; + CoUninitialize(); + return deviceName; +} + +#endif + QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #ifdef __APPLE__ if (QAudioDeviceInfo::availableDevices(mode).size() > 1) { @@ -248,23 +292,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { printf("Audio Error: device not found\n"); deviceName = QString("NONE"); } else { - IPropertyStore* pPropertyStore; - pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); - pEndpoint->Release(); - pEndpoint = NULL; - PROPVARIANT pv; - PropVariantInit(&pv); - hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); - pPropertyStore->Release(); - pPropertyStore = NULL; - deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); - if (!IsWindows8OrGreater()) { - // Windows 7 provides only the 31 first characters of the device name. - const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; - deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); - } - qCDebug(audioclient) << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName; - PropVariantClear(&pv); + deviceName = friendlyNameForAudioDevice(pEndpoint); } pMMDeviceEnumerator->Release(); pMMDeviceEnumerator = NULL; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index d3145629ee..3a14c878f6 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -126,6 +127,10 @@ public: static const float CALLBACK_ACCELERATOR_RATIO; +#ifdef Q_OS_WIN + static QString friendlyNameForAudioDevice(wchar_t* guid); +#endif + public slots: void start(); void stop(); diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 9e52bb80d3..a53643ba21 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -7,21 +7,13 @@ // #include "OculusDisplayPlugin.h" -#ifdef WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#include -#endif +// Odd ordering of header is required to avoid 'macro redinition warnings' +#include #include -#include #include -#include + #include "OculusHelpers.h" const QString OculusDisplayPlugin::NAME("Oculus Rift"); @@ -33,38 +25,9 @@ bool OculusDisplayPlugin::internalActivate() { if (result && _session) { ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); } - - auto audioClient = DependencyManager::get(); - QString riftAudioIn = getPreferredAudioInDevice(); - if (riftAudioIn != QString()) { - _savedAudioIn = audioClient->getDeviceName(QAudio::Mode::AudioInput); - QMetaObject::invokeMethod(audioClient.data(), "switchInputToAudioDevice", Q_ARG(const QString&, riftAudioIn)); - } else { - _savedAudioIn.clear(); - } - - QString riftAudioOut = getPreferredAudioOutDevice(); - if (riftAudioOut != QString()) { - _savedAudioOut = audioClient->getDeviceName(QAudio::Mode::AudioOutput); - QMetaObject::invokeMethod(audioClient.data(), "switchOutputToAudioDevice", Q_ARG(const QString&, riftAudioOut)); - } else { - _savedAudioOut.clear(); - } - return result; } -void OculusDisplayPlugin::internalDeactivate() { - auto audioClient = DependencyManager::get(); - if (_savedAudioIn != QString()) { - QMetaObject::invokeMethod(audioClient.data(), "switchInputToAudioDevice", Q_ARG(const QString&, _savedAudioIn)); - } - if (_savedAudioOut != QString()) { - QMetaObject::invokeMethod(audioClient.data(), "switchOutputToAudioDevice", Q_ARG(const QString&, _savedAudioOut)); - } - Parent::internalDeactivate(); -} - void OculusDisplayPlugin::cycleDebugOutput() { if (_session) { currentDebugMode = static_cast((currentDebugMode + 1) % ovrPerfHud_Count); @@ -137,50 +100,12 @@ bool OculusDisplayPlugin::isHmdMounted() const { (ovrFalse != status.HmdMounted)); } -static QString audioDeviceFriendlyName(LPCWSTR device) { - QString deviceName; - - HRESULT hr = S_OK; - CoInitialize(NULL); - IMMDeviceEnumerator* pMMDeviceEnumerator = NULL; - CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator); - IMMDevice* pEndpoint; - hr = pMMDeviceEnumerator->GetDevice(device, &pEndpoint); - if (hr == E_NOTFOUND) { - printf("Audio Error: device not found\n"); - deviceName = QString("NONE"); - } else { - IPropertyStore* pPropertyStore; - pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore); - pEndpoint->Release(); - pEndpoint = NULL; - PROPVARIANT pv; - PropVariantInit(&pv); - hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); - pPropertyStore->Release(); - pPropertyStore = NULL; - deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); - if (!IsWindows8OrGreater()) { - // Windows 7 provides only the 31 first characters of the device name. - const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; - deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN); - } - qDebug() << " device:" << deviceName; - PropVariantClear(&pv); - } - pMMDeviceEnumerator->Release(); - pMMDeviceEnumerator = NULL; - CoUninitialize(); - return deviceName; -} - - QString OculusDisplayPlugin::getPreferredAudioInDevice() const { WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) { return QString(); } - return audioDeviceFriendlyName(buffer); + return AudioClient::friendlyNameForAudioDevice(buffer); } QString OculusDisplayPlugin::getPreferredAudioOutDevice() const { @@ -188,6 +113,6 @@ QString OculusDisplayPlugin::getPreferredAudioOutDevice() const { if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) { return QString(); } - return audioDeviceFriendlyName(buffer); + return AudioClient::friendlyNameForAudioDevice(buffer); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index a5dd70c68d..d6cd6f6f3d 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -22,7 +22,6 @@ public: protected: bool internalActivate() override; - void internalDeactivate() override; void hmdPresent() override; bool isHmdMounted() const override; void customizeContext() override; @@ -33,8 +32,6 @@ private: static const QString NAME; bool _enablePreview { false }; bool _monoPreview { true }; - QString _savedAudioIn; - QString _savedAudioOut; SwapFboPtr _sceneFbo; }; From 63595e196e17e74fa084949636a56ff4dff532ef Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 31 Mar 2016 14:02:47 -0700 Subject: [PATCH 09/14] fix batch-loaded local scripts --- libraries/script-engine/src/ScriptEngine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c7a3396078..95ca4ab4f8 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -847,7 +847,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { QUrl url(include); // first lets check to see if it's already a full URL if (!url.scheme().isEmpty()) { - return url; + return expandScriptUrl(normalizeScriptURL(url)); } // we apparently weren't a fully qualified url, so, let's assume we're relative @@ -864,7 +864,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { } // at this point we should have a legitimate fully qualified URL for our parent - url = parentURL.resolved(url); + url = expandScriptUrl(normalizeScriptURL(parentURL.resolved(url))); return url; } From 27406f5b214b7af11a419239b5fa5472f9e3cf99 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 31 Mar 2016 15:10:03 -0700 Subject: [PATCH 10/14] Add texture stress test --- examples/tests/cube_texture.png | Bin 0 -> 2986055 bytes examples/tests/textureStress.fs | 41 +++++++++++++++++ examples/tests/textureStress.js | 67 +++++++++++++++++++++++++++ examples/tests/textureStress.qml | 69 ++++++++++++++++++++++++++++ libraries/ui/src/QmlWindowClass.cpp | 7 ++- libraries/ui/src/QmlWindowClass.h | 1 + 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 examples/tests/cube_texture.png create mode 100644 examples/tests/textureStress.fs create mode 100644 examples/tests/textureStress.js create mode 100644 examples/tests/textureStress.qml diff --git a/examples/tests/cube_texture.png b/examples/tests/cube_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..ed65d8d6cbefc98bc89319eb4879bb183c14e133 GIT binary patch literal 2986055 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