Bug 1466699 - Implement VRService thread draft
authorKearwood "Kip" Gilbert <kgilbert@mozilla.com>
Tue, 08 May 2018 11:31:28 -0700
changeset 814651 c86075cf77c4c402367c0ca9211ce416bbe321f0
parent 814584 e8b7331398910233e3adaaed01cad6b75bb562a2
push id115300
push userkgilbert@mozilla.com
push dateThu, 05 Jul 2018 21:11:31 +0000
bugs1466699
milestone63.0a1
Bug 1466699 - Implement VRService thread - Refactored gfxVROpenVR to use gfxVRExternal interface from the VR Service. Existing gfxVROpenVR left in place (for now) to allow VR service to be enabled or disabled by pref. - The VR service, containing gfxVROpenVR, is to run in-process within its own thread first, then to be later moved to its own process. - Fixed periodic immersive mode flicker that occured due to HMD pose and HMD state being separately sampled from the Shmem. It was possible to advance a frame without also getting an updated pose if a dirty copy of the shmem was detected. MozReview-Commit-ID: IvpJErmi5kF
gfx/thebes/gfxPrefs.h
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/gfxVRExternal.cpp
gfx/vr/moz.build
gfx/vr/service/OpenVRSession.cpp
gfx/vr/service/OpenVRSession.h
gfx/vr/service/VRService.cpp
gfx/vr/service/VRService.h
gfx/vr/service/VRSession.cpp
gfx/vr/service/VRSession.h
gfx/vr/service/moz.build
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -385,16 +385,17 @@ private:
   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);
   DECL_GFX_PREF(Live, "dom.vr.puppet.submitframe",             VRPuppetSubmitFrame, uint32_t, 0);
   DECL_GFX_PREF(Live, "dom.vr.display.rafMaxDuration",         VRDisplayRafMaxDuration, uint32_t, 50);
