Bug 1229480 - (WIP, don't commit) Implement Puppet VR Device draft
authorKearwood (Kip) Gilbert <kgilbert@mozilla.com>
Thu, 15 Dec 2016 15:12:28 -0800
changeset 464368 6cb5872bb3ce1e9d40b154bc26dd793c6af605cd
parent 457090 0d823cf54df53e0cea75a74adebace956bd333d8
child 465424 3b02d1a5dbab947795d22922a01b3f32ccdfcc81
push id42337
push userbmo:kgilbert@mozilla.com
push dateFri, 20 Jan 2017 21:37:26 +0000
bugs1229480
milestone53.0a1
Bug 1229480 - (WIP, don't commit) Implement Puppet VR Device MozReview-Commit-ID: GQ08eGVFXel
gfx/thebes/gfxPrefs.h
gfx/vr/VRManager.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVROpenVR.cpp
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
gfx/vr/moz.build
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -319,16 +319,17 @@ private:
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   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.poseprediction.enabled",         VRPosePredictionEnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "VRManager.h"
 #include "VRManagerParent.h"
 #include "gfxVR.h"
 #include "gfxVROpenVR.h"
+#include "gfxVRPuppet.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/VRDisplay.h"
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/Unused.h"
 
 #include "gfxPrefs.h"
 #include "gfxVR.h"
@@ -88,16 +89,23 @@ VRManager::VRManager()
   }
 
   // OSVR is cross platform compatible
   mgr = VRDisplayManagerOSVR::Create();
   if (mgr) {
       mManagers.AppendElement(mgr);
   }
 #endif
+
+  // Puppet VR devices
+  mgr = VRDisplayManagerPuppet::Create();
+  if (mgr) {
+      mManagers.AppendElement(mgr);
+  }
+
   // Enable gamepad extensions while VR is enabled.
   // Preference only can be set at the Parent process.
   if (XRE_IsParentProcess() && gfxPrefs::VREnabled()) {
     Preferences::SetBool("dom.gamepad.extensions.enabled", true);
   }
 }
 
 VRManager::~VRManager()
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -29,16 +29,17 @@ namespace gfx {
 class VRLayerParent;
 class VRDisplayHost;
 class VRControllerHost;
 
 enum class VRDeviceType : uint16_t {
   Oculus,
   OpenVR,
   OSVR,
+  Puppet,
   NumVRDeviceTypes
 };
 
 enum class VRDisplayCapabilityFlags : uint16_t {
   Cap_None = 0,
   /**
    * Cap_Position is set if the VRDisplay is capable of tracking its position.
    */
@@ -210,16 +211,23 @@ public:
   virtual void Destroy() = 0;
   virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
 
 protected:
   VRDisplayManager() { }
   virtual ~VRDisplayManager() { }
 };
 
+enum class VRControllerAxisType : uint16_t {
+  TrackpadXAxis,
+  TrackpadYAxis,
+  Trigger,
+  NumVRControllerAxisType
+};
+
 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; }
   uint32_t GetNumButtons() const { return mNumButtons; }
   uint32_t GetNumAxes() const { return mNumAxes; }
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -70,23 +70,16 @@ const uint64_t gOpenVRButtonMask[] = {
   // vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_DPad_Up),
   // vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_DPad_Right),
   // vr::ButtonMaskFromId(vr::EVRButtonId::k_EButton_DPad_Down)
 };
 
 const uint32_t gNumOpenVRButtonMask = sizeof(gOpenVRButtonMask) /
                                       sizeof(uint64_t);
 
