Bug 1299937 - Part 3: Implement haptic pulse for OpenVR controller; r=kip,qdot
authorDaosheng Mu <daoshengmu@gmail.com>
Thu, 02 Feb 2017 14:57:58 +0800
changeset 349317 ed20b557e9a5745a37fce239dcff94cb35b90c2c
parent 349316 691a5be9ff0d942abf71dfe73714c51fba04f55d
child 349318 3381a8485d054b31ada9f67fd880267db1d6b0ef
push id39455
push userdmu@mozilla.com
push dateFri, 24 Mar 2017 03:25:06 +0000
treeherderautoland@5d8e162ef5f7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskip, qdot
bugs1299937
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1299937 - Part 3: Implement haptic pulse for OpenVR controller; r=kip,qdot MozReview-Commit-ID: TUIbJpyng4
gfx/vr/VRDisplayHost.cpp
gfx/vr/VRDisplayHost.h
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/gfxVR.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVROSVR.cpp
gfx/vr/gfxVROSVR.h
gfx/vr/gfxVROculus.cpp
gfx/vr/gfxVROculus.h
gfx/vr/gfxVROpenVR.cpp
gfx/vr/gfxVROpenVR.h
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRManagerParent.cpp
gfx/vr/ipc/VRManagerParent.h
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -141,16 +141,17 @@ VRDisplayHost::CheckClearDisplayInfoDirt
   if (mDisplayInfo == mLastUpdateDisplayInfo) {
     return false;
   }
   mLastUpdateDisplayInfo = mDisplayInfo;
   return true;
 }
 
 VRControllerHost::VRControllerHost(VRDeviceType aType)
+ : mVibrateIndex(0)
 {
   MOZ_COUNT_CTOR(VRControllerHost);
   mControllerInfo.mType = aType;
   mControllerInfo.mControllerID = VRSystemManager::AllocateDisplayID();
 }
 
 VRControllerHost::~VRControllerHost()
 {
@@ -188,8 +189,19 @@ VRControllerHost::GetPose()
 }
 
 dom::GamepadHand
 VRControllerHost::GetHand()
 {
   return mControllerInfo.mHand;
 }
 
+void
+VRControllerHost::SetVibrateIndex(uint64_t aIndex)
+{
+  mVibrateIndex = aIndex;
+}
+
+uint64_t
+VRControllerHost::GetVibrateIndex()
+{
+  return mVibrateIndex;
+}
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -88,23 +88,26 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerHost)
 
   const VRControllerInfo& GetControllerInfo() const;
   void SetButtonPressed(uint64_t aBit);
   uint64_t GetButtonPressed();
   void SetPose(const dom::GamepadPoseState& aPose);
   const dom::GamepadPoseState& GetPose();
   dom::GamepadHand GetHand();
+  void SetVibrateIndex(uint64_t aIndex);
+  uint64_t GetVibrateIndex();
 
 protected:
   explicit VRControllerHost(VRDeviceType aType);
   virtual ~VRControllerHost();
 
   VRControllerInfo mControllerInfo;
   // The current button pressed bit of button mask.
   uint64_t mButtonPressed;
+  uint64_t mVibrateIndex;
   dom::GamepadPoseState mPose;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_HOST_H */
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -418,10 +418,20 @@ VRManager::NotifyGamepadChange(const T& 
 {
   dom::GamepadChangeEvent e(aInfo);
 
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     Unused << iter.Get()->GetKey()->SendGamepadUpdate(e);
   }
 }
 
+void
+VRManager::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                         double aIntensity, double aDuration)
+{
+  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
+    mManagers[i]->VibrateHaptic(aControllerIdx, aHapticIndex,
+                                aIntensity, aDuration);
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -44,16 +44,18 @@ public:
   void GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo);
 
   void SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
   RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
   void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
   void CreateVRTestSystem();
