diff --git a/examples/tests/cube_texture.png b/examples/tests/cube_texture.png new file mode 100644 index 0000000000..ed65d8d6cb Binary files /dev/null and b/examples/tests/cube_texture.png differ diff --git a/examples/tests/textureStress.fs b/examples/tests/textureStress.fs new file mode 100644 index 0000000000..cb59c9984f --- /dev/null +++ b/examples/tests/textureStress.fs @@ -0,0 +1,41 @@ +float aspect(vec2 v) { + return v.x / v.y; +} + +vec3 aspectCorrectedTexture() { + vec2 uv = _position.xy; + uv += 0.5; + uv.y = 1.0 - uv.y; + + float targetAspect = iWorldScale.x / iWorldScale.y; + float sourceAspect = aspect(iChannelResolution[0].xy); + float aspectCorrection = sourceAspect / targetAspect; + if (aspectCorrection > 1.0) { + float offset = aspectCorrection - 1.0; + float halfOffset = offset / 2.0; + uv.y -= halfOffset; + uv.y *= aspectCorrection; + } else { + float offset = 1.0 - aspectCorrection; + float halfOffset = offset / 2.0; + uv.x -= halfOffset; + uv.x /= aspectCorrection; + } + + if (any(lessThan(uv, vec2(0.0)))) { + return vec3(0.0); + } + + if (any(greaterThan(uv, vec2(1.0)))) { + return vec3(0.0); + } + + vec4 color = texture(iChannel0, uv); + return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x)); +} + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + specular = aspectCorrectedTexture(); + return 1.0; +} + diff --git a/examples/tests/textureStress.js b/examples/tests/textureStress.js new file mode 100644 index 0000000000..daf6eb41ec --- /dev/null +++ b/examples/tests/textureStress.js @@ -0,0 +1,67 @@ +Script.include("https://s3.amazonaws.com/DreamingContent/scripts/Austin.js"); + +var ENTITY_SPAWN_LIMIT = 500; +var ENTITY_LIFETIME = 600; +var RADIUS = 1.0; // Spawn within this radius (square) +var TEST_ENTITY_NAME = "EntitySpawnTest"; + +var entities = []; +var textureIndex = 0; +var texture = Script.resolvePath('cube_texture.png'); +var shader = Script.resolvePath('textureStress.fs'); +var qml = Script.resolvePath('textureStress.qml'); +qmlWindow = new OverlayWindow({ + title: 'Test Qml', + source: qml, + height: 240, + width: 320, + toolWindow: false, + visible: true +}); + +function deleteItems(count) { + if (!count) { + var ids = Entities.findEntities(MyAvatar.position, 50); + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id, ["name"]); + if (properties.name === TEST_ENTITY_NAME) { + Entities.deleteEntity(id); + } + }, this); + entities = []; + return; + } else { + // FIXME... implement + } +} + +function createItems(count) { + for (var i = 0; i < count; ++i) { + var newEntity = Entities.addEntity({ + type: "Box", + name: TEST_ENTITY_NAME, + position: AUSTIN.avatarRelativePosition(AUSTIN.randomPositionXZ({ x: 0, y: 0, z: -2 }, RADIUS)), + color: { r: 255, g: 255, b: 255 }, + dimensions: AUSTIN.randomDimensions(), + lifetime: ENTITY_LIFETIME, + userData: JSON.stringify({ + ProceduralEntity: { + version: 2, + shaderUrl: shader, + channels: [ texture + "?" + textureIndex++ ] + } + }) + }); + entities.push(newEntity); + } +} + +qmlWindow.fromQml.connect(function(message){ + print(message); + if (message[0] === "create") { + var count = message[1] || 1; + createItems(message[1] || 1); + } else if (message[0] === "delete") { + deleteItems(message[1]); + } +}); diff --git a/examples/tests/textureStress.qml b/examples/tests/textureStress.qml new file mode 100644 index 0000000000..1a8b994475 --- /dev/null +++ b/examples/tests/textureStress.qml @@ -0,0 +1,69 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + id: root + width: parent ? parent.width : 100 + height: parent ? parent.height : 100 + + signal sendToScript(var message); + + Text { + id: label + text: "GPU Texture Usage: " + } + Text { + id: usage + anchors.left: label.right + anchors.leftMargin: 8 + text: "N/A" + Timer { + repeat: true + running: true + interval: 500 + onTriggered: { + usage.text = Render.getConfig("Stats")["textureGPUMemoryUsage"]; + } + } + } + + Column { + + + anchors { left: parent.left; right: parent.right; top: label.bottom; topMargin: 8; bottom: parent.bottom } + spacing: 8 + + Button { + text: "Add 1" + onClicked: root.sendToScript(["create", 1]); + } + Button { + text: "Add 10" + onClicked: root.sendToScript(["create", 10]); + } + Button { + text: "Add 100" + onClicked: root.sendToScript(["create", 100]); + } + /* + Button { + text: "Delete 1" + onClicked: root.sendToScript(["delete", 1]); + } + Button { + text: "Delete 10" + onClicked: root.sendToScript(["delete", 10]); + } + Button { + text: "Delete 100" + onClicked: root.sendToScript(["delete", 100]); + } + */ + Button { + text: "Delete All" + onClicked: root.sendToScript(["delete", 0]); + } + } +} + + 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/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index ca666443fa..02d3f8873e 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,7 +36,7 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) { QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); + return QSharedPointer(new Animation(url), &Resource::deleter); } Animation::Animation(const QUrl& url) : Resource(url) {} 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/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index a7af1bdda2..abcdb2da7c 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -36,5 +36,5 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { QSharedPointer SoundCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { qCDebug(audio) << "Requesting sound at" << url.toString(); - return QSharedPointer(new Sound(url), &Resource::allReferencesCleared); + return QSharedPointer(new Sound(url), &Resource::deleter); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e18f85211f..a9dcb3883c 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(); } @@ -133,10 +140,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 +157,7 @@ void RenderableModelEntityItem::remapTextures() { auto newTextures = parseTexturesToMap(textures); if (newTextures != _currentTextures) { - geometry->setTextures(newTextures); + _model->setTextures(newTextures); _currentTextures = newTextures; } } @@ -366,41 +371,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) { @@ -431,15 +411,40 @@ 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; + + 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 { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index d94b6a4b93..73aac84d35 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -460,6 +460,14 @@ uint32 Texture::getStoredMipSize(uint16 level) const { return 0; } +gpu::Resource::Size Texture::getStoredSize() const { + auto size = 0; + for (int level = 0; level < evalNumMips(); ++level) { + size += getStoredMipSize(level); + } + return size; +} + uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) { uint16 sample = numSamplesTried; if (numSamplesTried <= 1) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index fc965f3251..8df2791f3b 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -289,9 +289,12 @@ public: Stamp getStamp() const { return _stamp; } Stamp getDataStamp() const { return _storage->getStamp(); } - // The size in bytes of data stored in the texture + // The theoretical size in bytes of data stored in the texture Size getSize() const { return _size; } + // The actual size in bytes of data stored in the texture + Size getStoredSize() const; + // Resize, unless auto mips mode would destroy all the sub mips Size resize1D(uint16 width, uint16 numSamples); Size resize2D(uint16 width, uint16 height, uint16 numSamples); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2a6f33b964..4120062308 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -66,16 +66,15 @@ 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(); + // Avoid caching nested resources - their references will be held by the parent + _geometryResource->_isCacheable = false; if (_geometryResource->isLoaded()) { onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty()); } else { connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded); } - - // Avoid caching nested resources - their references will be held by the parent - _geometryResource->_isCacheable = false; } } @@ -86,6 +85,10 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) { _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; } + + // Avoid holding onto extra references + _geometryResource.reset(); + finishedLoading(success); } @@ -157,7 +160,7 @@ class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) : - GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {} + GeometryResource(url, textureBaseUrl.isValid() ? textureBaseUrl : url), _mapping(mapping) {} virtual void downloadFinished(const QByteArray& data) override; @@ -166,7 +169,6 @@ protected: private: QVariantHash _mapping; - QUrl _textureBaseUrl; }; void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { @@ -220,13 +222,20 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl); } - return QSharedPointer(resource, &Resource::allReferencesCleared); + return QSharedPointer(resource, &Resource::deleter); } 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); + if (resource) { + if (resource->isLoaded() && !resource->hasTextures()) { + resource->setTextures(); + } + return std::make_shared(resource); + } else { + return NetworkGeometry::Pointer(); + } } const QVariantMap Geometry::getTextures() const { @@ -270,6 +279,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 +291,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 +303,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(); } } @@ -313,6 +321,21 @@ const std::shared_ptr Geometry::getShapeMaterial(int shap return nullptr; } +void GeometryResource::deleter() { + resetTextures(); + Resource::deleter(); +} + +void GeometryResource::setTextures() { + for (const FBXMaterial& material : _geometry->materials) { + _materials.push_back(std::make_shared(material, _textureBaseUrl)); + } +} + +void GeometryResource::resetTextures() { + _materials.clear(); +} + NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) { connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished); connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index dad7883a6a..45f78f4f19 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. @@ -99,15 +95,24 @@ class GeometryResource : public Resource, public Geometry { public: using Pointer = QSharedPointer; - GeometryResource(const QUrl& url) : Resource(url) {} + GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : Resource(url) {} virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); } + virtual void deleter() override; + protected: + friend class ModelCache; friend class GeometryMappingResource; - virtual bool isCacheable() const override { return _loaded && _isCacheable; } + // Geometries may not hold onto textures while cached - that is for the texture cache + bool hasTextures() const { return !_materials.empty(); } + void setTextures(); + void resetTextures(); + QUrl _textureBaseUrl; + + virtual bool isCacheable() const override { return _loaded && _isCacheable; } bool _isCacheable { true }; }; diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index 7e52052c30..3e14a99f87 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -28,6 +28,6 @@ NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { } QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkShader(url, delayLoad), &Resource::allReferencesCleared); + return QSharedPointer(new NetworkShader(url, delayLoad), &Resource::deleter); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 28f4882b86..3ba36dc2da 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -166,12 +166,11 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path) { return texture; } - QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), - &Resource::allReferencesCleared); + &Resource::deleter); } NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : @@ -339,10 +338,13 @@ void NetworkTexture::setImage(void* voidTexture, int originalWidth, if (gpuTexture) { _width = gpuTexture->getWidth(); _height = gpuTexture->getHeight(); + setBytes(gpuTexture->getStoredSize()); } else { + // FIXME: If !gpuTexture, we failed to load! _width = _height = 0; + qWarning() << "Texture did not load"; } - + finishedLoading(true); emit networkTextureCreated(qWeakPointerCast (_self)); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 9738233c85..b5f5ca7c25 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -117,22 +117,22 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) { } void ResourceCache::addUnusedResource(const QSharedPointer& resource) { - if (resource->getBytesTotal() > _unusedResourcesMaxSize) { - // If it doesn't fit anyway, let's leave whatever is already in the cache. + // If it doesn't fit or its size is unknown, leave the cache alone. + if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) { resource->setCache(nullptr); return; } - reserveUnusedResource(resource->getBytesTotal()); + reserveUnusedResource(resource->getBytes()); resource->setLRUKey(++_lastLRUKey); _unusedResources.insert(resource->getLRUKey(), resource); - _unusedResourcesSize += resource->getBytesTotal(); + _unusedResourcesSize += resource->getBytes(); } void ResourceCache::removeUnusedResource(const QSharedPointer& resource) { if (_unusedResources.contains(resource->getLRUKey())) { _unusedResources.remove(resource->getLRUKey()); - _unusedResourcesSize -= resource->getBytesTotal(); + _unusedResourcesSize -= resource->getBytes(); } } @@ -142,7 +142,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { // unload the oldest resource QMap >::iterator it = _unusedResources.begin(); - _unusedResourcesSize -= it.value()->getBytesTotal(); + _unusedResourcesSize -= it.value()->getBytes(); it.value()->setCache(nullptr); _unusedResources.erase(it); } @@ -399,7 +399,7 @@ void Resource::makeRequest() { connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); - _bytesReceived = _bytesTotal = 0; + _bytesReceived = _bytesTotal = _bytes = 0; _request->send(); } @@ -412,6 +412,8 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota void Resource::handleReplyFinished() { Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished"); + _bytes = _bytesTotal; + if (!_request || _request != sender()) { // This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted. qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current" diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 7f4d86393b..f674c96a1e 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -181,6 +181,9 @@ public: /// For loading resources, returns the number of total bytes (<= zero if unknown). qint64 getBytesTotal() const { return _bytesTotal; } + /// For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly set). + qint64 getBytes() const { return _bytes; } + /// For loading resources, returns the load progress. float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; } @@ -191,7 +194,7 @@ public: void setCache(ResourceCache* cache) { _cache = cache; } - Q_INVOKABLE void allReferencesCleared(); + virtual void deleter() { allReferencesCleared(); } const QUrl& getURL() const { return _url; } @@ -222,10 +225,15 @@ protected: /// This should be overridden by subclasses that need to process the data once it is downloaded. virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); } + /// Called when the download is finished and processed, sets the number of actual bytes. + void setBytes(qint64 bytes) { _bytes = bytes; } + /// Called when the download is finished and processed. /// This should be called by subclasses that override downloadFinished to mark the end of processing. Q_INVOKABLE void finishedLoading(bool success); + Q_INVOKABLE void allReferencesCleared(); + QUrl _url; QUrl _activeUrl; bool _startedLoading = false; @@ -253,6 +261,7 @@ private: QTimer* _replyTimer = nullptr; qint64 _bytesReceived = 0; qint64 _bytesTotal = 0; + qint64 _bytes = 0; int _attempts = 0; }; 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/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 37c15b0ca4..145ecfc7b9 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -36,6 +36,6 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { } QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared); + return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::deleter); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0bd2687169..3a5928f3d1 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; @@ -546,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, @@ -594,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(); @@ -791,6 +769,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 +792,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..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, @@ -129,6 +132,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 +391,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; 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..95ca4ab4f8 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); @@ -848,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 @@ -865,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; } 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 + "/"); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index d18ada1f5a..66667a0b2a 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -122,13 +122,18 @@ void QmlWindowClass::initQml(QVariantMap properties) { 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); + connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); }); } Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); } +void QmlWindowClass::qmlToScript(const QVariant& message) { + QJSValue js = qvariant_cast(message); + emit fromQml(js.toVariant()); +} + void QmlWindowClass::sendToQml(const QVariant& message) { // Forward messages received from the script on to QML QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 242f9b0dd4..e1865b133a 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -63,6 +63,7 @@ signals: protected slots: void hasClosed(); + void qmlToScript(const QVariant& message); protected: static QVariantMap parseArguments(QScriptContext* context); 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..a53643ba21 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -6,7 +6,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusDisplayPlugin.h" + +// Odd ordering of header is required to avoid 'macro redinition warnings' +#include + +#include + #include + #include "OculusHelpers.h" const QString OculusDisplayPlugin::NAME("Oculus Rift"); @@ -86,3 +93,26 @@ void OculusDisplayPlugin::hmdPresent() { } } } + +bool OculusDisplayPlugin::isHmdMounted() const { + ovrSessionStatus status; + return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) && + (ovrFalse != status.HmdMounted)); +} + +QString OculusDisplayPlugin::getPreferredAudioInDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) { + return QString(); + } + return AudioClient::friendlyNameForAudioDevice(buffer); +} + +QString OculusDisplayPlugin::getPreferredAudioOutDevice() const { + WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE]; + if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) { + return QString(); + } + return AudioClient::friendlyNameForAudioDevice(buffer); +} + diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index e7d7791e7f..d6cd6f6f3d 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -12,20 +12,18 @@ 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 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;