From 7e7cb086d3e46275865c47c6f91811947c65106e Mon Sep 17 00:00:00 2001 From: "Kearwood \"Kip\" Gilbert" Date: Tue, 10 Oct 2017 14:42:37 -0700 Subject: [PATCH] Bug 1407423 - Ensure that any time we have loaded the Oculus runtime libary, we are polling ShouldQuit,r=daoshengmu - Ensure ovr_GetSessionStatus is polled even when a VR presentation is not active. - When we fail to initialize an Oculus Session or detect VR hardware, immediately unload the Oculus Library as we can't poll for ShouldQuit without a valid Oculus session. - When we poll ovr_GetSessionStatus, we are now updating the mounted state in VRDisplayInfo::mIsMounted. - Added prefs to control enumeration throttling and timeout to release VR hardware when inactive. - Some refactoring to make frame loop more understandable and less brittle. - When throttling enumeration, we ensure that all other VR apis also throttle enumeration so that they don't pick up the same device during throttling. - Some long functions in VRManager have been broken up and had their inner-workings documented in more detail. MozReview-Commit-ID: CEYwwQ9mYd0 --HG-- extra : rebase_source : b82cd9f200e6311528d4bed469d1b8044e9fc7f2 extra : amend_source : e74f56f5ec95641fd478deb71d6c7ba5d2cba7b1 --- gfx/thebes/gfxPrefs.h | 3 + gfx/vr/VRDisplayHost.h | 2 +- gfx/vr/VRManager.cpp | 198 +++++++++++++++++++++++------------- gfx/vr/VRManager.h | 8 +- gfx/vr/gfxVR.cpp | 53 ++++++++++ gfx/vr/gfxVR.h | 5 +- gfx/vr/gfxVROSVR.cpp | 34 ++++++- gfx/vr/gfxVROSVR.h | 5 +- gfx/vr/gfxVROculus.cpp | 148 +++++++++++++++++++-------- gfx/vr/gfxVROculus.h | 21 ++-- gfx/vr/gfxVROpenVR.cpp | 82 ++++++++++----- gfx/vr/gfxVROpenVR.h | 9 +- gfx/vr/gfxVRPuppet.cpp | 45 ++++++-- gfx/vr/gfxVRPuppet.h | 7 +- modules/libpref/init/all.js | 14 +++ 15 files changed, 467 insertions(+), 167 deletions(-) diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 793dd88a993e..21df904ca774 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -367,6 +367,9 @@ private: DECL_GFX_PREF(Live, "dom.vr.oculus.quit.timeout", VROculusQuitTimeout, int32_t, 30000); DECL_GFX_PREF(Once, "dom.vr.openvr.enabled", VROpenVREnabled, bool, false); DECL_GFX_PREF(Once, "dom.vr.osvr.enabled", VROSVREnabled, bool, false); + DECL_GFX_PREF(Live, "dom.vr.controller.enumerate.interval", VRControllerEnumerateInterval, int32_t, 1000); + DECL_GFX_PREF(Live, "dom.vr.display.enumerate.interval", VRDisplayEnumerateInterval, int32_t, 5000); + DECL_GFX_PREF(Live, "dom.vr.inactive.timeout", VRInactiveTimeout, int32_t, 5000); DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled", VRPosePredictionEnabled, bool, true); DECL_GFX_PREF(Live, "dom.vr.require-gesture", VRRequireGesture, bool, true); DECL_GFX_PREF(Live, "dom.vr.puppet.enabled", VRPuppetEnabled, bool, false); diff --git a/gfx/vr/VRDisplayHost.h b/gfx/vr/VRDisplayHost.h index 509c761c3140..63ff99c73f84 100644 --- a/gfx/vr/VRDisplayHost.h +++ b/gfx/vr/VRDisplayHost.h @@ -41,7 +41,7 @@ public: virtual void ZeroSensor() = 0; virtual void StartPresentation() = 0; virtual void StopPresentation() = 0; - virtual void NotifyVSync(); + void NotifyVSync(); void StartFrame(); void SubmitFrame(VRLayerParent* aLayer, diff --git a/gfx/vr/VRManager.cpp b/gfx/vr/VRManager.cpp index 123f65ce66d0..5245a8b06ea3 100644 --- a/gfx/vr/VRManager.cpp +++ b/gfx/vr/VRManager.cpp @@ -56,6 +56,8 @@ VRManager::ManagerInit() VRManager::VRManager() : mInitialized(false) , mVRTestSystemCreated(false) + , mVRDisplaysRequested(false) + , mVRControllersRequested(false) { MOZ_COUNT_CTOR(VRManager); MOZ_ASSERT(sVRManagerSingleton == nullptr); @@ -174,12 +176,8 @@ VRManager::RemoveVRManagerParent(VRManagerParent* aVRManagerParent) } void -VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) +VRManager::UpdateRequestedDevices() { - MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread()); - const double kVRDisplayRefreshMaxDuration = 5000; // milliseconds - const double kVRDisplayInactiveMaxDuration = 30000; // milliseconds - bool bHaveEventListener = false; bool bHaveControllerListener = false; @@ -189,61 +187,55 @@ VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) bHaveControllerListener |= vmp->HaveControllerListener(); } - // VRDisplayHost::NotifyVSync may modify mVRDisplays, so we iterate - // through a local copy here. - nsTArray> displays; - for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) { - displays.AppendElement(iter.UserData()); - } - for (const auto& display: displays) { - display->NotifyVSync(); + mVRDisplaysRequested = bHaveEventListener; + // We only currently allow controllers to be used when + // also activating a VR display + mVRControllersRequested = mVRDisplaysRequested && bHaveControllerListener; +} + +/** + * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz). + * This must be called even when no WebVR site is active. + * If we don't have a 2d display attached to the system, we can call this + * at the VR display's native refresh rate. + **/ +void +VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) +{ + MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread()); + UpdateRequestedDevices(); + + for (const auto& manager : mManagers) { + manager->NotifyVSync(); } - if (bHaveEventListener) { - // If content has set an EventHandler to be notified of VR display events - // we must continually refresh the VR display enumeration to check - // for events that we must fire such as Window.onvrdisplayconnect - // Note that enumeration itself may activate display hardware, such - // as Oculus, so we only do this when we know we are displaying content - // that is looking for VR displays. - if (mLastRefreshTime.IsNull()) { - // This is the first vsync, must refresh VR displays - RefreshVRDisplays(); - if (bHaveControllerListener) { - RefreshVRControllers(); - } - mLastRefreshTime = TimeStamp::Now(); - } else { - // We don't have to do this every frame, so check if we - // have refreshed recently. - TimeDuration duration = TimeStamp::Now() - mLastRefreshTime; - if (duration.ToMilliseconds() > kVRDisplayRefreshMaxDuration) { - RefreshVRDisplays(); - if (bHaveControllerListener) { - RefreshVRControllers(); - } - mLastRefreshTime = TimeStamp::Now(); - } - } + // We must continually refresh the VR display enumeration to check + // for events that we must fire such as Window.onvrdisplayconnect + // Note that enumeration itself may activate display hardware, such + // as Oculus, so we only do this when we know we are displaying content + // that is looking for VR displays. + RefreshVRDisplays(); - if (bHaveControllerListener) { - for (const auto& manager: mManagers) { - if (!manager->GetIsPresenting()) { - manager->HandleInput(); - } - } - } - } + // Update state and enumeration of VR controllers + RefreshVRControllers(); + CheckForInactiveTimeout(); +} + +void +VRManager::CheckForInactiveTimeout() +{ // Shut down the VR devices when not in use - if (bHaveEventListener || bHaveControllerListener) { + if (mVRDisplaysRequested || mVRControllersRequested) { // We are using a VR device, keep it alive mLastActiveTime = TimeStamp::Now(); - } else if (mLastActiveTime.IsNull()) { + } + else if (mLastActiveTime.IsNull()) { Shutdown(); - } else { + } + else { TimeDuration duration = TimeStamp::Now() - mLastActiveTime; - if (duration.ToMilliseconds() > kVRDisplayInactiveMaxDuration) { + if (duration.ToMilliseconds() > gfxPrefs::VRInactiveTimeout()) { Shutdown(); } } @@ -267,26 +259,80 @@ VRManager::NotifyVRVsync(const uint32_t& aDisplayID) RefreshVRDisplays(); } +void +VRManager::EnumerateVRDisplays() +{ + /** + * Throttle the rate of enumeration to the interval set in + * VRDisplayEnumerateInterval + */ + if (!mLastDisplayEnumerationTime.IsNull()) { + TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime; + if (duration.ToMilliseconds() < gfxPrefs::VRDisplayEnumerateInterval()) { + return; + } + } + + /** + * Any VRSystemManager instance may request that no enumeration + * should occur, including enumeration from other VRSystemManager + * instances. + */ + for (const auto& manager : mManagers) { + if (manager->ShouldInhibitEnumeration()) { + return; + } + } + + /** + * If we get this far, don't try again until + * the VRDisplayEnumerateInterval elapses + */ + mLastDisplayEnumerationTime = TimeStamp::Now(); + + /** + * VRSystemManagers are inserted into mManagers in + * a strict order of priority. The managers for the + * most device-specialized API's will have a chance + * to enumerate devices before the more generic + * device-agnostic APIs. + */ + for (const auto& manager : mManagers) { + manager->Enumerate(); + /** + * After a VRSystemManager::Enumerate is called, it may request + * that further enumeration should stop. This can be used to prevent + * erraneous redundant enumeration of the same HMD by multiple managers. + * XXX - Perhaps there will be a better way to detect duplicate displays + * in the future. + */ + if (manager->ShouldInhibitEnumeration()) { + return; + } + } +} + void VRManager::RefreshVRDisplays(bool aMustDispatch) { - nsTArray > displays; + /** + * If we aren't viewing WebVR content, don't enumerate + * new hardware, as it will cause some devices to power on + * or interrupt other VR activities. + */ + if (mVRDisplaysRequested || aMustDispatch) { + EnumerateVRDisplays(); + } - /** We don't wish to enumerate the same display from multiple managers, - * so stop as soon as we get a display. - * It is still possible to get multiple displays from a single manager, - * but do not wish to mix-and-match for risk of reporting a duplicate. - * - * XXX - Perhaps there will be a better way to detect duplicate displays - * in the future. + /** + * VRSystemManager::GetHMDs will not activate new hardware + * or result in interruption of other VR activities. + * We can call it even when suppressing enumeration to get + * the already-enumerated displays. */ - for (uint32_t i = 0; i < mManagers.Length() && displays.Length() == 0; ++i) { - if (mManagers[i]->GetHMDs(displays)) { - // GetHMDs returns true to indicate that no further enumeration from - // other managers should be performed. This prevents erraneous - // redundant enumeration of the same HMD by multiple managers. - break; - } + nsTArray > displays; + for (const auto& manager: mManagers) { + manager->GetHMDs(displays); } bool displayInfoChanged = false; @@ -383,10 +429,10 @@ VRManager::GetVRControllerInfo(nsTArray& aControllerInfo) void VRManager::RefreshVRControllers() { - nsTArray> controllers; - ScanForControllers(); + nsTArray> controllers; + for (uint32_t i = 0; i < mManagers.Length() && controllers.Length() == 0; ++i) { mManagers[i]->GetControllers(controllers); @@ -419,9 +465,25 @@ VRManager::RefreshVRControllers() void VRManager::ScanForControllers() { + // We don't have to do this every frame, so check if we + // have enumerated recently + if (!mLastControllerEnumerationTime.IsNull()) { + TimeDuration duration = TimeStamp::Now() - mLastControllerEnumerationTime; + if (duration.ToMilliseconds() < gfxPrefs::VRControllerEnumerateInterval()) { + return; + } + } + + // Only enumerate controllers once we need them + if (!mVRControllersRequested) { + return; + } + for (uint32_t i = 0; i < mManagers.Length(); ++i) { mManagers[i]->ScanForControllers(); } + + mLastControllerEnumerationTime = TimeStamp::Now(); } void diff --git a/gfx/vr/VRManager.h b/gfx/vr/VRManager.h index 7c79922ed442..e968094f31cd 100644 --- a/gfx/vr/VRManager.h +++ b/gfx/vr/VRManager.h @@ -64,6 +64,9 @@ private: void Shutdown(); void DispatchVRDisplayInfoUpdate(); + void UpdateRequestedDevices(); + void EnumerateVRDisplays(); + void CheckForInactiveTimeout(); typedef nsTHashtable> VRManagerParentSet; VRManagerParentSet mVRManagerParents; @@ -79,9 +82,12 @@ private: Atomic mInitialized; - TimeStamp mLastRefreshTime; + TimeStamp mLastControllerEnumerationTime; + TimeStamp mLastDisplayEnumerationTime; TimeStamp mLastActiveTime; bool mVRTestSystemCreated; + bool mVRDisplaysRequested; + bool mVRControllersRequested; }; } // namespace gfx diff --git a/gfx/vr/gfxVR.cpp b/gfx/vr/gfxVR.cpp index 849adc10ff62..da6054b591c8 100644 --- a/gfx/vr/gfxVR.cpp +++ b/gfx/vr/gfxVR.cpp @@ -9,6 +9,7 @@ #include "gfxVR.h" #include "mozilla/dom/GamepadEventTypes.h" #include "mozilla/dom/GamepadBinding.h" +#include "VRDisplayHost.h" #ifndef M_PI # define M_PI 3.14159265358979323846 @@ -32,6 +33,58 @@ VRSystemManager::AllocateControllerID() return ++sControllerBase; } +/** + * VRSystemManager::NotifyVsync must be called even when a WebVR site is + * not active, in order to poll for respond to VR Platform API requests. + * This should be called very often, ideally once per frame. + * VRSystemManager::Refresh will not activate VR hardware or + * initialize VR runtimes that have not already been activated. + */ +void +VRSystemManager::NotifyVSync() +{ + // VRDisplayHost::NotifyVSync may modify mVRDisplays, so we iterate + // through a local copy here. + nsTArray> displays; + GetHMDs(displays); + for (const auto& display : displays) { + display->NotifyVSync(); + } + + // Ensure that the controller state is updated at least + // on every 2d display VSync when not in a VR presentation. + if (!GetIsPresenting()) { + HandleInput(); + } +} + +/** + * VRSystemManager::GetHMDs must not be called unless + * VRSystemManager::ShouldInhibitEnumeration is called + * on all VRSystemManager instances and they all return + * false. + * + * This is used to ensure that VR devices that can be + * enumerated by multiple API's are only enumerated by + * one API. + * + * GetHMDs is called for the most specific API + * (ie. Oculus SDK) first before calling GetHMDs on + * more generic api's (ie. OpenVR) to ensure that a device + * is accessed using the API most optimized for it. + * + * ShouldInhibitEnumeration may also be used to prevent + * devices from jumping to other API's when they are + * intentionally ignored, such as when responding to + * requests by the VR platform to unload the libraries + * for runtime software updates. + */ +bool +VRSystemManager::ShouldInhibitEnumeration() +{ + return false; +} + Matrix4x4 VRFieldOfView::ConstructProjectionMatrix(float zNear, float zFar, bool rightHanded) const diff --git a/gfx/vr/gfxVR.h b/gfx/vr/gfxVR.h index 7f71836ba0da..48da76bc1461 100644 --- a/gfx/vr/gfxVR.h +++ b/gfx/vr/gfxVR.h @@ -347,7 +347,10 @@ public: virtual void Destroy() = 0; virtual void Shutdown() = 0; - virtual bool GetHMDs(nsTArray>& aHMDResult) = 0; + virtual void Enumerate() = 0; + virtual void NotifyVSync(); + virtual bool ShouldInhibitEnumeration(); + virtual void GetHMDs(nsTArray>& aHMDResult) = 0; virtual bool GetIsPresenting() = 0; virtual void HandleInput() = 0; virtual void GetControllers(nsTArray>& aControllerResult) = 0; diff --git a/gfx/vr/gfxVROSVR.cpp b/gfx/vr/gfxVROSVR.cpp index c76a302c736b..053fc59f7480 100644 --- a/gfx/vr/gfxVROSVR.cpp +++ b/gfx/vr/gfxVROSVR.cpp @@ -550,25 +550,51 @@ VRSystemManagerOSVR::Shutdown() osvr_ClientShutdown(m_ctx); } -bool -VRSystemManagerOSVR::GetHMDs(nsTArray>& aHMDResult) +void +VRSystemManagerOSVR::NotifyVSync() +{ + VRSystemManager::NotifyVSync(); + + // TODO - Check for device disconnection or other OSVR events +} + +void +VRSystemManagerOSVR::Enumerate() { // make sure context, interface and display are initialized CheckOSVRStatus(); if (!Init()) { - return false; + return; } mHMDInfo = new VRDisplayOSVR(&m_ctx, &m_iface, &m_display); +} +bool +VRSystemManagerOSVR::ShouldInhibitEnumeration() +{ + if (VRSystemManager::ShouldInhibitEnumeration()) { + return true; + } if (mHMDInfo) { - aHMDResult.AppendElement(mHMDInfo); + // When we find an a VR device, don't + // allow any further enumeration as it + // may get picked up redundantly by other + // API's. return true; } return false; } +void +VRSystemManagerOSVR::GetHMDs(nsTArray>& aHMDResult) +{ + if (mHMDInfo) { + aHMDResult.AppendElement(mHMDInfo); + } +} + bool VRSystemManagerOSVR::GetIsPresenting() { diff --git a/gfx/vr/gfxVROSVR.h b/gfx/vr/gfxVROSVR.h index 3b70622aa39d..a79d7e659733 100644 --- a/gfx/vr/gfxVROSVR.h +++ b/gfx/vr/gfxVROSVR.h @@ -80,7 +80,10 @@ public: static already_AddRefed Create(); virtual void Destroy() override; virtual void Shutdown() override; - virtual bool GetHMDs(nsTArray>& aHMDResult) override; + virtual void NotifyVSync() override; + virtual void Enumerate() override; + virtual bool ShouldInhibitEnumeration() override; + virtual void GetHMDs(nsTArray>& aHMDResult) override; virtual bool GetIsPresenting() override; virtual void HandleInput() override; virtual void GetControllers(nsTArray>& diff --git a/gfx/vr/gfxVROculus.cpp b/gfx/vr/gfxVROculus.cpp index c9cdfb6563a3..6f266d834bda 100644 --- a/gfx/vr/gfxVROculus.cpp +++ b/gfx/vr/gfxVROculus.cpp @@ -204,8 +204,11 @@ VROculusSession::VROculusSession() , mSession(nullptr) , mInitFlags((ovrInitFlags)0) , mTextureSet(nullptr) - , mPresenting(false) + , mRequestPresentation(false) + , mRequestTracking(false) , mDrawBlack(false) + , mIsConnected(false) + , mIsMounted(false) { } @@ -219,26 +222,47 @@ VROculusSession::Get() bool VROculusSession::IsTrackingReady() const { - return mSession != nullptr; + // We should return true only if the HMD is connected and we + // are ready for tracking + MOZ_ASSERT(!mIsConnected || mSession); + return mIsConnected; } bool -VROculusSession::IsRenderReady() const +VROculusSession::IsPresentationReady() const { return !mRenderTargets.IsEmpty(); } +bool +VROculusSession::IsMounted() const +{ + return mIsMounted; +} + void VROculusSession::StopTracking() { - Uninitialize(true); + if (mRequestTracking) { + mRequestTracking = false; + Refresh(); + } +} + +void +VROculusSession::StartTracking() +{ + if (!mRequestTracking) { + mRequestTracking = true; + Refresh(); + } } void VROculusSession::StartPresentation(const IntSize& aSize) { - if (!mPresenting) { - mPresenting = true; + if (!mRequestPresentation) { + mRequestPresentation = true; mTelemetry.Clear(); mTelemetry.mPresentationStart = TimeStamp::Now(); @@ -258,9 +282,9 @@ VROculusSession::StartPresentation(const IntSize& aSize) void VROculusSession::StopPresentation() { - if (mPresenting) { + if (mRequestPresentation) { mLastPresentationEnd = TimeStamp::Now(); - mPresenting = false; + mRequestPresentation = false; const TimeDuration duration = mLastPresentationEnd - mTelemetry.mPresentationStart; Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 1); @@ -315,6 +339,8 @@ VROculusSession::StopSession() { if (mSession) { ovr_Destroy(mSession); + mIsConnected = false; + mIsMounted = false; mSession = nullptr; } } @@ -337,9 +363,14 @@ VROculusSession::Refresh(bool aForceRefresh) return; } + if (!mRequestTracking) { + Uninitialize(true); + return; + } + ovrInitFlags flags = (ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering); bool bInvisible = true; - if (mPresenting) { + if (mRequestPresentation) { bInvisible = false; } else if (!mLastPresentationEnd.IsNull()) { TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd; @@ -384,15 +415,25 @@ VROculusSession::Refresh(bool aForceRefresh) Uninitialize(false); } - Initialize(flags); + if(!Initialize(flags)) { + // If we fail to initialize, ensure the Oculus libraries + // are unloaded, as we can't poll for ovrSessionStatus::ShouldQuit + // without an active ovrSession. + Uninitialize(true); + } if (mSession) { ovrSessionStatus status; if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) { + mIsConnected = status.HmdPresent; + mIsMounted = status.HmdMounted; if (status.ShouldQuit) { mLastShouldQuit = TimeStamp::Now(); Uninitialize(true); } + } else { + mIsConnected = false; + mIsMounted = false; } } } @@ -437,7 +478,7 @@ VROculusSession::Initialize(ovrInitFlags aFlags) bool VROculusSession::StartRendering() { - if (!mPresenting) { + if (!mRequestPresentation) { // Nothing to do if we aren't presenting return true; } @@ -973,7 +1014,7 @@ VRDisplayOculus::StartPresentation() return; } mSession->StartPresentation(IntSize(mDisplayInfo.mEyeResolution.width * 2, mDisplayInfo.mEyeResolution.height)); - if (!mSession->IsRenderReady()) { + if (!mSession->IsPresentationReady()) { return; } @@ -1103,7 +1144,7 @@ VRDisplayOculus::SubmitFrame(ID3D11Texture2D* aSource, return false; } - if (!mSession->IsRenderReady()) { + if (!mSession->IsPresentationReady()) { return false; } /** @@ -1248,18 +1289,10 @@ VRDisplayOculus::SubmitFrame(ID3D11Texture2D* aSource, } void -VRDisplayOculus::NotifyVSync() +VRDisplayOculus::Refresh() { - mSession->Refresh(); - if (mSession->IsTrackingReady()) { - ovrSessionStatus sessionStatus; - ovrResult ovr = ovr_GetSessionStatus(mSession->Get(), &sessionStatus); - mDisplayInfo.mIsConnected = (ovr == ovrSuccess && sessionStatus.HmdPresent); - } else { - mDisplayInfo.mIsConnected = false; - } - - VRDisplayHost::NotifyVSync(); + mDisplayInfo.mIsConnected = mSession->IsTrackingReady(); + mDisplayInfo.mIsMounted = mSession->IsMounted(); } VRControllerOculus::VRControllerOculus(dom::GamepadHand aHand, uint32_t aDisplayID) @@ -1517,37 +1550,66 @@ VRSystemManagerOculus::Shutdown() mDisplay = nullptr; } +void +VRSystemManagerOculus::NotifyVSync() +{ + VRSystemManager::NotifyVSync(); + if (!mSession) { + return; + } + mSession->Refresh(); + if (mDisplay) { + mDisplay->Refresh(); + } + // Detect disconnection + if (!mSession->IsTrackingReady()) { + // No HMD connected + mDisplay = nullptr; + } +} + bool -VRSystemManagerOculus::GetHMDs(nsTArray>& aHMDResult) +VRSystemManagerOculus::ShouldInhibitEnumeration() +{ + if (VRSystemManager::ShouldInhibitEnumeration()) { + return true; + } + if (mDisplay) { + // When we find an Oculus VR device, don't + // allow any further enumeration as it + // may get picked up redundantly by other + // API's such as OpenVR. + return true; + } + if (mSession && mSession->IsQuitTimeoutActive()) { + // When we are responding to ShouldQuit, we return true here + // to prevent further enumeration by other VRSystemManager's such as + // VRSystemManagerOpenVR which would also enumerate the connected Oculus + // HMD, resulting in interference with the Oculus runtime software updates. + return true; + } + return false; +} + +void +VRSystemManagerOculus::Enumerate() { if (!mSession) { mSession = new VROculusSession(); } - mSession->Refresh(); - if (mSession->IsQuitTimeoutActive()) { - // We have responded to a ShouldQuit flag set by the Oculus runtime - // and are waiting for a timeout duration to elapse before allowing - // re-initialization of the Oculus OVR lib. We return true in this case - // to prevent further enumeration by other VRSystemManager's such as - // VRSystemManagerOpenVR which would also enumerate the connected Oculus - // HMD, resulting in interference with the Oculus runtime software updates. - mDisplay = nullptr; - return true; - } - - if (!mSession->IsTrackingReady()) { - // No HMD connected. - mDisplay = nullptr; - } else if (mDisplay == nullptr) { + mSession->StartTracking(); + if (mDisplay == nullptr && mSession->IsTrackingReady()) { // HMD Detected mDisplay = new VRDisplayOculus(mSession); } +} +void +VRSystemManagerOculus::GetHMDs(nsTArray>& aHMDResult) +{ if (mDisplay) { aHMDResult.AppendElement(mDisplay); - return true; } - return false; } bool diff --git a/gfx/vr/gfxVROculus.h b/gfx/vr/gfxVROculus.h index 8e3f0812a721..e77e054ba359 100644 --- a/gfx/vr/gfxVROculus.h +++ b/gfx/vr/gfxVROculus.h @@ -42,12 +42,14 @@ class VROculusSession public: VROculusSession(); void Refresh(bool aForceRefresh = false); + void StartTracking(); + void StopTracking(); bool IsTrackingReady() const; - bool IsRenderReady() const; - ovrSession Get(); void StartPresentation(const IntSize& aSize); void StopPresentation(); - void StopTracking(); + bool IsPresentationReady() const; + bool IsMounted() const; + ovrSession Get(); bool IsQuitTimeoutActive(); already_AddRefed GetNextRenderTarget(); ovrTextureSwapChain GetSwapChain(); @@ -65,8 +67,12 @@ private: // The timestamp of the last ending presentation TimeStamp mLastPresentationEnd; VRTelemetry mTelemetry; - bool mPresenting; + bool mRequestPresentation; + bool mRequestTracking; + bool mTracking; bool mDrawBlack; + bool mIsConnected; + bool mIsMounted; ~VROculusSession(); void Uninitialize(bool aUnloadLib); @@ -84,7 +90,6 @@ private: class VRDisplayOculus : public VRDisplayHost { public: - virtual void NotifyVSync() override; void ZeroSensor() override; protected: @@ -100,6 +105,7 @@ protected: public: explicit VRDisplayOculus(VROculusSession* aSession); void Destroy(); + void Refresh(); protected: virtual ~VRDisplayOculus(); @@ -176,7 +182,10 @@ public: static already_AddRefed Create(); virtual void Destroy() override; virtual void Shutdown() override; - virtual bool GetHMDs(nsTArray >& aHMDResult) override; + virtual void Enumerate() override; + virtual void NotifyVSync() override; + virtual bool ShouldInhibitEnumeration() override; + virtual void GetHMDs(nsTArray >& aHMDResult) override; virtual bool GetIsPresenting() override; virtual void HandleInput() override; virtual void GetControllers(nsTArray>& diff --git a/gfx/vr/gfxVROpenVR.cpp b/gfx/vr/gfxVROpenVR.cpp index c7a43df1050b..009504f93498 100644 --- a/gfx/vr/gfxVROpenVR.cpp +++ b/gfx/vr/gfxVROpenVR.cpp @@ -202,8 +202,10 @@ VRDisplayOpenVR::GetIsHmdPresent() } void -VRDisplayOpenVR::PollEvents() +VRDisplayOpenVR::Refresh() { + mIsHmdPresent = ::vr::VR_IsHmdPresent(); + ::vr::VREvent_t event; while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) { switch (event.eventType) { @@ -244,8 +246,6 @@ VRDisplayOpenVR::PollEvents() VRHMDSensorState VRDisplayOpenVR::GetSensorState() { - PollEvents(); - const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1; ::vr::TrackedDevicePose_t poses[posesSize]; // Note: We *must* call WaitGetPoses in order for any rendering to happen at all. @@ -422,17 +422,6 @@ VRDisplayOpenVR::SubmitFrame(MacIOSurface* aMacIOSurface, #endif -void -VRDisplayOpenVR::NotifyVSync() -{ - // We check if HMD is available once per frame. - mIsHmdPresent = ::vr::VR_IsHmdPresent(); - // Make sure we respond to OpenVR events even when not presenting - PollEvents(); - - VRDisplayHost::NotifyVSync(); -} - VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aDisplayID, uint32_t aNumButtons, uint32_t aNumTriggers, uint32_t aNumAxes, const nsCString& aId) @@ -633,50 +622,87 @@ VRSystemManagerOpenVR::Shutdown() mVRSystem = nullptr; } -bool -VRSystemManagerOpenVR::GetHMDs(nsTArray>& aHMDResult) +void +VRSystemManagerOpenVR::NotifyVSync() { - if (!::vr::VR_IsHmdPresent() || - (mOpenVRHMD && !mOpenVRHMD->GetIsHmdPresent())) { - // OpenVR runtime could be quit accidentally, - // and we make it re-initialize. - mOpenVRHMD = nullptr; - mVRSystem = nullptr; - } else if (mOpenVRHMD == nullptr) { + VRSystemManager::NotifyVSync(); + + // Avoid doing anything unless we have already + // successfully enumerated and loaded the OpenVR + // runtime. + if (mVRSystem == nullptr) { + return; + } + + if (mOpenVRHMD) { + mOpenVRHMD->Refresh(); + if (!mOpenVRHMD->GetIsHmdPresent()) { + // OpenVR runtime could be quit accidentally + // or a device could be disconnected. + // We free up resources and must re-initialize + // if a device is detected again later. + mOpenVRHMD = nullptr; + mVRSystem = nullptr; + } + } +} + +void +VRSystemManagerOpenVR::Enumerate() +{ + if (mOpenVRHMD == nullptr && ::vr::VR_IsHmdPresent()) { ::vr::HmdError err; ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene); if (err) { - return false; + return; } ::vr::IVRSystem *system = (::vr::IVRSystem *)::vr::VR_GetGenericInterface(::vr::IVRSystem_Version, &err); if (err || !system) { ::vr::VR_Shutdown(); - return false; + return; } ::vr::IVRChaperone *chaperone = (::vr::IVRChaperone *)::vr::VR_GetGenericInterface(::vr::IVRChaperone_Version, &err); if (err || !chaperone) { ::vr::VR_Shutdown(); - return false; + return; } ::vr::IVRCompositor *compositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(::vr::IVRCompositor_Version, &err); if (err || !compositor) { ::vr::VR_Shutdown(); - return false; + return; } mVRSystem = system; mOpenVRHMD = new VRDisplayOpenVR(system, chaperone, compositor); } +} +bool +VRSystemManagerOpenVR::ShouldInhibitEnumeration() +{ + if (VRSystemManager::ShouldInhibitEnumeration()) { + return true; + } if (mOpenVRHMD) { - aHMDResult.AppendElement(mOpenVRHMD); + // When we find an a VR device, don't + // allow any further enumeration as it + // may get picked up redundantly by other + // API's. return true; } return false; } +void +VRSystemManagerOpenVR::GetHMDs(nsTArray>& aHMDResult) +{ + if (mOpenVRHMD) { + aHMDResult.AppendElement(mOpenVRHMD); + } +} + bool VRSystemManagerOpenVR::GetIsPresenting() { diff --git a/gfx/vr/gfxVROpenVR.h b/gfx/vr/gfxVROpenVR.h index f632c4266852..2bb310a697ed 100644 --- a/gfx/vr/gfxVROpenVR.h +++ b/gfx/vr/gfxVROpenVR.h @@ -30,7 +30,6 @@ namespace impl { class VRDisplayOpenVR : public VRDisplayHost { public: - virtual void NotifyVSync() override; void ZeroSensor() override; bool GetIsHmdPresent(); @@ -54,7 +53,7 @@ public: explicit VRDisplayOpenVR(::vr::IVRSystem *aVRSystem, ::vr::IVRChaperone *aVRChaperone, ::vr::IVRCompositor *aVRCompositor); - + void Refresh(); protected: virtual ~VRDisplayOpenVR(); void Destroy(); @@ -70,7 +69,6 @@ protected: void UpdateStageParameters(); void UpdateEyeParameters(gfx::Matrix4x4* aHeadToEyeTransforms = nullptr); - void PollEvents(); bool SubmitFrame(void* aTextureHandle, ::vr::ETextureType aTextureType, const IntSize& aSize, @@ -127,7 +125,10 @@ public: virtual void Destroy() override; virtual void Shutdown() override; - virtual bool GetHMDs(nsTArray >& aHMDResult) override; + virtual void NotifyVSync() override; + virtual void Enumerate() override; + virtual bool ShouldInhibitEnumeration() override; + virtual void GetHMDs(nsTArray>& aHMDResult) override; virtual bool GetIsPresenting() override; virtual void HandleInput() override; virtual void GetControllers(nsTArray>& diff --git a/gfx/vr/gfxVRPuppet.cpp b/gfx/vr/gfxVRPuppet.cpp index 6404f2c0ec65..f94260c3499b 100644 --- a/gfx/vr/gfxVRPuppet.cpp +++ b/gfx/vr/gfxVRPuppet.cpp @@ -566,12 +566,10 @@ VRDisplayPuppet::SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescrip #endif void -VRDisplayPuppet::NotifyVSync() +VRDisplayPuppet::Refresh() { - // We update mIsConneced once per frame. + // We update mIsConneced once per refresh. mDisplayInfo.mIsConnected = true; - - VRDisplayHost::NotifyVSync(); } VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand, uint32_t aDisplayID) @@ -703,14 +701,45 @@ VRSystemManagerPuppet::Shutdown() mPuppetHMD = nullptr; } -bool -VRSystemManagerPuppet::GetHMDs(nsTArray>& aHMDResult) +void +VRSystemManagerPuppet::NotifyVSync() +{ + VRSystemManager::NotifyVSync(); + if (mPuppetHMD) { + mPuppetHMD->Refresh(); + } +} + +void +VRSystemManagerPuppet::Enumerate() { if (mPuppetHMD == nullptr) { mPuppetHMD = new VRDisplayPuppet(); } - aHMDResult.AppendElement(mPuppetHMD); - return true; +} + +bool +VRSystemManagerPuppet::ShouldInhibitEnumeration() +{ + if (VRSystemManager::ShouldInhibitEnumeration()) { + return true; + } + if (mPuppetHMD) { + // When we find an a VR device, don't + // allow any further enumeration as it + // may get picked up redundantly by other + // API's. + return true; + } + return false; +} + +void +VRSystemManagerPuppet::GetHMDs(nsTArray>& aHMDResult) +{ + if (mPuppetHMD) { + aHMDResult.AppendElement(mPuppetHMD); + } } bool diff --git a/gfx/vr/gfxVRPuppet.h b/gfx/vr/gfxVRPuppet.h index 39b4cdc4c610..d47c854ae329 100644 --- a/gfx/vr/gfxVRPuppet.h +++ b/gfx/vr/gfxVRPuppet.h @@ -24,7 +24,6 @@ class VRDisplayPuppet : public VRDisplayHost { public: void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo); - virtual void NotifyVSync() override; void SetSensorState(const VRHMDSensorState& aSensorState); void ZeroSensor() override; @@ -50,6 +49,7 @@ protected: public: explicit VRDisplayPuppet(); + void Refresh(); protected: virtual ~VRDisplayPuppet(); @@ -110,7 +110,9 @@ public: virtual void Destroy() override; virtual void Shutdown() override; - virtual bool GetHMDs(nsTArray>& aHMDResult) override; + virtual void Enumerate() override; + virtual bool ShouldInhibitEnumeration() override; + virtual void GetHMDs(nsTArray>& aHMDResult) override; virtual bool GetIsPresenting() override; virtual void HandleInput() override; virtual void GetControllers(nsTArray>& @@ -123,6 +125,7 @@ public: double aDuration, uint32_t aPromiseID) override; virtual void StopVibrateHaptic(uint32_t aControllerIdx) override; + virtual void NotifyVSync() override; protected: VRSystemManagerPuppet(); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index b86f4dfba2a1..90ee3f9065ff 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5157,6 +5157,20 @@ pref("dom.vr.openvr.enabled", true); // See Bug 1310663 (Linux) pref("dom.vr.openvr.enabled", false); #endif +// Minimum number of milliseconds that the browser will wait before +// attempting to poll again for connected VR controllers. The browser +// will not attempt to poll for VR controllers until it needs to use them. +pref("dom.vr.controller.enumerate.interval", 1000); +// Minimum number of milliseconds that the browser will wait before +// attempting to poll again for connected VR displays. The browser +// will not attempt to poll for VR displays until it needs to use +// them, such as when detecting a WebVR site. +pref("dom.vr.display.enumerate.interval", 5000); +// Minimum number of milliseconds that the VR session will be kept +// alive after the browser and content no longer are using the +// hardware. If a VR multitasking environment, this should be set +// very low or set to 0. +pref("dom.vr.inactive.timeout", 5000); // Pose prediction reduces latency effects by returning future predicted HMD // poses to callers of the WebVR API. This currently only has an effect for // Oculus Rift on SDK 0.8 or greater.