+  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                     double aIntensity, double aDuration);
 
 protected:
   VRManager();
   ~VRManager();
 
 private:
   RefPtr<layers::TextureHost> mLastFrame;
 
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -62,17 +62,18 @@ void
 VRSystemManager::AddGamepad(const VRControllerInfo& controllerInfo)
 {
   dom::GamepadAdded a(NS_ConvertUTF8toUTF16(controllerInfo.GetControllerName()),
                       mControllerCount,
                       controllerInfo.GetMappingType(),
                       controllerInfo.GetHand(),
                       dom::GamepadServiceType::VR,
                       controllerInfo.GetNumButtons(),
-                      controllerInfo.GetNumAxes());
+                      controllerInfo.GetNumAxes(),
+                      controllerInfo.GetNumHaptics());
 
   VRManager* vm = VRManager::Get();
   MOZ_ASSERT(vm);
   vm->NotifyGamepadChange<dom::GamepadAdded>(a);
 }
 
 void
 VRSystemManager::RemoveGamepad(uint32_t aIndex)
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -209,33 +209,36 @@ struct VRControllerInfo
 {
   VRDeviceType GetType() const { return mType; }
   uint32_t GetControllerID() const { return mControllerID; }
   const nsCString& GetControllerName() const { return mControllerName; }
   dom::GamepadMappingType GetMappingType() const { return mMappingType; }
   dom::GamepadHand GetHand() const { return mHand; }
   uint32_t GetNumButtons() const { return mNumButtons; }
   uint32_t GetNumAxes() const { return mNumAxes; }
+  uint32_t GetNumHaptics() const { return mNumHaptics; }
 
   uint32_t mControllerID;
   VRDeviceType mType;
   nsCString mControllerName;
   dom::GamepadMappingType mMappingType;
   dom::GamepadHand mHand;
   uint32_t mNumButtons;
   uint32_t mNumAxes;
+  uint32_t mNumHaptics;
 
   bool operator==(const VRControllerInfo& other) const {
     return mType == other.mType &&
            mControllerID == other.mControllerID &&
            mControllerName == other.mControllerName &&
            mMappingType == other.mMappingType &&
            mHand == other.mHand &&
            mNumButtons == other.mNumButtons &&
-           mNumAxes == other.mNumAxes;
+           mNumAxes == other.mNumAxes &&
+           mNumHaptics == other.mNumHaptics;
   }
 
   bool operator!=(const VRControllerInfo& other) const {
     return !(*this == other);
   }
 };
 
 class VRSystemManager {
@@ -251,16 +254,19 @@ public:
   virtual bool Init() = 0;
   virtual void Destroy() = 0;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
   virtual bool GetIsPresenting() = 0;
   virtual void HandleInput() = 0;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
   virtual void ScanForControllers() = 0;
   virtual void RemoveControllers() = 0;
+  virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                             double aIntensity, double aDuration, uint32_t aPromiseID) = 0;
+  virtual void StopVibrateHaptic(uint32_t aControllerIdx) = 0;
   void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, double aValue);
   void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
   void NewPoseState(uint32_t aIndex, const dom::GamepadPoseState& aPose);
   void AddGamepad(const VRControllerInfo& controllerInfo);
   void RemoveGamepad(uint32_t aIndex);
 
 protected:
   VRSystemManager() : mControllerCount(0) { }
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -538,16 +538,30 @@ VRSystemManagerOSVR::GetIsPresenting()
 }
 
 void
 VRSystemManagerOSVR::HandleInput()
 {
 }
 
 void
+VRSystemManagerOSVR::VibrateHaptic(uint32_t aControllerIdx,
+                                   uint32_t aHapticIndex,
+                                   double aIntensity,
+                                   double aDuration,
+                                   uint32_t aPromiseID)
+{
+}
+
+void
+VRSystemManagerOSVR::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+}
+
+void
 VRSystemManagerOSVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
 {
 }
 
 void
 VRSystemManagerOSVR::ScanForControllers()
 {
 }