+  DECL_GFX_PREF(Once, "dom.vr.service.enabled",                VRServiceEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
 
   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);
   DECL_GFX_PREF(Live, "general.smoothScroll.lines.durationMaxMS",
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -24,16 +24,19 @@
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
 #include "gfxVROpenVR.h"
 #include "gfxVROSVR.h"
 #endif
 
 #include "gfxVRPuppet.h"
 #include "ipc/VRLayerParent.h"
+#if !defined(MOZ_WIDGET_ANDROID)
+#include "service/VRService.h"
+#endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
 namespace mozilla {
 namespace gfx {
@@ -69,41 +72,60 @@ VRManager::VRManager()
    * native interface for Oculus HMD's.
    *
    * OpenvR comes second, as it is the native interface for HTC Vive
    * which is the most common HMD at this time.
    *
    * OSVR will be used if Oculus SDK and OpenVR don't detect any HMDS,
    * to support everyone else.
    */
-  mExternalManager = VRSystemManagerExternal::Create();
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  // The VR Service accesses all hardware from a separate process
+  // and replaces the other VRSystemManager when enabled.
+  mVRService = VRService::Create();
+  if (mVRService) {
+    mExternalManager = VRSystemManagerExternal::Create(mVRService->GetAPIShmem());
+  }
   if (mExternalManager) {
+    mManagers.AppendElement(mExternalManager);
+  }
+#endif
+
+  if (!mExternalManager) {
+    mExternalManager = VRSystemManagerExternal::Create();
+    if (mExternalManager) {
       mManagers.AppendElement(mExternalManager);
+    }
   }
 
 #if defined(XP_WIN)
-  // The Oculus runtime is supported only on Windows
-  mgr = VRSystemManagerOculus::Create();
-  if (mgr) {
-    mManagers.AppendElement(mgr);
+  if (!mVRService) {
+    // The Oculus runtime is supported only on Windows
+    mgr = VRSystemManagerOculus::Create();
+    if (mgr) {
+      mManagers.AppendElement(mgr);
+    }
   }
 #endif
 
 #if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
-  // OpenVR is cross platform compatible
-  mgr = VRSystemManagerOpenVR::Create();
-  if (mgr) {
-    mManagers.AppendElement(mgr);
-  }
+  if (!mVRService) {
+    // OpenVR is cross platform compatible
+    mgr = VRSystemManagerOpenVR::Create();
+    if (mgr) {
+      mManagers.AppendElement(mgr);
+    }
 
-  // OSVR is cross platform compatible
-  mgr = VRSystemManagerOSVR::Create();
-  if (mgr) {
-      mManagers.AppendElement(mgr);
-  }
+    // OSVR is cross platform compatible
+    mgr = VRSystemManagerOSVR::Create();
+    if (mgr) {
+        mManagers.AppendElement(mgr);
+    }
+  } // !mVRService
 #endif
 
   // 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);
   }
 }
@@ -130,16 +152,21 @@ VRManager::Destroy()
 void
 VRManager::Shutdown()
 {
   mVRDisplays.Clear();
   mVRControllers.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Shutdown();
   }
+#if !defined(MOZ_WIDGET_ANDROID)
+  if (mVRService) {
+    mVRService->Stop();
+  }
+#endif
 }
 
 void
 VRManager::Init()
 {
   mInitialized = true;
 }
 
@@ -316,16 +343,21 @@ void
 VRManager::RefreshVRDisplays(bool aMustDispatch)
 {
   /**
   * 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) {
+#if !defined(MOZ_WIDGET_ANDROID)
+    if (mVRService) {
+      mVRService->Start();
+    }
+#endif
     EnumerateVRDisplays();
   }
 
   /**
    * 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.
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -18,16 +18,19 @@ namespace mozilla {
 namespace layers {
 class TextureHost;
 }
 namespace gfx {
 
 class VRLayerParent;
 class VRManagerParent;
 class VRDisplayHost;
+#if !defined(MOZ_WIDGET_ANDROID)
+class VRService;
+#endif
 class VRSystemManagerPuppet;
 class VRSystemManagerExternal;
 
 class VRManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRManager)
 
 public:
@@ -87,16 +90,19 @@ private:
 
   Atomic<bool> mInitialized;
 
   TimeStamp mLastControllerEnumerationTime;
   TimeStamp mLastDisplayEnumerationTime;
   TimeStamp mLastActiveTime;
   RefPtr<VRSystemManagerPuppet> mPuppetManager;
   RefPtr<VRSystemManagerExternal> mExternalManager;
+#if !defined(MOZ_WIDGET_ANDROID)
+  RefPtr<VRService> mVRService;
+#endif
   bool mVRDisplaysRequested;
   bool mVRControllersRequested;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // GFX_VR_MANAGER_H
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -86,27 +86,22 @@ VRDisplayExternal::ZeroSensor()
 }
 
 void
 VRDisplayExternal::Refresh()
 {
   VRManager *vm = VRManager::Get();
   VRSystemManagerExternal* manager = vm->GetExternalManager();
 
-  manager->PullState(&mDisplayInfo.mDisplayState);
+  manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState);
 }
 
 VRHMDSensorState
 VRDisplayExternal::GetSensorState()
 {
-  VRManager *vm = VRManager::Get();
-  VRSystemManagerExternal* manager = vm->GetExternalManager();
-
-  manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState);
-
   return mLastSensorState;
 }
 
 void
 VRDisplayExternal::StartPresentation()
 {
   if (mIsPresenting) {
     return;
@@ -221,17 +216,17 @@ VRDisplayExternal::SubmitFrame(const lay
 
   VRManager *vm = VRManager::Get();
   VRSystemManagerExternal* manager = vm->GetExternalManager();
   manager->PushState(&state);
 
   VRDisplayState displayState;
   memset(&displayState, 0, sizeof(VRDisplayState));
   while (displayState.mLastSubmittedFrameId < aFrameId) {
-    if (manager->PullState(&displayState)) {
+    if (manager->PullState(&displayState, &mLastSensorState)) {
       if (!displayState.mIsConnected) {
         // Service has shut down or hardware has been disconnected
         return false;
       }
     }
 #ifdef XP_WIN
     Sleep(0);
 #else
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -49,16 +49,17 @@ SOURCES += [
     'VRDisplayHost.cpp',
     'VRDisplayLocal.cpp',
 ]
 
 # Build OpenVR on Windows, Linux, and macOS desktop targets
 if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
     DIRS += [
         'openvr',
+        'service',
     ]
     SOURCES += [
         'gfxVROpenVR.cpp',
     ]
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     SOURCES += [
         'gfxVROculus.cpp',
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/OpenVRSession.cpp
@@ -0,0 +1,487 @@
+#include "OpenVRSession.h"
+
+#if defined(XP_WIN)
+#include <d3d11.h>
+#include "mozilla/gfx/DeviceManagerDx.h"
+#endif // defined(XP_WIN)
+
+#if defined(MOZILLA_INTERNAL_API)
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#endif
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846264338327950288
+#endif
+
+#define BTN_MASK_FROM_ID(_id) \
+  ::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace gfx {
+
+OpenVRSession::OpenVRSession()
+  : VRSession()
+  , mVRSystem(nullptr)
+  , mVRChaperone(nullptr)
+  , mVRCompositor(nullptr)
+  , mShouldQuit(false)
+{
+}
+
+OpenVRSession::~OpenVRSession()
+{
+  Shutdown();
+}
+
+bool
+OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState)
+{
+  if (mVRSystem != nullptr) {
+    // Already initialized
+    return true;
+  }
+  if (!::vr::VR_IsHmdPresent()) {
+    fprintf(stderr, "No HMD detected, VR_IsHmdPresent returned false.\n");
+    return false;
+  }
+
+  ::vr::HmdError err;
+
+  ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
+  if (err) {
+    return false;
+  }
+
+  mVRSystem = (::vr::IVRSystem *)::vr::VR_GetGenericInterface(::vr::IVRSystem_Version, &err);
+  if (err || !mVRSystem) {
+    Shutdown();
+    return false;
+  }
+  mVRChaperone = (::vr::IVRChaperone *)::vr::VR_GetGenericInterface(::vr::IVRChaperone_Version, &err);
+  if (err || !mVRChaperone) {
+    Shutdown();
+    return false;
+  }
+  mVRCompositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(::vr::IVRCompositor_Version, &err);
+  if (err || !mVRCompositor) {
+    Shutdown();
+    return false;
+  }
+
+#if defined(XP_WIN)
+  if (!CreateD3DObjects()) {
+    Shutdown();
+    return false;
+  }
+
+#endif
+
+  // Configure coordinate system
+  mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated);
+
+  if (!InitState(aSystemState)) {
+    Shutdown();
+    return false;
+  }
+
+  // Succeeded
+  return true;
+}
+
+#if defined(XP_WIN)
+bool
+OpenVRSession::CreateD3DObjects()
+{
+  RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
+  if (!device) {
+    return false;
+  }
+  if (!CreateD3DContext(device)) {
+    return false;
+  }
+  return true;
+}
+#endif
+
+void
+OpenVRSession::Shutdown()
+{
+  if (mVRSystem || mVRCompositor || mVRSystem) {
+    ::vr::VR_Shutdown();
+    mVRCompositor = nullptr;
+    mVRChaperone = nullptr;
+    mVRSystem = nullptr;
+  }
+}
+
+bool
+OpenVRSession::InitState(VRSystemState& aSystemState)
+{
+  VRDisplayState& state = aSystemState.displayState;
+  strncpy(state.mDisplayName, "OpenVR HMD", kVRDisplayNameMaxLen);
+  state.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
+  state.mIsMounted = false;
+  state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
+    (int)VRDisplayCapabilityFlags::Cap_Orientation |
+    (int)VRDisplayCapabilityFlags::Cap_Position |
+    (int)VRDisplayCapabilityFlags::Cap_External |
+    (int)VRDisplayCapabilityFlags::Cap_Present |
+    (int)VRDisplayCapabilityFlags::Cap_StageParameters);
+
+  ::vr::ETrackedPropertyError err;
+  bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err);
+  if (err == ::vr::TrackedProp_Success && bHasProximitySensor) {
+    state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)state.mCapabilityFlags | (int)VRDisplayCapabilityFlags::Cap_MountDetection);
+  }
+
+  uint32_t w, h;
+  mVRSystem->GetRecommendedRenderTargetSize(&w, &h);
+  state.mEyeResolution.width = w;
+  state.mEyeResolution.height = h;
+
+  // default to an identity quaternion
+  aSystemState.sensorState.orientation[3] = 1.0f;
+
+  UpdateStageParameters(state);
+  UpdateEyeParameters(state);
+
+  VRHMDSensorState& sensorState = aSystemState.sensorState;
+  sensorState.flags = (VRDisplayCapabilityFlags)(
+    (int)VRDisplayCapabilityFlags::Cap_Orientation |
+    (int)VRDisplayCapabilityFlags::Cap_Position);
+  sensorState.orientation[3] = 1.0f; // Default to an identity quaternion
+
+  return true;
+}
+
+void
+OpenVRSession::UpdateStageParameters(VRDisplayState& state)
+{
+  float sizeX = 0.0f;
+  float sizeZ = 0.0f;
+  if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) {
+    ::vr::HmdMatrix34_t t = mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
+    state.mStageSize.width = sizeX;
+    state.mStageSize.height = sizeZ;
+
+    state.mSittingToStandingTransform[0] = t.m[0][0];
+    state.mSittingToStandingTransform[1] = t.m[1][0];
+    state.mSittingToStandingTransform[2] = t.m[2][0];
+    state.mSittingToStandingTransform[3] = 0.0f;
+
+    state.mSittingToStandingTransform[4] = t.m[0][1];
+    state.mSittingToStandingTransform[5] = t.m[1][1];
+    state.mSittingToStandingTransform[6] = t.m[2][1];
+    state.mSittingToStandingTransform[7] = 0.0f;
+
+    state.mSittingToStandingTransform[8] = t.m[0][2];
+    state.mSittingToStandingTransform[9] = t.m[1][2];
+    state.mSittingToStandingTransform[10] = t.m[2][2];
+    state.mSittingToStandingTransform[11] = 0.0f;
+
+    state.mSittingToStandingTransform[12] = t.m[0][3];
+    state.mSittingToStandingTransform[13] = t.m[1][3];
+    state.mSittingToStandingTransform[14] = t.m[2][3];
+    state.mSittingToStandingTransform[15] = 1.0f;
+  } else {
+    // If we fail, fall back to reasonable defaults.
+    // 1m x 1m space, 0.75m high in seated position
+
+    state.mStageSize.width = 1.0f;
+    state.mStageSize.height = 1.0f;
+
+    state.mSittingToStandingTransform[0] = 1.0f;
+    state.mSittingToStandingTransform[1] = 0.0f;
+    state.mSittingToStandingTransform[2] = 0.0f;
+    state.mSittingToStandingTransform[3] = 0.0f;
+
+    state.mSittingToStandingTransform[4] = 0.0f;
+    state.mSittingToStandingTransform[5] = 1.0f;
+    state.mSittingToStandingTransform[6] = 0.0f;
+    state.mSittingToStandingTransform[7] = 0.0f;
+
+    state.mSittingToStandingTransform[8] = 0.0f;
+    state.mSittingToStandingTransform[9] = 0.0f;
+    state.mSittingToStandingTransform[10] = 1.0f;
+    state.mSittingToStandingTransform[11] = 0.0f;
+
+    state.mSittingToStandingTransform[12] = 0.0f;
+    state.mSittingToStandingTransform[13] = 0.75f;
+    state.mSittingToStandingTransform[14] = 0.0f;
+    state.mSittingToStandingTransform[15] = 1.0f;
+  }
+}
+
+void
+OpenVRSession::UpdateEyeParameters(VRDisplayState& state, gfx::Matrix4x4* headToEyeTransforms /* = nullptr */)
+{
+  for (uint32_t eye = 0; eye < 2; ++eye) {
+    ::vr::HmdMatrix34_t eyeToHead = mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye));
+    state.mEyeTranslation[eye].x = eyeToHead.m[0][3];
+    state.mEyeTranslation[eye].y = eyeToHead.m[1][3];
+    state.mEyeTranslation[eye].z = eyeToHead.m[2][3];
+
+    float left, right, up, down;
+    mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &left, &right, &up, &down);
+    state.mEyeFOV[eye].upDegrees = atan(-up) * 180.0 / M_PI;
+    state.mEyeFOV[eye].rightDegrees = atan(right) * 180.0 / M_PI;
+    state.mEyeFOV[eye].downDegrees = atan(down) * 180.0 / M_PI;
+    state.mEyeFOV[eye].leftDegrees = atan(-left) * 180.0 / M_PI;
+
+    if (headToEyeTransforms) {
+      Matrix4x4 pose;
+      // NOTE! eyeToHead.m is a 3x4 matrix, not 4x4.  But
+      // because of its arrangement, we can copy the 12 elements in and
+      // then transpose them to the right place.
+      memcpy(&pose._11, &eyeToHead.m, sizeof(eyeToHead.m));
+      pose.Transpose();
+      pose.Invert();
+      headToEyeTransforms[eye] = pose;
+    }
+  }
+}
+
+void
+OpenVRSession::GetSensorState(VRSystemState& state)
+{
+  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.
+  mVRCompositor->WaitGetPoses(nullptr, 0, poses, posesSize);
+  gfx::Matrix4x4 headToEyeTransforms[2];
+  UpdateEyeParameters(state.displayState, headToEyeTransforms);
+
+  ::vr::Compositor_FrameTiming timing;
+  timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
+  if (mVRCompositor->GetFrameTiming(&timing)) {
+    state.sensorState.timestamp = timing.m_flSystemTimeInSeconds;
+  } else {
+    // This should not happen, but log it just in case
+    fprintf(stderr, "OpenVR - IVRCompositor::GetFrameTiming failed");
+  }
+
+  if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
+    poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
+    poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == ::vr::TrackingResult_Running_OK)
+  {
+    const ::vr::TrackedDevicePose_t& pose = poses[::vr::k_unTrackedDeviceIndex_Hmd];
+
+    gfx::Matrix4x4 m;
+    // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4.  But
+    // because of its arrangement, we can copy the 12 elements in and
+    // then transpose them to the right place.  We do this so we can
+    // pull out a Quaternion.
+    memcpy(&m._11, &pose.mDeviceToAbsoluteTracking, sizeof(pose.mDeviceToAbsoluteTracking));
+    m.Transpose();
+
+    gfx::Quaternion rot;
+    rot.SetFromRotationMatrix(m);
+    rot.Invert();
+
+    state.sensorState.flags = (VRDisplayCapabilityFlags)((int)state.sensorState.flags | (int)VRDisplayCapabilityFlags::Cap_Orientation);
+    state.sensorState.orientation[0] = rot.x;
+    state.sensorState.orientation[1] = rot.y;
+    state.sensorState.orientation[2] = rot.z;
+    state.sensorState.orientation[3] = rot.w;
+    state.sensorState.angularVelocity[0] = pose.vAngularVelocity.v[0];
+    state.sensorState.angularVelocity[1] = pose.vAngularVelocity.v[1];
+    state.sensorState.angularVelocity[2] = pose.vAngularVelocity.v[2];
+
+    state.sensorState.flags =(VRDisplayCapabilityFlags)((int)state.sensorState.flags | (int)VRDisplayCapabilityFlags::Cap_Position);
+    state.sensorState.position[0] = m._41;
+    state.sensorState.position[1] = m._42;
+    state.sensorState.position[2] = m._43;
+    state.sensorState.linearVelocity[0] = pose.vVelocity.v[0];
+    state.sensorState.linearVelocity[1] = pose.vVelocity.v[1];
+    state.sensorState.linearVelocity[2] = pose.vVelocity.v[2];
+  }
+
+  state.sensorState.CalcViewMatrices(headToEyeTransforms);
+  state.sensorState.inputFrameID++;
+}
+
+void
+OpenVRSession::GetControllerState(VRSystemState &state)
+{
+  // TODO - Implement
+}
+
+void
+OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState)
+{
+  GetSensorState(aSystemState);
+  GetControllerState(aSystemState);
+}
+
+bool
+OpenVRSession::ShouldQuit() const
+{
+  return mShouldQuit;
+}
+
+void
+OpenVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState)
+{
+  bool isHmdPresent = ::vr::VR_IsHmdPresent();
+  if (!isHmdPresent) {
+    mShouldQuit = true;
+  }
+
+  ::vr::VREvent_t event;
+  while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
+    switch (event.eventType) {
+      case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          aSystemState.displayState.mIsMounted = true;
+        }
+        break;
+      case ::vr::VREvent_TrackedDeviceUserInteractionEnded:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          aSystemState.displayState.mIsMounted = false;
+        }
+        break;
+      case ::vr::EVREventType::VREvent_TrackedDeviceActivated:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          aSystemState.displayState.mIsConnected = true;
+        }
+        break;
+      case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          aSystemState.displayState.mIsConnected = false;
+        }
+        break;
+      case ::vr::EVREventType::VREvent_DriverRequestedQuit:
+      case ::vr::EVREventType::VREvent_Quit:
+      case ::vr::EVREventType::VREvent_ProcessQuit:
+      case ::vr::EVREventType::VREvent_QuitAcknowledged:
+      case ::vr::EVREventType::VREvent_QuitAborted_UserPrompt:
+        mShouldQuit = true;
+        break;
+      default:
+        // ignore
+        break;
+    }
+  }
+}
+
+bool
+OpenVRSession::SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer)
+{
+#if defined(XP_WIN)
+
+  if (aLayer.mTextureType == VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor) {
+      RefPtr<ID3D11Texture2D> dxTexture;
+      HRESULT hr = mDevice->OpenSharedResource((HANDLE)aLayer.mTextureHandle,
+        __uuidof(ID3D11Texture2D),
+        (void**)(ID3D11Texture2D**)getter_AddRefs(dxTexture));
+      if (FAILED(hr) || !dxTexture) {
+        NS_WARNING("Failed to open shared texture");
+        return false;
+      }
+
+      // Similar to LockD3DTexture in TextureD3D11.cpp
+      RefPtr<IDXGIKeyedMutex> mutex;
+      dxTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
+      if (mutex) {
+        HRESULT hr = mutex->AcquireSync(0, 1000);
+        if (hr == WAIT_TIMEOUT) {
+          gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
+        }
+        else if (hr == WAIT_ABANDONED) {
+          gfxCriticalNote << "GFX: D3D11 lock mutex abandoned";
+        }
+        if (FAILED(hr)) {
+          NS_WARNING("Failed to lock the texture");
+          return false;
+        }
+      }
+      bool success = SubmitFrame((void *)dxTexture,
+                     ::vr::ETextureType::TextureType_DirectX,
+                     aLayer.mLeftEyeRect, aLayer.mRightEyeRect);
+      if (mutex) {
+        HRESULT hr = mutex->ReleaseSync(0);
+        if (FAILED(hr)) {
+          NS_WARNING("Failed to unlock the texture");
+        }
+      }
+      if (!success) {
+        return false;
+      }
+      return true;
+  }
+
+#elif defined(XP_MACOSX)
+
+  if (aLayer.mTextureType == VRLayerTextureType::LayerTextureType_MacIOSurface) {
+    return SubmitFrame(aLayer.mTextureHandle,
+                       ::vr::ETextureType::TextureType_IOSurface,
+                       aLayer.mLeftEyeRect, aLayer.mRightEyeRect);
+  }
+
+#endif
+
+  return false;
+}
+
+bool
+OpenVRSession::SubmitFrame(void* aTextureHandle,
+                           ::vr::ETextureType aTextureType,
+                           const VRLayerEyeRect& aLeftEyeRect,
+                           const VRLayerEyeRect& aRightEyeRect)
+{
+  ::vr::Texture_t tex;
+  tex.handle = aTextureHandle;
+  tex.eType = aTextureType;
+  tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
+
+  ::vr::VRTextureBounds_t bounds;
+  bounds.uMin = aLeftEyeRect.x;
+  bounds.vMin = 1.0 - aLeftEyeRect.y;
+  bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width;
+  bounds.vMax = 1.0 - (aLeftEyeRect.y + aLeftEyeRect.height);
+
+  ::vr::EVRCompositorError err;
+  err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds);
+  if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
+    printf_stderr("OpenVR Compositor Submit() failed.\n");
+  }
+
+  bounds.uMin = aRightEyeRect.x;
+  bounds.vMin = 1.0 - aRightEyeRect.y;
+  bounds.uMax = aRightEyeRect.x + aRightEyeRect.width;
+  bounds.vMax = 1.0 - (aRightEyeRect.y + aRightEyeRect.height);
+
+  err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds);
+  if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
+    printf_stderr("OpenVR Compositor Submit() failed.\n");
+  }
+
+  mVRCompositor->PostPresentHandoff();
+  return true;
+}
+
+void
+OpenVRSession::StopPresentation()
+{
+  mVRCompositor->ClearLastSubmittedFrame();
+
+  ::vr::Compositor_CumulativeStats stats;
+  mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats));
+  // TODO - Need to send telemetry back to browser.
+  // Bug 1473398 will refactor this original gfxVROpenVR code:
+  //   const uint32_t droppedFramesPerSec = (stats.m_nNumReprojectedFrames -
+  //                                      mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds();
+  // Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR, droppedFramesPerSec);
+}
+
+bool
+OpenVRSession::StartPresentation()
+{
+  return true;
+}
+
+} // namespace mozilla
+} // namespace gfx
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/OpenVRSession.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_SERVICE_OPENVRSESSION_H
+#define GFX_VR_SERVICE_OPENVRSESSION_H
+
+#include "VRSession.h"
+
+#include "openvr.h"
+#include "mozilla/gfx/2D.h"
+#include "moz_external_vr.h"
+
+#if defined(XP_WIN)
+#include <d3d11_1.h>
+#elif defined(XP_MACOSX)
+class MacIOSurface;
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+class OpenVRSession : public VRSession
+{
+public:
+  OpenVRSession();
+  virtual ~OpenVRSession();
+
+  bool Initialize(mozilla::gfx::VRSystemState& aSystemState) override;
+  void Shutdown() override;
+  void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override;
+  void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override;
+  bool ShouldQuit() const override;
+  bool StartPresentation() override;
+  void StopPresentation() override;
+  bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) override;
+
+private:
+  // OpenVR State
+  ::vr::IVRSystem* mVRSystem = nullptr;
+  ::vr::IVRChaperone* mVRChaperone = nullptr;
+  ::vr::IVRCompositor* mVRCompositor = nullptr;
+  bool mShouldQuit;
+
+  bool InitState(mozilla::gfx::VRSystemState& aSystemState);
+  void UpdateStageParameters(mozilla::gfx::VRDisplayState& state);
+  void UpdateEyeParameters(mozilla::gfx::VRDisplayState& state, gfx::Matrix4x4* headToEyeTransforms = nullptr);
+  void GetSensorState(mozilla::gfx::VRSystemState& state);
+  void GetControllerState(VRSystemState &state);
+
+  bool SubmitFrame(void* aTextureHandle,
+                   ::vr::ETextureType aTextureType,
+                   const VRLayerEyeRect& aLeftEyeRect,
+                   const VRLayerEyeRect& aRightEyeRect);
+#if defined(XP_WIN)
+  bool CreateD3DObjects();
+#endif
+};
+
+} // namespace mozilla
+} // namespace gfx
+
+#endif // GFX_VR_SERVICE_OPENVRSESSION_H
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRService.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "VRService.h"
+#include "OpenVRSession.h"
+#include "gfxPrefs.h"
+#include "base/thread.h"                // for Thread
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace std;
+
+namespace {
+
+int64_t
+FrameIDFromBrowserState(const mozilla::gfx::VRBrowserState& aState)
+{
+  for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
+    const VRLayerState& layer = aState.layerState[iLayer];
+    if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
+      return layer.layer_stereo_immersive.mFrameId;
+    }
+  }
+  return 0;
+}
+
+bool
+IsImmersiveContentActive(const mozilla::gfx::VRBrowserState& aState)
+{
+  for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
+    const VRLayerState& layer = aState.layerState[iLayer];
+    if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
+      return true;
+    }
+  }
+  return false;
+}
+
+} // anonymous namespace
+
+/*static*/ already_AddRefed<VRService>
+VRService::Create()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gfxPrefs::VRServiceEnabled()) {
+    return nullptr;
+  }
+
+  RefPtr<VRService> service = new VRService();
+  return service.forget();
+}
+
+VRService::VRService()
+ : mSystemState{}
+ , mBrowserState{}
+ , mServiceThread(nullptr)
+ , mShutdownRequested(false)
+{
+  memset(&mAPIShmem, 0, sizeof(mAPIShmem));
+}
+
+VRService::~VRService()
+{
+  Stop();
+}
+
+void
+VRService::Start()
+{
+  if (!mServiceThread) {
+    /**
+     * We must ensure that any time the service is re-started, that
+     * the VRSystemState is reset, including mSystemState.enumerationCompleted
+     * This must happen before VRService::Start returns to the caller, in order
+     * to prevent the WebVR/WebXR promises from being resolved before the
+     * enumeration has been completed.
+     */
+    memset(&mSystemState, 0, sizeof(mSystemState));
+    PushState(mSystemState);
+
+    mServiceThread = new base::Thread("VRService");
+    base::Thread::Options options;
+    /* Timeout values are powers-of-two to enable us get better data.
+       128ms is chosen for transient hangs because 8Hz should be the minimally
+       acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
+    options.transient_hang_timeout = 128; // milliseconds
+    /* 2048ms is chosen for permanent hangs because it's longer than most
+     * Compositor hangs seen in the wild, but is short enough to not miss getting
+     * native hang stacks. */
+    options.permanent_hang_timeout = 2048; // milliseconds
+
+    if (!mServiceThread->StartWithOptions(options)) {
+      delete mServiceThread;
+      mServiceThread = nullptr;
+      return;
+    }
+
+    mServiceThread->message_loop()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceInitialize",
+      this, &VRService::ServiceInitialize
+    ));
+  }
+}
+
+void
+VRService::Stop()
+{
+  if (mServiceThread) {
+    mServiceThread->message_loop()->PostTask(NewRunnableMethod(
+      "gfx::VRService::RequestShutdown",
+      this, &VRService::RequestShutdown
+    ));
+    delete mServiceThread;
+    mServiceThread = nullptr;
+  }
+}
+
+bool
+VRService::IsInServiceThread()
+{
+  return mServiceThread && mServiceThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void
+VRService::RequestShutdown()
+{
+  MOZ_ASSERT(IsInServiceThread());
+  mShutdownRequested = true;
+}
+
+void
+VRService::ServiceInitialize()
+{
+  MOZ_ASSERT(IsInServiceThread());
+
+  mShutdownRequested = false;
+  memset(&mBrowserState, 0, sizeof(mBrowserState));
+
+  // Try to start a VRSession
+  unique_ptr<VRSession> session;
+
+  // Try OpenVR
+  session = make_unique<OpenVRSession>();
+  if (!session->Initialize(mSystemState)) {
+    session = nullptr;
+  }
+  if (session) {
+    mSession = std::move(session);
+    // Setting enumerationCompleted to true indicates to the browser
+    // that it should resolve any promises in the WebVR/WebXR API
+    // waiting for hardware detection.
+    mSystemState.enumerationCompleted = true;
+    PushState(mSystemState);
+
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceWaitForImmersive",
+      this, &VRService::ServiceWaitForImmersive
+    ));
+  } else {
+    // VR hardware was not detected.
+    // We must inform the browser of the failure so it may try again
+    // later and resolve WebVR promises.  A failure or shutdown is
+    // indicated by enumerationCompleted being set to true, with all
+    // other fields remaining zeroed out.
+    memset(&mSystemState, 0, sizeof(mSystemState));
+    mSystemState.enumerationCompleted = true;
+    PushState(mSystemState);
+  }
+}
+
+void
+VRService::ServiceShutdown()
+{
+  MOZ_ASSERT(IsInServiceThread());
+
+  mSession = nullptr;
+
+  // Notify the browser that we have shut down.
+  // This is indicated by enumerationCompleted being set
+  // to true, with all other fields remaining zeroed out.
+  memset(&mSystemState, 0, sizeof(mSystemState));
+  mSystemState.enumerationCompleted = true;
+  PushState(mSystemState);
+}
+
+void
+VRService::ServiceWaitForImmersive()
+{
+  MOZ_ASSERT(IsInServiceThread());
+  MOZ_ASSERT(mSession);
+
+  mSession->ProcessEvents(mSystemState);
+  PushState(mSystemState);
+  PullState(mBrowserState);
+
+  if (mSession->ShouldQuit() || mShutdownRequested) {
+    // Shut down
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceShutdown",
+      this, &VRService::ServiceShutdown
+    ));
+  } else if (IsImmersiveContentActive(mBrowserState)) {
+    // Enter Immersive Mode
+    mSession->StartPresentation();
+    mSession->StartFrame(mSystemState);
+    PushState(mSystemState);
+
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceImmersiveMode",
+      this, &VRService::ServiceImmersiveMode
+    ));
+  } else {
+    // Continue waiting for immersive mode
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceWaitForImmersive",
+      this, &VRService::ServiceWaitForImmersive
+    ));
+  }
+}
+
+void
+VRService::ServiceImmersiveMode()
+{
+  MOZ_ASSERT(IsInServiceThread());
+  MOZ_ASSERT(mSession);
+
+  mSession->ProcessEvents(mSystemState);
+  PushState(mSystemState);
+  PullState(mBrowserState);
+
+  if (mSession->ShouldQuit() || mShutdownRequested) {
+    // Shut down
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceShutdown",
+      this, &VRService::ServiceShutdown
+    ));
+    return;
+  } else if (!IsImmersiveContentActive(mBrowserState)) {
+    // Exit immersive mode
+    mSession->StopPresentation();
+    MessageLoop::current()->PostTask(NewRunnableMethod(
+      "gfx::VRService::ServiceWaitForImmersive",
+      this, &VRService::ServiceWaitForImmersive
+    ));
+    return;
+  }
+
+  uint64_t newFrameId = FrameIDFromBrowserState(mBrowserState);
+  if (newFrameId != mSystemState.displayState.mLastSubmittedFrameId) {
+    // A new immersive frame has been received.
+    // Submit the textures to the VR system compositor.
+    bool success = false;
+    for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
+      const VRLayerState& layer = mBrowserState.layerState[iLayer];
+      if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
+        success = mSession->SubmitFrame(layer.layer_stereo_immersive);
+        break;
+      }
+    }
+
+    // Changing mLastSubmittedFrameId triggers a new frame to start
+    // rendering.  Changes to mLastSubmittedFrameId and the values
+    // used for rendering, such as headset pose, must be pushed
+    // atomically to the browser.
+    mSystemState.displayState.mLastSubmittedFrameId = newFrameId;
+    mSystemState.displayState.mLastSubmittedFrameSuccessful = success;
+    mSession->StartFrame(mSystemState);
+    PushState(mSystemState);
+  }
+
+  // Continue immersive mode
+  MessageLoop::current()->PostTask(NewRunnableMethod(
+    "gfx::VRService::ServiceImmersiveMode",
+    this, &VRService::ServiceImmersiveMode
+  ));
+}
+
+void
+VRService::PushState(const mozilla::gfx::VRSystemState& aState)
+{
+  // Copying the VR service state to the shmem is atomic, infallable,
+  // and non-blocking on x86/x64 architectures.  Arm requires a mutex
+  // that is locked for the duration of the memcpy to and from shmem on
+  // both sides.
+
+#if defined(MOZ_WIDGET_ANDROID)
+    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == 0) {
+      memcpy((void *)&mAPIShmem.state, &aState, sizeof(VRSystemState));
+      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
+    }
+#else
+  mAPIShmem.generationA++;
+  memcpy((void *)&mAPIShmem.state, &aState, sizeof(VRSystemState));
+  mAPIShmem.generationB++;
+#endif
+}
+
+void
+VRService::PullState(mozilla::gfx::VRBrowserState& aState)
+{
+  // Copying the browser state from the shmem is non-blocking
+  // on x86/x64 architectures.  Arm requires a mutex that is
+  // locked for the duration of the memcpy to and from shmem on
+  // both sides.
+  // On x86/x64 It is fallable -- If a dirty copy is detected by
+  // a mismatch of browserGenerationA and browserGenerationB,
+  // the copy is discarded and will not replace the last known
+  // browser state.
+
+#if defined(MOZ_WIDGET_ANDROID)
+    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->browserMutex)) == 0) {
+      memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
+      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->browserMutex));
+    }
+#else
+  VRExternalShmem tmp;
+  memcpy(&tmp, &mAPIShmem, sizeof(VRExternalShmem));
+  if (tmp.browserGenerationA == tmp.browserGenerationB && tmp.browserGenerationA != 0 && tmp.browserGenerationA != -1) {
+    memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
+  }
+#endif
+}
+
+VRExternalShmem*
+VRService::GetAPIShmem()
+{
+  return &mAPIShmem;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRService.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_SERVICE_VRSERVICE_H
+#define GFX_VR_SERVICE_VRSERVICE_H
+
+#include "mozilla/Atomics.h"
+
+#include "moz_external_vr.h"
+
+#include <thread>
+namespace base {
+class Thread;
+} // namespace base
+namespace mozilla {
+namespace gfx {
+
+class VRSession;
+
+class VRService
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRService)
+  static already_AddRefed<VRService> Create();
+
+  void Start();
+  void Stop();
+  VRExternalShmem* GetAPIShmem();
+
+private:
+  VRService();
+  ~VRService();
+  void PushState(const mozilla::gfx::VRSystemState& aState);
+  void PullState(mozilla::gfx::VRBrowserState& aState);
+
+  /**
+   * VRSystemState contains the most recent state of the VR
+   * system, to be shared with the browser by Shmem.
+   * mSystemState is the VR Service copy of this data, which
+   * is memcpy'ed atomically to the Shmem.
+   * VRSystemState is written by the VR Service, but read-only
+   * by the browser.
+   */
+  VRSystemState mSystemState;
+  /**
+   * VRBrowserState contains the most recent state of the browser.
+   * mBrowserState is memcpy'ed from the Shmem atomically
+   */
+  VRBrowserState mBrowserState;
+
+  std::unique_ptr<VRSession> mSession;
+  base::Thread* mServiceThread;
+  bool mShutdownRequested;
+
+  VRExternalShmem mAPIShmem;
+
+  bool IsInServiceThread();
+  void RequestShutdown();
+
+  /**
+   * The VR Service thread is a state machine that always has one
+   * task queued depending on the state.
+   *
+   * VR Service thread state task functions:
+   */
+  void ServiceInitialize();
+  void ServiceShutdown();
+  void ServiceWaitForImmersive();
+  void ServiceImmersiveMode();
+
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif // GFX_VR_SERVICE_VRSERVICE_H
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRSession.cpp
@@ -0,0 +1,80 @@
+#include "VRSession.h"
+
+#include "moz_external_vr.h"
+
+#if defined(XP_WIN)
+#include <d3d11.h>
+#endif // defined(XP_WIN)
+
+using namespace mozilla::gfx;
+
+VRSession::VRSession()
+{
+
+}
+
+VRSession::~VRSession()
+{
+
+}
+
+#if defined(XP_WIN)
+bool
+VRSession::CreateD3DContext(RefPtr<ID3D11Device> aDevice)
+{
+  if (!mDevice) {
+    if (!aDevice) {
+      NS_WARNING("OpenVRSession::CreateD3DObjects failed to get a D3D11Device");
+      return false;
+    }
+    if (FAILED(aDevice->QueryInterface(__uuidof(ID3D11Device1), getter_AddRefs(mDevice)))) {
+      NS_WARNING("OpenVRSession::CreateD3DObjects failed to get a D3D11Device1");
+      return false;
+    }
+  }
+  if (!mContext) {
+    mDevice->GetImmediateContext1(getter_AddRefs(mContext));
+    if (!mContext) {
+      NS_WARNING("OpenVRSession::CreateD3DObjects failed to get an immediate context");
+      return false;
+    }
+  }
+  if (!mDeviceContextState) {
+    D3D_FEATURE_LEVEL featureLevels[] {
+      D3D_FEATURE_LEVEL_11_1,
+      D3D_FEATURE_LEVEL_11_0
+    };
+    mDevice->CreateDeviceContextState(0,
+                                      featureLevels,
+                                      2,
+                                      D3D11_SDK_VERSION,
+                                      __uuidof(ID3D11Device1),
+                                      nullptr,
+                                      getter_AddRefs(mDeviceContextState));
+  }
+  if (!mDeviceContextState) {
+    NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11DeviceContextState");
+    return false;
+  }
+  return true;
+}
+
+ID3D11Device1*
+VRSession::GetD3DDevice()
+{
+  return mDevice;
+}
+
+ID3D11DeviceContext1*
+VRSession::GetD3DDeviceContext()
+{
+  return mContext;
+}
+
+ID3DDeviceContextState*
+VRSession::GetD3DDeviceContextState()
+{
+  return mDeviceContextState;
+}
+
+#endif // defined(XP_WIN)
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRSession.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_SERVICE_VRSESSION_H
+#define GFX_VR_SERVICE_VRSESSION_H
+
+#include "VRSession.h"
+
+#include "moz_external_vr.h"
+
+#if defined(XP_WIN)
+#include <d3d11_1.h>
+#elif defined(XP_MACOSX)
+class MacIOSurface;
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+class VRSession
+{
+public:
+  VRSession();
+  virtual ~VRSession();
+
+  virtual bool Initialize(mozilla::gfx::VRSystemState& aSystemState) = 0;
+  virtual void Shutdown() = 0;
+  virtual void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) = 0;
+  virtual void StartFrame(mozilla::gfx::VRSystemState& aSystemState) = 0;
+  virtual bool ShouldQuit() const = 0;
+  virtual bool StartPresentation() = 0;
+  virtual void StopPresentation() = 0;
+  virtual bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) = 0;
+
+#if defined(XP_WIN)
+protected:
+  bool CreateD3DContext(RefPtr<ID3D11Device> aDevice);
+  RefPtr<ID3D11Device1> mDevice;
+  RefPtr<ID3D11DeviceContext1> mContext;
+  ID3D11Device1* GetD3DDevice();
+  ID3D11DeviceContext1* GetD3DDeviceContext();
+  ID3DDeviceContextState* GetD3DDeviceContextState();
+  RefPtr<ID3DDeviceContextState> mDeviceContextState;
+#endif
+};
+
+} // namespace mozilla
+} // namespace gfx
+
+#endif // GFX_VR_SERVICE_VRSESSION_H
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Build OpenVR on Windows, Linux, and macOS desktop targets
+if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
+    UNIFIED_SOURCES += [
+        'OpenVRSession.cpp',
+        'VRService.cpp',
+        'VRSession.cpp',
+    ]
+    LOCAL_INCLUDES += [
+        '/dom/base',
+        '/gfx/layers/d3d11',
+        '/gfx/thebes',
+    ]
+    include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5278,16 +5278,18 @@ pref("dom.vr.puppet.enabled", false);
 // result as a base64 image, 2: show it on the screen).
 pref("dom.vr.puppet.submitframe", 0);
 // The number of milliseconds since last frame start before triggering a new frame.
 // When content is failing to submit frames on time or the lower level VR platform API's
 // are rejecting frames, it determines the rate at which RAF callbacks will be called.
 pref("dom.vr.display.rafMaxDuration", 50);
 // VR test system.
 pref("dom.vr.test.enabled", false);
+// Enable the VR Service, which interfaces with VR hardware in a separate thread
+pref("dom.vr.service.enabled", false);
 
 // If the user puts a finger down on an element and we think the user
 // might be executing a pan gesture, how long do we wait before
 // tentatively deciding the gesture is actually a tap and activating
 // the target element?
 pref("ui.touch_activation.delay_ms", 100);
 
 // If the user has clicked an element, how long do we keep the