diff --git a/gfx/vr/gfxVROculus.cpp b/gfx/vr/gfxVROculus.cpp index 5c159f25611f..43d81cc72fdf 100644 --- a/gfx/vr/gfxVROculus.cpp +++ b/gfx/vr/gfxVROculus.cpp @@ -29,6 +29,7 @@ #include "gfxVROculus.h" +#include "mozilla/layers/CompositorThread.h" #include "mozilla/dom/GamepadEventTypes.h" #include "mozilla/dom/GamepadBinding.h" @@ -145,7 +146,7 @@ enum class OculusRightControllerButtonType : uint16_t { static const uint32_t kNumOculusButton = static_cast (OculusLeftControllerButtonType:: NumButtonType); -static const uint32_t kNumOculusHaptcs = 0; // TODO: Bug 1305892 +static const uint32_t kNumOculusHaptcs = 1; static bool @@ -886,6 +887,8 @@ VRControllerOculus::VRControllerOculus(dom::GamepadHand aHand) : VRControllerHost(VRDeviceType::Oculus) , mIndexTrigger(0.0f) , mHandTrigger(0.0f) + , mVibrateThread(nullptr) + , mIsVibrateStopped(false) { MOZ_COUNT_CTOR_INHERITED(VRControllerOculus, VRControllerHost); @@ -957,6 +960,144 @@ VRControllerOculus::~VRControllerOculus() MOZ_COUNT_DTOR_INHERITED(VRControllerOculus, VRControllerHost); } +void +VRControllerOculus::UpdateVibrateHaptic(ovrSession aSession, + uint32_t aHapticIndex, + double aIntensity, + double aDuration, + uint64_t aVibrateIndex, + uint32_t aPromiseID) +{ + // UpdateVibrateHaptic() only can be called by mVibrateThread + MOZ_ASSERT(mVibrateThread == NS_GetCurrentThread()); + + // It has been interrupted by loss focus. + if (mIsVibrateStopped) { + VibrateHapticComplete(aSession, aPromiseID, true); + return; + } + // Avoid the previous vibrate event to override the new one. + if (mVibrateIndex != aVibrateIndex) { + VibrateHapticComplete(aSession, aPromiseID, false); + return; + } + + const double duration = (aIntensity == 0) ? 0 : aDuration; + // Vibration amplitude in the [0.0, 1.0] range. + const float amplitude = aIntensity > 1.0 ? 1.0 : aIntensity; + // Vibration is enabled by specifying the frequency. + // Specifying 0.0f will disable the vibration, 0.5f will vibrate at 160Hz, + // and 1.0f will vibrate at 320Hz. + const float frequency = (duration > 0) ? 1.0f : 0.0f; + ovrControllerType hand; + + switch (GetHand()) { + case GamepadHand::Left: + hand = ovrControllerType::ovrControllerType_LTouch; + break; + case GamepadHand::Right: + hand = ovrControllerType::ovrControllerType_RTouch; + break; + default: + MOZ_ASSERT(false); + break; + } + + // Oculus Touch only can get the response from ovr_SetControllerVibration() + // at the presenting mode. + ovrResult result = ovr_SetControllerVibration(aSession, hand, frequency, + (frequency == 0.0f) ? 0.0f : amplitude); + if (result != ovrSuccess) { + printf_stderr("%s hand ovr_SetControllerVibration skipped.\n", + GamepadHandValues::strings[uint32_t(GetHand())].value); + } + + // In Oculus dev doc, it mentions vibration lasts for a maximum of 2.5 seconds + // at ovr_SetControllerVibration(), but we found 2.450 sec is more close to the + // real looping use case. + const double kVibrateRate = 2450.0; + const double remainingTime = (duration > kVibrateRate) + ? (duration - kVibrateRate) : duration; + + if (remainingTime) { + MOZ_ASSERT(mVibrateThread); + + RefPtr runnable = + NewRunnableMethod + (this, &VRControllerOculus::UpdateVibrateHaptic, aSession, + aHapticIndex, aIntensity, (duration > kVibrateRate) ? remainingTime : 0, aVibrateIndex, aPromiseID); + NS_DelayedDispatchToCurrentThread(runnable.forget(), + (duration > kVibrateRate) ? kVibrateRate : remainingTime); + } else { + VibrateHapticComplete(aSession, aPromiseID, true); + } +} + +void +VRControllerOculus::VibrateHapticComplete(ovrSession aSession, uint32_t aPromiseID, + bool aStop) +{ + if (aStop) { + ovrControllerType hand; + + switch (GetHand()) { + case GamepadHand::Left: + hand = ovrControllerType::ovrControllerType_LTouch; + break; + case GamepadHand::Right: + hand = ovrControllerType::ovrControllerType_RTouch; + break; + default: + MOZ_ASSERT(false); + break; + } + + ovrResult result = ovr_SetControllerVibration(aSession, hand, 0.0f, 0.0f); + if (result != ovrSuccess) { + printf_stderr("%s Haptics skipped.\n", + GamepadHandValues::strings[uint32_t(GetHand())].value); + } + } + + VRManager *vm = VRManager::Get(); + MOZ_ASSERT(vm); + + CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod + (vm, &VRManager::NotifyVibrateHapticCompleted, aPromiseID)); +} + +void +VRControllerOculus::VibrateHaptic(ovrSession aSession, + uint32_t aHapticIndex, + double aIntensity, + double aDuration, + uint32_t aPromiseID) +{ + // Spinning up the haptics thread at the first haptics call. + if (!mVibrateThread) { + nsresult rv = NS_NewThread(getter_AddRefs(mVibrateThread)); + MOZ_ASSERT(mVibrateThread); + + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to create async thread."); + } + } + ++mVibrateIndex; + mIsVibrateStopped = false; + + RefPtr runnable = + NewRunnableMethod + (this, &VRControllerOculus::UpdateVibrateHaptic, aSession, + aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromiseID); + mVibrateThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +void +VRControllerOculus::StopVibrateHaptic() +{ + mIsVibrateStopped = true; +} + /*static*/ already_AddRefed VRSystemManagerOculus::Create() { @@ -1262,12 +1403,31 @@ VRSystemManagerOculus::VibrateHaptic(uint32_t aControllerIdx, double aDuration, uint32_t aPromiseID) { - // TODO: Bug 1305892 + // mSession is available after VRDisplay is created + // at GetHMDs(). + if (!mSession) { + return; + } + + RefPtr controller = mOculusController[aControllerIdx]; + MOZ_ASSERT(controller); + + controller->VibrateHaptic(mSession, aHapticIndex, aIntensity, aDuration, aPromiseID); } void VRSystemManagerOculus::StopVibrateHaptic(uint32_t aControllerIdx) { + // mSession is available after VRDisplay is created + // at GetHMDs(). + if (!mSession) { + return; + } + + RefPtr controller = mOculusController[aControllerIdx]; + MOZ_ASSERT(controller); + + controller->StopVibrateHaptic(); } void diff --git a/gfx/vr/gfxVROculus.h b/gfx/vr/gfxVROculus.h index 94146d0b850a..edc775312313 100644 --- a/gfx/vr/gfxVROculus.h +++ b/gfx/vr/gfxVROculus.h @@ -103,15 +103,31 @@ public: void SetIndexTrigger(float aValue); float GetHandTrigger(); void SetHandTrigger(float aValue); + void VibrateHaptic(ovrSession aSession, + uint32_t aHapticIndex, + double aIntensity, + double aDuration, + uint32_t aPromiseID); + void StopVibrateHaptic(); protected: virtual ~VRControllerOculus(); private: + void UpdateVibrateHaptic(ovrSession aSession, + uint32_t aHapticIndex, + double aIntensity, + double aDuration, + uint64_t aVibrateIndex, + uint32_t aPromiseID); + void VibrateHapticComplete(ovrSession aSession, uint32_t aPromiseID, bool aStop); + float mAxisMove[static_cast( OculusControllerAxisType::NumVRControllerAxisType)]; float mIndexTrigger; float mHandTrigger; + nsCOMPtr mVibrateThread; + Atomic mIsVibrateStopped; }; } // namespace impl diff --git a/gfx/vr/gfxVROpenVR.cpp b/gfx/vr/gfxVROpenVR.cpp index ad148206df4a..162ec41531a6 100644 --- a/gfx/vr/gfxVROpenVR.cpp +++ b/gfx/vr/gfxVROpenVR.cpp @@ -462,11 +462,11 @@ VRControllerOpenVR::UpdateVibrateHaptic(vr::IVRSystem* aVRSystem, return; } - double duration = (aIntensity == 0) ? 0 : aDuration; + const double duration = (aIntensity == 0) ? 0 : aDuration; // We expect OpenVR to vibrate for 5 ms, but we found it only response the // commend ~ 3.9 ms. For duration time longer than 3.9 ms, we separate them // to a loop of 3.9 ms for make users feel that is a continuous events. - uint32_t microSec = (duration < 3.9 ? duration : 3.9) * 1000 * aIntensity; + const uint32_t microSec = (duration < 3.9 ? duration : 3.9) * 1000 * aIntensity; aVRSystem->TriggerHapticPulse(GetTrackedIndex(), aHapticIndex, microSec);