--- a/gfx/vr/gfxVROSVR.h
+++ b/gfx/vr/gfxVROSVR.h
@@ -68,16 +68,19 @@ public:
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
+  virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                             double aIntensity, double aDuration, uint32_t aPromiseID) override;
+  virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
 
 protected:
   VRSystemManagerOSVR()
     : mOSVRInitialized(false)
     , mClientContextInitialized(false)
     , mDisplayConfigInitialized(false)
     , mInterfaceInitialized(false)
     , m_ctx(nullptr)
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -907,17 +907,18 @@ VRControllerOculus::VRControllerOculus(d
 
   MOZ_ASSERT(kNumOculusButton ==
              static_cast<uint32_t>(OculusLeftControllerButtonType::NumButtonType)
              && kNumOculusButton ==
              static_cast<uint32_t>(OculusRightControllerButtonType::NumButtonType));
 
   mControllerInfo.mNumButtons = kNumOculusButton;
   mControllerInfo.mNumAxes = static_cast<uint32_t>(
-                             OculusControllerAxisType::NumVRControllerAxisType);;
+                             OculusControllerAxisType::NumVRControllerAxisType);
+  mControllerInfo.mNumHaptics = kNumOculusHaptcs;
 }
 
 float
 VRControllerOculus::GetAxisMove(uint32_t aAxis)
 {
   return mAxisMove[aAxis];
 }
 
@@ -1250,16 +1251,31 @@ VRSystemManagerOculus::HandlePoseTrackin
   MOZ_ASSERT(aController);
   if (aPose != aController->GetPose()) {
     aController->SetPose(aPose);
     NewPoseState(aControllerIdx, aPose);
   }
 }
 
 void
+VRSystemManagerOculus::VibrateHaptic(uint32_t aControllerIdx,
+                                     uint32_t aHapticIndex,
+                                     double aIntensity,
+                                     double aDuration,
+                                     uint32_t aPromiseID)
+{
+  // TODO: Bug 1305892
+}
+
+void
+VRSystemManagerOculus::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+}
+
+void
 VRSystemManagerOculus::GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                                       aControllerResult)
 {
   if (!mOculusInitialized) {
     return;
   }
 
   aControllerResult.Clear();
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -124,16 +124,19 @@ public:
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
+  virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                             double aIntensity, double aDuration, uint32_t aPromiseID) override;
+  virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
 
 protected:
   VRSystemManagerOculus()
     : mSession(nullptr), mOculusInitialized(false)
   { }
 
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -54,16 +54,18 @@ static pfn_VR_ShutdownInternal vr_Shutdo
 static pfn_VR_IsHmdPresent vr_IsHmdPresent = nullptr;
 static pfn_VR_IsRuntimeInstalled vr_IsRuntimeInstalled = nullptr;
 static pfn_VR_GetStringForHmdError vr_GetStringForHmdError = nullptr;
 static pfn_VR_GetGenericInterface vr_GetGenericInterface = nullptr;
 
 #define BTN_MASK_FROM_ID(_id) \
   vr::ButtonMaskFromId(vr::EVRButtonId::_id)
 
+static const uint32_t kNumOpenVRHaptcs = 1;
+
 
 bool
 LoadOpenVRRuntime()
 {
   static PRLibrary *openvrLib = nullptr;
   std::string openvrPath = gfxPrefs::VROpenVRRuntime();
 
   if (!openvrPath.c_str())
@@ -387,27 +389,35 @@ VRDisplayOpenVR::NotifyVSync()
   // Make sure we respond to OpenVR events even when not presenting
   PollEvents();
 }
 
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                                        uint32_t aNumAxes)
   : VRControllerHost(VRDeviceType::OpenVR)
   , mTrigger(0)
+  , mVibrateThread(nullptr)
+  , mIsVibrating(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
   mControllerInfo.mControllerName.AssignLiteral("OpenVR Gamepad");
   mControllerInfo.mMappingType = GamepadMappingType::_empty;
   mControllerInfo.mHand = aHand;
   mControllerInfo.mNumButtons = aNumButtons;
   mControllerInfo.mNumAxes = aNumAxes;
+  mControllerInfo.mNumHaptics = kNumOpenVRHaptcs;
 }
 
 VRControllerOpenVR::~VRControllerOpenVR()
 {
+  if (mVibrateThread) {
+    mVibrateThread->Shutdown();
+    mVibrateThread = nullptr;
+  }
+
   MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
 }
 
 void
 VRControllerOpenVR::SetTrackedIndex(uint32_t aTrackedIndex)
 {
   mTrackedIndex = aTrackedIndex;
 }