-enum class VRControllerAxisType : uint16_t {
-  TrackpadXAxis,
-  TrackpadYAxis,
-  Trigger,
-  NumVRControllerAxisType
-};
-
 #define VRControllerAxis(aButtonId) (aButtonId - vr::EVRButtonId::k_EButton_Axis0)
 
 const uint32_t gOpenVRAxes[] = {
   VRControllerAxis(vr::EVRButtonId::k_EButton_Axis0),
   VRControllerAxis(vr::EVRButtonId::k_EButton_Axis0),
   VRControllerAxis(vr::EVRButtonId::k_EButton_Axis1)
 };
 
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <math.h>
+
+#include "prlink.h"
+#include "prmem.h"
+#include "prenv.h"
+#include "gfxPrefs.h"
+#include "nsString.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/gfx/Quaternion.h"
+
+#ifdef XP_WIN
+#include "CompositorD3D11.h"
+#include "TextureD3D11.h"
+#endif // XP_WIN
+
+#include "gfxVRPuppet.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIScreenManager.h"
+
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::gfx::impl;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+
+// Reminder: changing the order of these buttons may break web content.
+const uint64_t gPuppetButtonMask[] = {
+  1,
+  2,
+  4,
+  8,
+};
+
+const uint32_t gNumPuppetButtonMask = sizeof(gPuppetButtonMask) /
+                                      sizeof(uint64_t);
+
+const uint32_t gPuppetAxes[] = {
+  0,
+  1,
+  2,
+};
+
+const uint32_t gNumPuppetAxis = sizeof(gPuppetAxes) /
+                                sizeof(uint32_t);
+
+
+VRDisplayPuppet::VRDisplayPuppet()
+  : VRDisplayHost(VRDeviceType::Puppet)
+  , mIsPresenting(false)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
+
+  mDisplayInfo.mDisplayName.AssignLiteral("Puppet HMD");
+  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                                  VRDisplayCapabilityFlags::Cap_Orientation |
+                                  VRDisplayCapabilityFlags::Cap_Position |
+                                  VRDisplayCapabilityFlags::Cap_External |
+                                  VRDisplayCapabilityFlags::Cap_Present |
+                                  VRDisplayCapabilityFlags::Cap_StageParameters;
+
+  mDisplayInfo.mEyeResolution.width = 1836; // 1080 * 1.7
+  mDisplayInfo.mEyeResolution.height = 2040; // 1200 * 1.7
+
+  // SteamVR gives the application a single FOV to use; it's not configurable as with Oculus
+  for (uint32_t eye = 0; eye < 2; ++eye) {
+    mDisplayInfo.mEyeTranslation[eye].x = 0.0f;
+    mDisplayInfo.mEyeTranslation[eye].y = 0.0f;
+    mDisplayInfo.mEyeTranslation[eye].z = 0.0f;
+    mDisplayInfo.mEyeFOV[eye] = VRFieldOfView(45.0, 45.0, 45.0, 45.0);
+  }
+
+  // default: 1m x 1m space, 0.75m high in seated position
+
+  mDisplayInfo.mStageSize.width = 1.0f;
+  mDisplayInfo.mStageSize.height = 1.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._11 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._12 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._13 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._14 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._21 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._22 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._23 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._24 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._31 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._32 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._33 = 1.0f;
+  mDisplayInfo.mSittingToStandingTransform._34 = 0.0f;
+
+  mDisplayInfo.mSittingToStandingTransform._41 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._42 = 0.75f;
+  mDisplayInfo.mSittingToStandingTransform._43 = 0.0f;
+  mDisplayInfo.mSittingToStandingTransform._44 = 1.0f;
+}
+
+VRDisplayPuppet::~VRDisplayPuppet()
+{
+  Destroy();
+  MOZ_COUNT_DTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
+}
+
+void
+VRDisplayPuppet::Destroy()
+{
+  StopPresentation();
+}
+
+void
+VRDisplayPuppet::ZeroSensor()
+{
+  // XXX Implement Me
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetSensorState()
+{
+  return GetSensorState(0.0f);
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetImmediateSensorState()
+{
+  return GetSensorState(0.0f);
+}
+
+VRHMDSensorState
+VRDisplayPuppet::GetSensorState(double timeOffset)
+{
+  VRHMDSensorState result;
+  result.Clear();
+  result.timestamp = PR_Now();
+
+  gfx::Quaternion rot;
+
+  result.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
+  result.orientation[0] = rot.x;
+  result.orientation[1] = rot.y;
+  result.orientation[2] = rot.z;
+  result.orientation[3] = rot.w;
+  result.angularVelocity[0] = 0.0f;
+  result.angularVelocity[1] = 0.0f;
+  result.angularVelocity[2] = 0.0f;
+
+  result.flags |= VRDisplayCapabilityFlags::Cap_Position;
+  result.position[0] = 0.0f;
+  result.position[1] = 0.0f;
+  result.position[2] = 0.0f;
+  result.linearVelocity[0] = 0.0f;
+  result.linearVelocity[1] = 0.0f;
+  result.linearVelocity[2] = 0.0f;
+
+  return result;
+}
+
+void
+VRDisplayPuppet::StartPresentation()
+{
+  if (mIsPresenting) {
+    return;
+  }
+  mIsPresenting = true;
+}
+
+void
+VRDisplayPuppet::StopPresentation()
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  mIsPresenting = false;
+}
+
+
+#if defined(XP_WIN)
+
+void
+VRDisplayPuppet::SubmitFrame(TextureSourceD3D11* aSource,
+  const IntSize& aSize,
+  const VRHMDSensorState& aSensorState,
+  const gfx::Rect& aLeftEyeRect,
+  const gfx::Rect& aRightEyeRect)
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  ID3D11Texture2D* tex = aSource->GetD3D11Texture();
+
+  // FINDME!!! KIP!!! HACK!!!  Need to block until the next simulated vblank
+  // interval
+
+  // Trigger the next VSync immediately
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+}
+
+#else
+
+void
+VRDisplayPuppet::SubmitFrame(TextureSourceOGL* aSource,
+  const IntSize& aSize,
+  const VRHMDSensorState& aSensorState,
+  const gfx::Rect& aLeftEyeRect,
+  const gfx::Rect& aRightEyeRect)
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  GLTextureSource* glSource = aSource->AsGLTextureSource();
+  if (glSource == nullptr) {
+    printf_stderr("Puppet Compositor Submit() failed.\n");
+    return;
+  }
+
+  // GLuint tex = glSource->GetTextureHandle();
+
+  // FINDME!!! KIP!!! HACK!!!  Need to block until the next simulated vblank
+  // interval
+
+  // Trigger the next VSync immediately
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+}
+
+#endif
+
+void
+VRDisplayPuppet::NotifyVSync()
+{
+  // We update mIsConneced once per frame.
+  mDisplayInfo.mIsConnected = true; // FINDME!! KIP!! HACK!!
+}
+
+VRDisplayManagerPuppet::VRDisplayManagerPuppet()
+{
+}
+
+/*static*/ already_AddRefed<VRDisplayManagerPuppet>
+VRDisplayManagerPuppet::Create()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gfxPrefs::VREnabled() || !gfxPrefs::VRPuppetEnabled()) {
+    return nullptr;
+  }
+
+  RefPtr<VRDisplayManagerPuppet> manager = new VRDisplayManagerPuppet();
+  return manager.forget();
+}
+
+bool
+VRDisplayManagerPuppet::Init()
+{
+  return true;
+}
+
+void
+VRDisplayManagerPuppet::Destroy()
+{
+  mPuppetHMD = nullptr;
+}
+
+void
+VRDisplayManagerPuppet::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
+  if (mPuppetHMD == nullptr) {
+    mPuppetHMD = new VRDisplayPuppet();
+  }
+  aHMDResult.AppendElement(mPuppetHMD);
+}
+
+VRControllerPuppet::VRControllerPuppet()
+  : VRControllerHost(VRDeviceType::Puppet)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
+  mControllerInfo.mControllerName.AssignLiteral("Puppet HMD");
+  mControllerInfo.mMappingType = GamepadMappingType::_empty;
+  mControllerInfo.mNumButtons = gNumPuppetButtonMask;
+  mControllerInfo.mNumAxes = gNumPuppetAxis;
+}
+
+VRControllerPuppet::~VRControllerPuppet()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRControllerPuppet, VRControllerHost);
+}
+
+VRControllerManagerPuppet::VRControllerManagerPuppet()
+{
+}
+
+VRControllerManagerPuppet::~VRControllerManagerPuppet()
+{
+  Destroy();
+}
+
+/*static*/ already_AddRefed<VRControllerManagerPuppet>
+VRControllerManagerPuppet::Create()
+{
+  if (!gfxPrefs::VREnabled() || !gfxPrefs::VRPuppetEnabled()) {
+    return nullptr;
+  }
+
+  RefPtr<VRControllerManagerPuppet> manager = new VRControllerManagerPuppet();
+  return manager.forget();
+}
+
+bool
+VRControllerManagerPuppet::Init()
+{
+  return true;
+}
+
+void
+VRControllerManagerPuppet::Destroy()
+{
+  RemoveDevices();
+}
+
+void
+VRControllerManagerPuppet::HandleInput()
+{
+}
+
+void
+VRControllerManagerPuppet::HandleButtonPress(uint32_t aControllerIdx,
+                                             uint64_t aButtonPressed)
+{
+  uint64_t buttonMask = 0;
+  RefPtr<impl::VRControllerPuppet> controller;
+  controller = mPuppetController[aControllerIdx];
+  uint64_t diff = (controller->GetButtonPressed() ^ aButtonPressed);
+
+  if (!diff) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < gNumPuppetButtonMask; ++i) {
+    buttonMask = gPuppetButtonMask[i];
+
+    if (diff & buttonMask) {
+      // diff & aButtonPressed would be true while a new button press
+      // event, otherwise it is an old press event and needs to notify
+      // the button has been released.
+      NewButtonEvent(aControllerIdx, i, diff & aButtonPressed);
+    }
+  }
+
+  controller->SetButtonPressed(aButtonPressed);
+}
+
+void
+VRControllerManagerPuppet::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                                          float aValue)
+{
+  if (aValue != 0.0f) {
+    NewAxisMove(aControllerIdx, aAxis, aValue);
+  }
+}
+
+void
+VRControllerManagerPuppet::HandlePoseTracking(uint32_t aControllerIdx,
+                                              const GamepadPoseState& aPose,
+                                              VRControllerHost* aController)
+{
+  if (aPose != aController->GetPose()) {
+    aController->SetPose(aPose);
+    NewPoseState(aControllerIdx, aPose);
+  }
+}
+
+void
+VRControllerManagerPuppet::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+{
+  aControllerResult.Clear();
+  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
+    aControllerResult.AppendElement(mPuppetController[i]);
+  }
+}
+
+void
+VRControllerManagerPuppet::ScanForDevices()
+{
+
+}
+
+void
+VRControllerManagerPuppet::RemoveDevices()
+{
+  mPuppetController.Clear();
+  mControllerCount = 0;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRPuppet.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_VR_PUPPET_H
+#define GFX_VR_PUPPET_H
+
+#include "nsTArray.h"
+#include "nsIScreen.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/EnumeratedArray.h"
+
+#include "gfxVR.h"
+
+namespace mozilla {
+namespace gfx {
+namespace impl {
+
+class VRDisplayPuppet : public VRDisplayHost
+{
+public:
+  virtual void NotifyVSync() override;
+  virtual VRHMDSensorState GetSensorState() override;
+  virtual VRHMDSensorState GetImmediateSensorState() override;
+  void ZeroSensor() override;
+
+protected:
+  virtual void StartPresentation() override;
+  virtual void StopPresentation() override;
+#if defined(XP_WIN)
+  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+                           const IntSize& aSize,
+                           const VRHMDSensorState& aSensorState,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) override;
+#else
+  virtual void SubmitFrame(mozilla::layers::TextureSourceOGL* aSource,
+                           const IntSize& aSize,
+                           const VRHMDSensorState& aSensorState,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) override;
+#endif
+
+public:
+  explicit VRDisplayPuppet();
+
+protected:
+  virtual ~VRDisplayPuppet();
+  void Destroy();
+
+  VRHMDSensorState GetSensorState(double timeOffset);
+
+  bool mIsPresenting;
+};
+
+} // namespace impl
+
+class VRDisplayManagerPuppet : public VRDisplayManager
+{
+public:
+  static already_AddRefed<VRDisplayManagerPuppet> Create();
+
+  virtual bool Init() override;
+  virtual void Destroy() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
+protected:
+  VRDisplayManagerPuppet();
+
+  // there can only be one
+  RefPtr<impl::VRDisplayPuppet> mPuppetHMD;
+};
+
+namespace impl {
+
+class VRControllerPuppet : public VRControllerHost
+{
+public:
+  explicit VRControllerPuppet();
+
+protected:
+  virtual ~VRControllerPuppet();
+};
+
+} // namespace impl
+
+class VRControllerManagerPuppet : public VRControllerManager
+{
+public:
+  static already_AddRefed<VRControllerManagerPuppet> Create();
+
+  virtual bool Init() override;
+  virtual void Destroy() override;
+  virtual void HandleInput() override;
+  virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
+                              aControllerResult) override;
+  virtual void ScanForDevices() override;
+  virtual void RemoveDevices() override;
+
+private:
+  VRControllerManagerPuppet();
+  ~VRControllerManagerPuppet();
+
+  virtual void HandleButtonPress(uint32_t aControllerIdx,
+                                 uint64_t aButtonPressed) override;
+  virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
+                              float aValue) override;
+  virtual void HandlePoseTracking(uint32_t aControllerIdx,
+                                  const dom::GamepadPoseState& aPose,
+                                  VRControllerHost* aController) override;
+
+  nsTArray<RefPtr<impl::VRControllerPuppet>> mPuppetController;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+
+#endif /* GFX_VR_PUPPET_H */
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -20,16 +20,17 @@ LOCAL_INCLUDES += [
     '/gfx/layers/d3d11',
     '/gfx/thebes',
 ]
 
 UNIFIED_SOURCES += [
     'gfxVR.cpp',
     'gfxVROpenVR.cpp',
     'gfxVROSVR.cpp',
+    'gfxVRPuppet.cpp',
     'ipc/VRLayerChild.cpp',
     'ipc/VRLayerParent.cpp',
     'ipc/VRManagerChild.cpp',
     'ipc/VRManagerParent.cpp',
     'VRDisplayClient.cpp',
     'VRDisplayHost.cpp',
     'VRDisplayPresentation.cpp',
     'VRManager.cpp',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4977,16 +4977,18 @@ pref("dom.vr.oculus.enabled", true);
 pref("dom.vr.osvr.enabled", false);
 // OpenVR device
 pref("dom.vr.openvr.enabled", false);
 // 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.  It is disabled by default for now due to
 // frame uniformity issues with e10s.
 pref("dom.vr.poseprediction.enabled", false);
+// Puppet device, used for simulating VR hardware within tests and dev tools
+pref("dom.vr.puppet.enabled", false);
 // path to openvr DLL
 pref("gfx.vr.openvr-runtime", "");
 // path to OSVR DLLs
 pref("gfx.vr.osvr.utilLibPath", "");
 pref("gfx.vr.osvr.commonLibPath", "");
 pref("gfx.vr.osvr.clientLibPath", "");
 pref("gfx.vr.osvr.clientKitLibPath", "");