@@ -425,16 +435,85 @@ VRControllerOpenVR::SetTrigger(float aVa
 }
 
 float
 VRControllerOpenVR::GetTrigger()
 {
   return mTrigger;
 }
 
+void
+VRControllerOpenVR::UpdateVibrateHaptic(vr::IVRSystem* aVRSystem,
+                                        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());
+  // Avoid the previous vibrate event to override the new one.
+  if (mVibrateIndex != aVibrateIndex) {
+    return;
+  }
+
+  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;
+  aVRSystem->TriggerHapticPulse(GetTrackedIndex(),
+                                aHapticIndex, microSec);
+
+  // In OpenVR spec, it mentions TriggerHapticPulse() may not trigger another haptic pulse
+  // on this controller and axis combination for 5ms.
+  const double kVibrateRate = 5.0;
+  if (duration >= kVibrateRate) {
+    MOZ_ASSERT(mVibrateThread);
+
+    RefPtr<Runnable> runnable =
+      NewRunnableMethod<vr::IVRSystem*, uint32_t, double, double, uint64_t, uint32_t>
+        (this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
+         aHapticIndex, aIntensity, duration - kVibrateRate, aVibrateIndex, aPromiseID);
+    NS_DelayedDispatchToCurrentThread(runnable.forget(), kVibrateRate);
+  }
+}
+
+void
+VRControllerOpenVR::VibrateHaptic(vr::IVRSystem* aVRSystem,
+                                  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;
+
+  mIsVibrating = true;
+  RefPtr<Runnable> runnable =
+      NewRunnableMethod<vr::IVRSystem*, uint32_t, double, double, uint64_t, uint32_t>
+        (this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem,
+         aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromiseID);
+  mVibrateThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+void
+VRControllerOpenVR::StopVibrateHaptic()
+{
+  mIsVibrating = false;
+}
+
 VRSystemManagerOpenVR::VRSystemManagerOpenVR()
   : mVRSystem(nullptr)
   , mOpenVRInstalled(false)
 {
 }
 
 /*static*/ already_AddRefed<VRSystemManagerOpenVR>
 VRSystemManagerOpenVR::Create()
@@ -739,23 +818,58 @@ VRSystemManagerOpenVR::HandleAxisMove(ui
   }
 }
 
 void
 VRSystemManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx,
                                           const GamepadPoseState& aPose,
                                           VRControllerHost* aController)
 {
+  MOZ_ASSERT(aController);
   if (aPose != aController->GetPose()) {
     aController->SetPose(aPose);
     NewPoseState(aControllerIdx, aPose);
   }
 }
 
 void
+VRSystemManagerOpenVR::VibrateHaptic(uint32_t aControllerIdx,
+                                     uint32_t aHapticIndex,
+                                     double aIntensity,
+                                     double aDuration,
+                                     uint32_t aPromiseID)
+{
+  // mVRSystem is available after VRDisplay is created
+  // at GetHMDs().
+  if (!mVRSystem) {
+    return;
+  }
+
+  RefPtr<impl::VRControllerOpenVR> controller = mOpenVRController[aControllerIdx];
+  MOZ_ASSERT(controller);
+
+  controller->VibrateHaptic(mVRSystem, aHapticIndex, aIntensity, aDuration, aPromiseID);
+}
+
+void
+VRSystemManagerOpenVR::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+  // mVRSystem is available after VRDisplay is created
+  // at GetHMDs().
+  if (!mVRSystem) {
+    return;
+  }
+
+  RefPtr<impl::VRControllerOpenVR> controller = mOpenVRController[aControllerIdx];
+  MOZ_ASSERT(controller);
+
+  controller->StopVibrateHaptic();
+}
+
+void
 VRSystemManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
 {
   if (!mOpenVRInstalled) {
     return;
   }
 
   aControllerResult.Clear();
   for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
@@ -883,8 +997,9 @@ VRSystemManagerOpenVR::ScanForController
 }
 
 void
 VRSystemManagerOpenVR::RemoveControllers()
 {
   mOpenVRController.Clear();
   mControllerCount = 0;
 }
+
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -72,24 +72,39 @@ class VRControllerOpenVR : public VRCont
 {
 public:
   explicit VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                               uint32_t aNumAxes);
   void SetTrackedIndex(uint32_t aTrackedIndex);
   uint32_t GetTrackedIndex();
   void SetTrigger(float aValue);
   float GetTrigger();
+  void VibrateHaptic(vr::IVRSystem* aVRSystem,
+                     uint32_t aHapticIndex,
+                     double aIntensity,
+                     double aDuration,
+                     uint32_t aPromiseID);
+  void StopVibrateHaptic();
 
 protected:
   virtual ~VRControllerOpenVR();
 
 private:
+  void UpdateVibrateHaptic(vr::IVRSystem* aVRSystem,
+                           uint32_t aHapticIndex,
+                           double aIntensity,
+                           double aDuration,
+                           uint64_t aVibrateIndex,
+                           uint32_t aPromiseID);
+
   // The index of tracked devices from vr::IVRSystem.
   uint32_t mTrackedIndex;
   float mTrigger;
+  nsCOMPtr<nsIThread> mVibrateThread;
+  bool mIsVibrating;
 };
 
 } // namespace impl
 
 class VRSystemManagerOpenVR : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerOpenVR> Create();
@@ -98,16 +113,22 @@ public:
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
+  virtual void VibrateHaptic(uint32_t aControllerIdx,
+                             uint32_t aHapticIndex,
+                             double aIntensity,
+                             double aDuration,
+                             uint32_t aPromiseID) override;
+  virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
 
 protected:
   VRSystemManagerOpenVR();
 
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
                          uint32_t aButton,
                          uint64_t aButtonMask,
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -34,16 +34,18 @@ static const uint32_t kPuppetAxes[] = {
   0,
   1,
   2
 };
 
 static const uint32_t kNumPuppetAxis = sizeof(kPuppetAxes) /
                                        sizeof(uint32_t);
 
+static const uint32_t kNumPuppetHaptcs = 1;
+
 VRDisplayPuppet::VRDisplayPuppet()
  : VRDisplayHost(VRDeviceType::Puppet)
  , mIsPresenting(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
 
   mDisplayInfo.mDisplayName.AssignLiteral("Puppet HMD");
   mDisplayInfo.mIsConnected = true;
@@ -231,16 +233,17 @@ VRControllerPuppet::VRControllerPuppet(d
   , mButtonPressState(0)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
   mControllerInfo.mControllerName.AssignLiteral("Puppet Gamepad");
   mControllerInfo.mMappingType = GamepadMappingType::_empty;
   mControllerInfo.mHand = aHand;
   mControllerInfo.mNumButtons = kNumPuppetButtonMask;
   mControllerInfo.mNumAxes = kNumPuppetAxis;
+  mControllerInfo.mNumHaptics = kNumPuppetHaptcs;
 }
 
 VRControllerPuppet::~VRControllerPuppet()
 {
   MOZ_COUNT_DTOR_INHERITED(VRControllerPuppet, VRControllerHost);
 }
 
 void
@@ -414,16 +417,30 @@ VRSystemManagerPuppet::HandlePoseTrackin
   MOZ_ASSERT(aController);
   if (aPose != aController->GetPose()) {
     aController->SetPose(aPose);
     NewPoseState(aControllerIdx, aPose);
   }
 }
 
 void
+VRSystemManagerPuppet::VibrateHaptic(uint32_t aControllerIdx,
+                                     uint32_t aHapticIndex,
+                                     double aIntensity,
+                                     double aDuration,
+                                     uint32_t aPromiseID)
+{
+}
+
+void
+VRSystemManagerPuppet::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+}
+
+void
 VRSystemManagerPuppet::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
 {
   aControllerResult.Clear();
   for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
     aControllerResult.AppendElement(mPuppetController[i]);
   }
 }
 
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -90,16 +90,22 @@ public:
   virtual void Destroy() override;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
+  virtual void VibrateHaptic(uint32_t aControllerIdx,
+                             uint32_t aHapticIndex,
+                             double aIntensity,
+                             double aDuration,
+                             uint32_t aPromiseID) override;
+  virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
 
 protected:
   VRSystemManagerPuppet();
 
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
                          uint32_t aButton,
                          uint64_t aButtonMask,
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -49,21 +49,25 @@ parent:
   // sensor state is the "Zero" position.
   async ResetSensor(uint32_t aDisplayID);
 
   sync GetSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
   async SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
+  async VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                      double aIntensity, double aDuration);
+
   async CreateVRTestSystem();
   async CreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID);
   async CreateVRServiceTestController(nsCString aID, uint32_t aPromiseID);
   async SetDisplayInfoToMockDisplay(uint32_t aDeviceID, VRDisplayInfo aDisplayInfo);
   async SetSensorStateToMockDisplay(uint32_t aDeviceID, VRHMDSensorState aSensorState);
+
   async NewButtonEventToMockController(uint32_t aDeviceID, long aButton,
                                        bool aPressed);
   async NewAxisMoveEventToMockController(uint32_t aDeviceID, long aAxis,
                                          double aValue);
   async NewPoseMoveToMockController(uint32_t aDeviceID, GamepadPoseState aPose);
 
 child:
 
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -433,16 +433,28 @@ VRManagerParent::RecvNewPoseMoveToMockCo
   RefPtr<impl::VRControllerPuppet> controllerPuppet;
   mVRControllerTests.Get(mControllerTestID,
                          getter_AddRefs(controllerPuppet));
   MOZ_ASSERT(controllerPuppet);
   controllerPuppet->SetPoseMoveState(pose);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+VRManagerParent::RecvVibrateHaptic(const uint32_t& aControllerIdx,
+                                   const uint32_t& aHapticIndex,
+                                   const double& aIntensity,
+                                   const double& aDuration)
+{
+  VRManager* vm = VRManager::Get();
+  vm->VibrateHaptic(aControllerIdx, aHapticIndex, aIntensity,
+                    aDuration);
+  return IPC_OK();
+}
+
 bool
 VRManagerParent::SendGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
 {
   // GamepadManager only exists at the content process
   // or the same process in non-e10s mode.
   if (mHaveControllerListener &&
       (mIsContentChild || IsSameProcess())) {
     return PVRManagerParent::SendGamepadUpdate(aGamepadEvent);
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -86,16 +86,18 @@ protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual mozilla::ipc::IPCResult RecvRefreshDisplays() override;
   virtual mozilla::ipc::IPCResult RecvResetSensor(const uint32_t& aDisplayID) override;
   virtual mozilla::ipc::IPCResult RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
   virtual mozilla::ipc::IPCResult RecvSetHaveEventListener(const bool& aHaveEventListener) override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerAdded() override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerRemoved() override;
+  virtual mozilla::ipc::IPCResult RecvVibrateHaptic(const uint32_t& aControllerIdx, const uint32_t& aHapticIndex,
+                                                    const double& aIntensity, const double& aDuration) override;
   virtual mozilla::ipc::IPCResult RecvCreateVRTestSystem() override;
   virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestDisplay(const nsCString& aID, const uint32_t& aPromiseID) override;
   virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestController(const nsCString& aID, const uint32_t& aPromiseID) override;
   virtual mozilla::ipc::IPCResult RecvSetDisplayInfoToMockDisplay(const uint32_t& aDeviceID,
                                                                   const VRDisplayInfo& aDisplayInfo) override;
   virtual mozilla::ipc::IPCResult RecvSetSensorStateToMockDisplay(const uint32_t& aDeviceID,
                                                                   const VRHMDSensorState& aSensorState) override;
   virtual mozilla::ipc::IPCResult RecvNewButtonEventToMockController(const uint32_t& aDeviceID, const long& aButton,