Bug 1413362 - part 2: Gecko gfx/vr GVR WebVR implementation r=kip,daoshengmu
☠☠ backed out by 0b32504fcef2 ☠ ☠
authorRandall Barker <rbarker@mozilla.com>
Tue, 31 Oct 2017 17:25:40 -0700
changeset 443751 2d9da0d19d04c54d04f87f8ca5d13cc81f3ba048
parent 443750 653c66220a5f3c59f2119bb7cea3e722fee6c47c
child 443752 8b15dfaeecaa4cc729c180fe0d09de97b8228633
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskip, daoshengmu
bugs1413362
milestone58.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 1413362 - part 2: Gecko gfx/vr GVR WebVR implementation r=kip,daoshengmu MozReview-Commit-ID: HWaXhQo0VML
config/system-headers
dom/vr/VRDisplay.cpp
gfx/gl/GLBlitHelper.h
gfx/vr/VRDisplayClient.cpp
gfx/vr/VRDisplayClient.h
gfx/vr/VRDisplayHost.cpp
gfx/vr/VRDisplayHost.h
gfx/vr/VRManager.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVRGVR.cpp
gfx/vr/gfxVRGVR.h
gfx/vr/gfxVRGVRAPI.h
gfx/vr/gfxVROSVR.cpp
gfx/vr/gfxVROSVR.h
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
gfx/vr/ipc/VRMessageUtils.h
gfx/vr/jni/gfxGVRJNI.cpp
gfx/vr/moz.build
--- a/config/system-headers
+++ b/config/system-headers
@@ -1183,16 +1183,20 @@ UTextEdit.h
 UTextTraits.h
 utime.h
 UWindows.h
 values.h
 varargs.h
 vcclr.h
 View.h
 Volume.h
+#ifdef ANDROID
+vr/gvr/capi/include/gvr.h
+vr/gvr/capi/include/gvr_controller.h
+#endif
 wab.h
 wait.h
 wchar.h
 wctype.h
 winbase.h
 win/compobj.h
 windef.h
 Window.h
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -653,16 +653,21 @@ VRDisplay::GetLayers(nsTArray<VRLayer>& 
   }
 }
 
 void
 VRDisplay::SubmitFrame()
 {
   AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplay");
 
+  if (mClient && !mClient->IsPresentationGenerationCurrent()) {
+    mPresentation = nullptr;
+    mClient->MakePresentationGenerationCurrent();
+  }
+
   if (mPresentation) {
     mPresentation->SubmitFrame();
   }
   mFrameInfo.Clear();
 }
 
 int32_t
 VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback,
--- a/gfx/gl/GLBlitHelper.h
+++ b/gfx/gl/GLBlitHelper.h
@@ -31,16 +31,17 @@ class MacIOSurfaceImage;
 class SurfaceDescriptorD3D10;
 class SurfaceDescriptorDXGIYCbCr;
 } // namespace layers
 
 namespace gl {
 
 class BindAnglePlanes;
 class GLContext;
+class GLBlitHelper;
 
 bool
 GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
               gfx::IntSize* const out_divisors);
 
 template<uint8_t N>
 struct Mat
 {
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -28,16 +28,17 @@ using namespace mozilla;
 using namespace mozilla::gfx;
 
 VRDisplayClient::VRDisplayClient(const VRDisplayInfo& aDisplayInfo)
   : mDisplayInfo(aDisplayInfo)
   , bLastEventWasMounted(false)
   , bLastEventWasPresenting(false)
   , mPresentationCount(0)
   , mLastEventFrameId(0)
+  , mLastPresentingGeneration(0)
 {
   MOZ_COUNT_CTOR(VRDisplayClient);
 }
 
 VRDisplayClient::~VRDisplayClient() {
   MOZ_COUNT_DTOR(VRDisplayClient);
 }
 
@@ -72,16 +73,32 @@ VRDisplayClient::ZeroSensor()
 
 void
 VRDisplayClient::SetGroupMask(uint32_t aGroupMask)
 {
   VRManagerChild *vm = VRManagerChild::Get();
   vm->SendSetGroupMask(mDisplayInfo.mDisplayID, aGroupMask);
 }
 
+bool
+VRDisplayClient::IsPresentationGenerationCurrent() const
+{
+  if (mLastPresentingGeneration != mDisplayInfo.mPresentingGeneration) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+VRDisplayClient::MakePresentationGenerationCurrent()
+{
+  mLastPresentingGeneration = mDisplayInfo.mPresentingGeneration;
+}
+
 void
 VRDisplayClient::FireEvents()
 {
   VRManagerChild *vm = VRManagerChild::Get();
   // Only fire these events for non-chrome VR sessions
   bool isPresenting = (mDisplayInfo.mPresentingGroups & kVRGroupContent) != 0;
 
   // Check if we need to trigger onVRDisplayPresentChange event
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -39,28 +39,32 @@ public:
                                                             uint32_t aGroup);
   void PresentationDestroyed();
 
   bool GetIsConnected() const;
 
   void NotifyDisconnected();
   void SetGroupMask(uint32_t aGroupMask);
 
+  bool IsPresentationGenerationCurrent() const;
+  void MakePresentationGenerationCurrent();
+
 protected:
   virtual ~VRDisplayClient();
 
   void FireEvents();
 
   VRDisplayInfo mDisplayInfo;
 
   bool bLastEventWasMounted;
   bool bLastEventWasPresenting;
 
   int mPresentationCount;
   uint64_t mLastEventFrameId;
+  uint32_t mLastPresentingGeneration;
 private:
   VRSubmitFrameResultInfo mSubmitFrameResult;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_CLIENT_H */
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -67,16 +67,17 @@ VRDisplayHost::VRDisplayHost(VRDeviceTyp
  , mFrameStarted(false)
 {
   MOZ_COUNT_CTOR(VRDisplayHost);
   mDisplayInfo.mType = aType;
   mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
   mDisplayInfo.mPresentingGroups = 0;
   mDisplayInfo.mGroupMask = kVRGroupContent;
   mDisplayInfo.mFrameId = 0;
+  mDisplayInfo.mPresentingGeneration = 0;
 }
 
 VRDisplayHost::~VRDisplayHost()
 {
   MOZ_COUNT_DTOR(VRDisplayHost);
 }
 
 #if defined(XP_WIN)
@@ -328,24 +329,33 @@ VRDisplayHost::SubmitFrame(VRLayerParent
       }
       IntSize texSize = gfx::IntSize(surf->GetDevicePixelWidth(),
                                      surf->GetDevicePixelHeight());
       if (!SubmitFrame(surf, texSize, aLeftEyeRect, aRightEyeRect)) {
         return;
       }
       break;
     }
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+    case SurfaceDescriptor::TEGLImageDescriptor: {
+       const EGLImageDescriptor& desc = aTexture.get_EGLImageDescriptor();
+       if (!SubmitFrame(&desc, aLeftEyeRect, aRightEyeRect)) {
+         return;
+       }
+       break;
+    }
 #endif
     default: {
       NS_WARNING("Unsupported SurfaceDescriptor type for VR layer texture");
       return;
     }
   }
 
-#if defined(XP_WIN) || defined(XP_MACOSX)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_ANDROID_GOOGLE_VR)
+
   /**
    * Trigger the next VSync immediately after we are successfully
    * submitting frames.  As SubmitFrame is responsible for throttling
    * the render loop, if we don't successfully call it, we shouldn't trigger
    * NotifyVRVsync immediately, as it will run unbounded.
    * If NotifyVRVsync is not called here due to SubmitFrame failing, the
    * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause
    * frames to continue at a lower refresh rate until frame submission
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -80,16 +80,20 @@ protected:
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) = 0;
 #elif defined(XP_MACOSX)
   virtual bool SubmitFrame(MacIOSurface* aMacIOSurface,
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) = 0;
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+  virtual bool SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) = 0;
 #endif
 
   VRDisplayInfo mDisplayInfo;
 
   nsTArray<RefPtr<VRLayerParent>> mLayers;
   // Weak reference to mLayers entries are cleared in
   // VRLayerParent destructor
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -20,16 +20,20 @@
 #include "gfxVR.h"
 #if defined(XP_WIN)
 #include "gfxVROculus.h"
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
 #include "gfxVROpenVR.h"
 #include "gfxVROSVR.h"
 #endif
+#if defined(MOZ_ANDROID_GOOGLE_VR)
+#include "gfxVRGVR.h"
+#endif // MOZ_ANDROID_GOOGLE_VR
+
 #include "gfxVRPuppet.h"
 #include "ipc/VRLayerParent.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
@@ -88,16 +92,24 @@ VRManager::VRManager()
   }
 
   // OSVR is cross platform compatible
   mgr = VRSystemManagerOSVR::Create();
   if (mgr) {
       mManagers.AppendElement(mgr);
   }
 #endif
+
+#if defined(MOZ_ANDROID_GOOGLE_VR)
+   mgr = VRSystemManagerGVR::Create();
+   if (mgr) {
+     mManagers.AppendElement(mgr);
+   }
+#endif // defined(MOZ_ANDROID_GOOGLE_VR)
+
   // 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
@@ -30,16 +30,17 @@ namespace gfx {
 class VRLayerParent;
 class VRDisplayHost;
 class VRControllerHost;
 
 enum class VRDeviceType : uint16_t {
   Oculus,
   OpenVR,
   OSVR,
+  GVR,
   Puppet,
   NumVRDeviceTypes
 };
 
 enum class VRDisplayCapabilityFlags : uint16_t {
   Cap_None = 0,
   /**
    * Cap_Position is set if the VRDisplay is capable of tracking its position.
@@ -216,16 +217,17 @@ struct VRDisplayInfo
   IntSize mEyeResolution;
   bool mIsConnected;
   bool mIsMounted;
   uint32_t mPresentingGroups;
   uint32_t mGroupMask;
   Size mStageSize;
   Matrix4x4 mSittingToStandingTransform;
   uint64_t mFrameId;
+  uint32_t mPresentingGeneration;
   VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
 
   bool operator==(const VRDisplayInfo& other) const {
     for (size_t i = 0; i < kVRMaxLatencyFrames; i++) {
       if (mLastSensorState[i] != other.mLastSensorState[i]) {
         return false;
       }
     }
@@ -239,17 +241,18 @@ struct VRDisplayInfo
            mPresentingGroups == other.mPresentingGroups &&
            mGroupMask == other.mGroupMask &&
            mEyeFOV[0] == other.mEyeFOV[0] &&
            mEyeFOV[1] == other.mEyeFOV[1] &&
            mEyeTranslation[0] == other.mEyeTranslation[0] &&
            mEyeTranslation[1] == other.mEyeTranslation[1] &&
            mStageSize == other.mStageSize &&
            mSittingToStandingTransform == other.mSittingToStandingTransform &&
-           mFrameId == other.mFrameId;
+           mFrameId == other.mFrameId &&
+           mPresentingGeneration == other.mPresentingGeneration;
   }
 
   bool operator!=(const VRDisplayInfo& other) const {
     return !(*this == other);
   }
 
   const VRHMDSensorState& GetSensorState() const
   {
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRGVR.cpp
@@ -0,0 +1,799 @@
+/* -*- 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 "GLBlitHelper.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "GLContextTypes.h"
+#include "GLImages.h"
+#include "GLLibraryEGL.h"
+
+#include "gfxPrefs.h"
+#include "gfxVRGVRAPI.h"
+#include "gfxVRGVR.h"
+
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Quaternion.h"
+#include "mozilla/jni/Utils.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/TextureHostOGL.h"
+#include "mozilla/Preferences.h"
+
+#include "GeckoVRManager.h"
+#include "nsString.h"
+
+#include "SurfaceTypes.h"
+
+#include "VRManager.h"
+
+#define MOZ_CHECK_GVR_ERRORS
+
+#if defined(MOZ_CHECK_GVR_ERRORS)
+#define GVR_LOGTAG "GeckoWebVR"
+#include <android/log.h>
+#define GVR_CHECK(X) X; \
+{ \
+  gvr_context* context = (mPresentingContext ? mPresentingContext : GetNonPresentingContext()); \
+  if (context && (gvr_get_error(context) != GVR_ERROR_NONE)) { \
+     __android_log_print(ANDROID_LOG_ERROR, GVR_LOGTAG, \
+                         "GVR ERROR: %s at%s:%s:%d", \
+                         gvr_get_error_string(gvr_get_error(context)), \
+                         __FILE__, __FUNCTION__, __LINE__); \
+    gvr_clear_error(context); \
+  } else if (!context) { \
+    __android_log_print(ANDROID_LOG_ERROR, GVR_LOGTAG, \
+                        "UNABLE TO CHECK GVR ERROR: NO CONTEXT"); \
+  } \
+}
+#define GVR_LOG(format, ...) __android_log_print(ANDROID_LOG_INFO, GVR_LOGTAG, format, ##__VA_ARGS__);
+#else
+#define GVR_CHECK(X) X
+#define GVR_LOG(...)
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gl;
+using namespace mozilla::gfx;
+using namespace mozilla::gfx::impl;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+
+namespace {
+static VRDisplayGVR* sContextObserver;
+static RefPtr<GLContextEGL> sGLContextEGL;
+static gvr_context* sNonPresentingContext;
+
+gvr_context*
+GetNonPresentingContext() {
+  if (!sNonPresentingContext) {
+    // Try and restore if it has been lost
+    sNonPresentingContext = (gvr_context*)GeckoVRManager::CreateGVRNonPresentingContext();
+  }
+  return sNonPresentingContext;
+}
+
+class SynchronousRunnable : public nsIRunnable {
+public:
+  enum class Type {
+    PresentingContext,
+    NonPresentingContext,
+    Pause,
+    Resume
+  };
+  SynchronousRunnable(const Type aType, void* aContext)
+  : mType(aType)
+  , mContext(aContext)
+  , mUpdateMonitor(new Monitor("SynchronousRunnable_for_Android"))
+  , mUpdated(false)
+  {}
+  NS_DECL_THREADSAFE_ISUPPORTS
+  nsresult Run() override
+  {
+    MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+    MonitorAutoLock lock(*mUpdateMonitor);
+    if (mType == Type::PresentingContext) {
+      SetGVRPresentingContext(mContext);
+    } else if (mType == Type::NonPresentingContext) {
+      CleanupGVRNonPresentingContext();
+    } else if (mType == Type::Pause) {
+      SetGVRPaused(true);
+    } else if (mType == Type::Resume) {
+      SetGVRPaused(false);
+    } else {
+      GVR_LOG("UNKNOWN SynchronousRunnable::Type!");
+    }
+    mUpdated = true;
+    lock.NotifyAll();
+    return NS_OK;
+  }
+  void Wait()
+  {
+    MonitorAutoLock lock(*mUpdateMonitor);
+    while(!mUpdated) {
+      lock.Wait();
+    }
+  }
+
+  static bool Dispatch(const Type aType, void* aContext)
+  {
+    if (!CompositorThreadHolder::IsInCompositorThread()) {
+      RefPtr<SynchronousRunnable> runnable = new SynchronousRunnable(aType, aContext);
+      CompositorThreadHolder::Loop()->PostTask(do_AddRef(runnable));
+      runnable->Wait();
+      return true;
+    }
+
+    return false;
+  }
+
+protected:
+  virtual ~SynchronousRunnable()
+  {
+    delete mUpdateMonitor;
+  }
+
+  Type mType;
+  void* mContext;
+  Monitor* mUpdateMonitor;
+  bool mUpdated;
+};
+
+}
+
+NS_IMPL_ISUPPORTS(SynchronousRunnable, nsIRunnable)
+
+void
+mozilla::gfx::SetGVRPresentingContext(void* aGVRPresentingContext)
+{
+  if (SynchronousRunnable::Dispatch(SynchronousRunnable::Type::PresentingContext, aGVRPresentingContext)) {
+    GVR_LOG("Done waiting for compositor thread to set presenting context.");
+    return;
+  }
+
+  MOZ_ASSERT(sContextObserver);
+  if (!sGLContextEGL && aGVRPresentingContext) {
+    CreateContextFlags flags = CreateContextFlags::NONE;
+    SurfaceCaps caps = SurfaceCaps::ForRGBA();
+    nsCString str;
+    sGLContextEGL = GLContextEGL::CreateEGLPBufferOffscreenContext(flags, IntSize(4, 4), caps, &str);
+    if (!sGLContextEGL->MakeCurrent()) {
+      GVR_LOG("Failed to make GL context current");
+    }
+  }
+  sContextObserver->SetPresentingContext(aGVRPresentingContext);
+}
+
+void
+mozilla::gfx::CleanupGVRNonPresentingContext()
+{
+  if (SynchronousRunnable::Dispatch(SynchronousRunnable::Type::NonPresentingContext, nullptr)) {
+    GVR_LOG("Done waiting for compositor thread to set non presenting context.");
+    return;
+  }
+
+  if (sNonPresentingContext) {
+    sNonPresentingContext = nullptr;
+    GeckoVRManager::DestroyGVRNonPresentingContext();
+  }
+}
+
+void
+mozilla::gfx::SetGVRPaused(const bool aPaused)
+{
+  if (SynchronousRunnable::Dispatch((aPaused ? SynchronousRunnable::Type::Pause : SynchronousRunnable::Type::Resume), nullptr)) {
+    GVR_LOG("Done waiting for GVR in compositor to: %s",(aPaused ? "Pause" : "Resume"));
+    return;
+  }
+  MOZ_ASSERT(sContextObserver);
+  sContextObserver->SetPaused(aPaused);
+}
+
+VRDisplayGVR::VRDisplayGVR()
+  : VRDisplayHost(VRDeviceType::GVR)
+  , mIsPresenting(false)
+  , mControllerAdded(false)
+  , mPresentingContext(nullptr)
+  , mControllerContext(nullptr)
+  , mControllerState(nullptr)
+  , mViewportList(nullptr)
+  , mLeftViewport(nullptr)
+  , mRightViewport(nullptr)
+  , mSwapChain(nullptr)
+  , mFrameBufferSize{0, 0}
+{
+  MOZ_COUNT_CTOR_INHERITED(VRDisplayGVR, VRDisplayHost);
+  MOZ_ASSERT(GetNonPresentingContext());
+  MOZ_ASSERT(!sContextObserver); // There can be only one GVR display at a time.
+  sContextObserver = this;
+
+  mDisplayInfo.mDisplayName.AssignLiteral("GVR HMD");
+  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mIsMounted = true;
+  mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                                  VRDisplayCapabilityFlags::Cap_Orientation |
+                                  VRDisplayCapabilityFlags::Cap_Position | // Not yet...
+                                  VRDisplayCapabilityFlags::Cap_Present;
+
+  GVR_CHECK(gvr_refresh_viewer_profile(GetNonPresentingContext()));
+  mViewportList = GVR_CHECK(gvr_buffer_viewport_list_create(GetNonPresentingContext()));
+  mLeftViewport = GVR_CHECK(gvr_buffer_viewport_create(GetNonPresentingContext()));
+  mRightViewport = GVR_CHECK(gvr_buffer_viewport_create(GetNonPresentingContext()));
+  UpdateViewport();
+
+  dom::GamepadHand hand = dom::GamepadHand::Right;
+  const gvr_user_prefs* prefs = GVR_CHECK(gvr_get_user_prefs(GetNonPresentingContext()));
+  if (prefs) {
+    hand = ((gvr_user_prefs_get_controller_handedness(prefs) == GVR_CONTROLLER_RIGHT_HANDED) ?
+             dom::GamepadHand::Right : dom::GamepadHand::Left);
+  }
+  mController = new VRControllerGVR(hand, mDisplayInfo.mDisplayID);
+}
+
+VRDisplayGVR::~VRDisplayGVR()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRDisplayGVR, VRDisplayHost);
+}
+
+void
+VRDisplayGVR::ZeroSensor()
+{
+}
+
+void
+VRDisplayGVR::StartPresentation()
+{
+  if (mIsPresenting) {
+    return;
+  }
+
+  mIsPresenting = true;
+  GeckoVRManager::EnableVRMode();
+}
+
+void
+VRDisplayGVR::StopPresentation()
+{
+  if (!mIsPresenting) {
+    return;
+  }
+
+  mIsPresenting = false;
+  GeckoVRManager::DisableVRMode();
+}
+
+bool
+VRDisplayGVR::SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
+                          const gfx::Rect& aLeftEyeRect,
+                          const gfx::Rect& aRightEyeRect)
+{
+  if (!mPresentingContext) {
+    GVR_LOG("Unable to submit frame. No presenting context")
+    return false;
+  }
+  if (!sGLContextEGL) {
+    GVR_LOG("Unable to submit frame. No GL Context");
+    return false;
+  }
+
+  if (!sGLContextEGL->MakeCurrent()) {
+    GVR_LOG("Failed to make GL context current");
+    return false;
+  }
+
+  EGLImage image = (EGLImage)aDescriptor->image();
+  EGLSync sync = (EGLSync)aDescriptor->fence();
+  gfx::IntSize size = aDescriptor->size();
+  MOZ_ASSERT(mSwapChain);
+  GVR_CHECK(gvr_get_recommended_buffer_viewports(mPresentingContext, mViewportList));
+  if ((size.width != mFrameBufferSize.width) || (size.height != mFrameBufferSize.height)) {
+    mFrameBufferSize.width = size.width;
+    mFrameBufferSize.height = size.height;
+    GVR_CHECK(gvr_swap_chain_resize_buffer(mSwapChain, 0, mFrameBufferSize));
+    GVR_LOG("Resize Swap Chain %d,%d", mFrameBufferSize.width, mFrameBufferSize.height);
+  }
+  gvr_frame* frame = GVR_CHECK(gvr_swap_chain_acquire_frame(mSwapChain));
+  if (!frame) {
+    // Sometimes the swap chain seems to not initialized correctly so that
+    // frames can not be acquired. Recreating the swap chain seems to fix the
+    // issue.
+    GVR_LOG("Unable to acquire GVR frame. Recreating swap chain.");
+    RecreateSwapChain();
+    return false;
+  }
+  GVR_CHECK(gvr_frame_bind_buffer(frame, 0));
+
+  EGLint status = LOCAL_EGL_CONDITION_SATISFIED;
+
+  if (sync) {
+    MOZ_ASSERT(sEGLLibrary.IsExtensionSupported(GLLibraryEGL::KHR_fence_sync));
+    status = sEGLLibrary.fClientWaitSync(EGL_DISPLAY(), sync, 0, LOCAL_EGL_FOREVER);
+  }
+
+  if (status != LOCAL_EGL_CONDITION_SATISFIED) {
+    MOZ_ASSERT(status != 0,
+               "ClientWaitSync generated an error. Has sync already been destroyed?");
+    return false;
+  }
+
+  if (image) {
+    GLuint tex = 0;
+    sGLContextEGL->fGenTextures(1, &tex);
+
+    const ScopedSaveMultiTex saveTex(sGLContextEGL, 1, LOCAL_GL_TEXTURE_2D);
+    sGLContextEGL->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
+    sGLContextEGL->TexParams_SetClampNoMips();
+    sGLContextEGL->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, image);
+    sGLContextEGL->BlitHelper()->DrawBlitTextureToFramebuffer(tex, gfx::IntSize(size.width, size.height), gfx::IntSize(mFrameBufferSize.width,  mFrameBufferSize.height));
+    sGLContextEGL->fDeleteTextures(1, &tex);
+  } else {
+    GVR_LOG("Unable to submit frame. Unable to extract EGLImage");
+    return false;
+  }
+  GVR_CHECK(gvr_frame_unbind(frame));
+  GVR_CHECK(gvr_frame_submit(&frame, mViewportList, mHeadMatrix));
+  return true;
+}
+
+void
+VRDisplayGVR::NotifyVSync()
+{
+  VRDisplayHost::NotifyVSync();
+}
+
+static void
+FillMatrix(gfx::Matrix4x4 &target, const gvr_mat4f& source)
+{
+  target._11 = source.m[0][0];
+  target._12 = source.m[0][1];
+  target._13 = source.m[0][2];
+  target._14 = source.m[0][3];
+  target._21 = source.m[1][0];
+  target._22 = source.m[1][1];
+  target._23 = source.m[1][2];
+  target._24 = source.m[1][3];
+  target._31 = source.m[2][0];
+  target._32 = source.m[2][1];
+  target._33 = source.m[2][2];
+  target._34 = source.m[2][3];
+  target._41 = source.m[3][0];
+  target._42 = source.m[3][1];
+  target._43 = source.m[3][2];
+  target._44 = source.m[3][3];
+}
+
+VRHMDSensorState
+VRDisplayGVR::GetSensorState()
+{
+  VRHMDSensorState result{};
+
+  gvr_context* context = (mPresentingContext ? mPresentingContext : GetNonPresentingContext());
+
+  if (!context) {
+    GVR_LOG("Unable to get sensor state. Context is null");
+    return result;
+  }
+
+  gvr_clock_time_point when = GVR_CHECK(gvr_get_time_point_now());
+  if (mIsPresenting) {
+    // 50ms into the future is what GVR docs recommends using for head rotation
+    // prediction.
+    when.monotonic_system_time_nanos += 50000000;
+  }
+  mHeadMatrix = GVR_CHECK(gvr_get_head_space_from_start_space_rotation(context, when));
+  gvr_mat4f neck = GVR_CHECK(gvr_apply_neck_model(context, mHeadMatrix, 1.0));;
+
+  gfx::Matrix4x4 m;
+
+  FillMatrix(m, neck);
+  m.Invert();
+  gfx::Quaternion rot;
+  rot.SetFromRotationMatrix(m);
+
+  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] = m._14;
+  result.position[1] = m._24;
+  result.position[2] = m._34;
+  result.linearVelocity[0] = 0.0f;
+  result.linearVelocity[1] = 0.0f;
+  result.linearVelocity[2] = 0.0f;
+
+  UpdateHeadToEye(context, &rot);
+  result.CalcViewMatrices(mHeadToEyes);
+
+  return result;
+}
+
+void
+VRDisplayGVR::SetPaused(const bool aPaused)
+{
+  if (aPaused) {
+    if (mPresentingContext) {
+      GVR_CHECK(gvr_pause_tracking(mPresentingContext));
+    } else if (sNonPresentingContext) {
+      GVR_CHECK(gvr_pause_tracking(sNonPresentingContext));
+    }
+
+    if (mControllerContext) {
+      GVR_CHECK(gvr_controller_pause(mControllerContext));
+    }
+  } else {
+    if (mPresentingContext) {
+      GVR_CHECK(gvr_refresh_viewer_profile(mPresentingContext));
+      GVR_CHECK(gvr_resume_tracking(mPresentingContext));
+    } else if (sNonPresentingContext) {
+      GVR_CHECK(gvr_resume_tracking(sNonPresentingContext));
+    }
+
+    if (mControllerContext) {
+      GVR_CHECK(gvr_controller_resume(mControllerContext));
+    }
+  }
+}
+
+void
+VRDisplayGVR::SetPresentingContext(void* aGVRPresentingContext)
+{
+  MOZ_ASSERT(sGLContextEGL);
+  sGLContextEGL->MakeCurrent();
+  mPresentingContext = (gvr_context*)aGVRPresentingContext;
+  if (mPresentingContext) {
+    GVR_CHECK(gvr_initialize_gl(mPresentingContext));
+    RecreateSwapChain();
+  } else {
+
+    if (mSwapChain) {
+      // gvr_swap_chain_destroy will set the pointer to null
+      GVR_CHECK(gvr_swap_chain_destroy(&mSwapChain));
+      MOZ_ASSERT(!mSwapChain);
+    }
+
+    // The presentation context has been destroy, probably by the user so increment the presenting
+    // generation if we are presenting so that the DOM knows to end the current presentation.
+    if (mIsPresenting) {
+      mDisplayInfo.mPresentingGeneration++;
+    }
+  }
+}
+
+void
+VRDisplayGVR::UpdateHeadToEye(gvr_context* aContext, gfx::Quaternion* aRot)
+{
+  if (!aContext) {
+    return;
+  }
+
+  for (uint32_t eyeIndex = 0; eyeIndex < 2; eyeIndex++) {
+    gvr_mat4f eye = GVR_CHECK(gvr_get_eye_from_head_matrix(aContext, eyeIndex));
+    mDisplayInfo.mEyeTranslation[eyeIndex].x = -eye.m[0][3];
+    mDisplayInfo.mEyeTranslation[eyeIndex].y = -eye.m[1][3];
+    mDisplayInfo.mEyeTranslation[eyeIndex].z = -eye.m[2][3];
+    if (aRot) {
+      mHeadToEyes[eyeIndex].SetRotationFromQuaternion(*aRot);
+    } else {
+      mHeadToEyes[eyeIndex] = gfx::Matrix4x4();
+    }
+    mHeadToEyes[eyeIndex].PreTranslate(eye.m[0][3], eye.m[1][3], eye.m[2][3]);
+  }
+}
+
+void
+VRDisplayGVR::UpdateViewport()
+{
+  gvr_context* context = (mPresentingContext ? mPresentingContext : GetNonPresentingContext());
+
+  if (!context) {
+    return;
+  }
+
+  GVR_CHECK(gvr_get_recommended_buffer_viewports(context, mViewportList));
+  GVR_CHECK(gvr_buffer_viewport_list_get_item(mViewportList, 0, mLeftViewport));
+  GVR_CHECK(gvr_buffer_viewport_list_get_item(mViewportList, 1, mRightViewport));
+
+  gvr_rectf fov = GVR_CHECK(gvr_buffer_viewport_get_source_fov(mLeftViewport));
+  mDisplayInfo.mEyeFOV[VRDisplayInfo::Eye_Left] = VRFieldOfView(fov.top, fov.right, fov.bottom, fov.left);
+  GVR_LOG("FOV:L top:%f right:%f bottom:%f left:%f",(float)fov.top, (float)fov.left, (float)fov.bottom, (float)fov.right);
+
+  fov = GVR_CHECK(gvr_buffer_viewport_get_source_fov(mRightViewport));
+  mDisplayInfo.mEyeFOV[VRDisplayInfo::Eye_Right] = VRFieldOfView(fov.top, fov.right, fov.bottom, fov.left);
+  GVR_LOG("FOV:R top:%f right:%f bottom:%f left:%f",(float)fov.top, (float)fov.left, (float)fov.bottom, (float)fov.right);
+
+  gvr_sizei size = GVR_CHECK(gvr_get_maximum_effective_render_target_size(context));
+  mDisplayInfo.mEyeResolution = IntSize(size.width / 2, size.height);
+  GVR_LOG("Eye Resolution: %dx%d",mDisplayInfo.mEyeResolution.width,mDisplayInfo.mEyeResolution.height);
+
+  UpdateHeadToEye(context);
+}
+
+void
+VRDisplayGVR::RecreateSwapChain()
+{
+  MOZ_ASSERT(sGLContextEGL);
+  sGLContextEGL->MakeCurrent();
+  if (mSwapChain) {
+    // gvr_swap_chain_destroy will set the pointer to null
+    GVR_CHECK(gvr_swap_chain_destroy(&mSwapChain));
+    MOZ_ASSERT(!mSwapChain);
+  }
+  gvr_buffer_spec* spec = GVR_CHECK(gvr_buffer_spec_create(mPresentingContext));
+  mFrameBufferSize = GVR_CHECK(gvr_get_maximum_effective_render_target_size(mPresentingContext));
+  GVR_CHECK(gvr_buffer_spec_set_size(spec, mFrameBufferSize));
+  GVR_CHECK(gvr_buffer_spec_set_samples(spec, 0));
+  GVR_CHECK(gvr_buffer_spec_set_color_format(spec, GVR_COLOR_FORMAT_RGBA_8888));
+  GVR_CHECK(gvr_buffer_spec_set_depth_stencil_format(spec, GVR_DEPTH_STENCIL_FORMAT_NONE));
+  mSwapChain = GVR_CHECK(gvr_swap_chain_create(mPresentingContext, (const gvr_buffer_spec**)&spec, 1));
+  GVR_CHECK(gvr_buffer_spec_destroy(&spec));
+}
+
+void
+VRDisplayGVR::EnableControllers(const bool aEnable, VRSystemManager* aManager)
+{
+  if (aEnable && !mControllerAdded) {
+    // Sometimes the gamepad doesn't get removed cleanly so just try to remove it before adding it.
+    aManager->RemoveGamepad(mController->GetControllerInfo().mControllerID);
+    aManager->AddGamepad(mController->GetControllerInfo());
+    mControllerAdded = true;
+  } else if (!aEnable && mControllerAdded) {
+    mControllerAdded = false;
+    aManager->RemoveGamepad(mController->GetControllerInfo().mControllerID);
+  }
+
+  gvr_context* context = mPresentingContext;
+
+  if (!context) {
+    if (mControllerContext) {
+      GVR_CHECK(gvr_controller_destroy(&mControllerContext));
+    }
+    return;
+  }
+
+  if ((aEnable && mControllerContext) || (!aEnable && !mControllerContext)) {
+    return;
+  }
+
+  if (aEnable) {
+    if (!mControllerContext) {
+      int32_t options = GVR_CHECK(gvr_controller_get_default_options());
+      options |= GVR_CONTROLLER_ENABLE_GYRO | GVR_CONTROLLER_ENABLE_ACCEL | GVR_CONTROLLER_ENABLE_ARM_MODEL;
+      mControllerContext = GVR_CHECK(gvr_controller_create_and_init(options, context));
+      GVR_CHECK(gvr_controller_resume(mControllerContext));
+    }
+    if (!mControllerState) {
+      mControllerState = GVR_CHECK(gvr_controller_state_create());
+    }
+  } else {
+    GVR_CHECK(gvr_controller_pause(mControllerContext));
+    GVR_CHECK(gvr_controller_destroy(&mControllerContext));
+  }
+}
+
+void
+VRDisplayGVR::UpdateControllers(VRSystemManager* aManager)
+{
+  if (!mControllerContext) {
+    return;
+  }
+
+  GVR_CHECK(gvr_controller_apply_arm_model(mControllerContext, GVR_CONTROLLER_RIGHT_HANDED,GVR_ARM_MODEL_FOLLOW_GAZE, mHeadMatrix));
+  GVR_CHECK(gvr_controller_state_update(mControllerContext, 0, mControllerState));
+  mController->Update(mControllerState, aManager);
+}
+
+
+void
+VRDisplayGVR::GetControllers(nsTArray<RefPtr<VRControllerHost> >& aControllerResult)
+{
+  aControllerResult.AppendElement(mController.get());
+}
+
+VRControllerGVR::VRControllerGVR(dom::GamepadHand aHand, uint32_t aDisplayID)
+  : VRControllerHost(VRDeviceType::GVR, aHand, aDisplayID)
+{
+  MOZ_COUNT_CTOR_INHERITED(VRControllerGVR, VRControllerHost);
+  mControllerInfo.mControllerName.AssignLiteral("Daydream Controller");
+  // The gvr_controller_button enum starts with GVR_CONTROLLER_BUTTON_NONE at index zero
+  // so the GVR controller has one less button than GVR_CONTROLLER_BUTTON_COUNT specifies.
+  mControllerInfo.mNumButtons = GVR_CONTROLLER_BUTTON_COUNT - 1; // Skip dummy none button
+  mControllerInfo.mNumAxes = 2;
+  mControllerInfo.mNumHaptics = 0;
+}
+
+VRControllerGVR::~VRControllerGVR()
+{
+  MOZ_COUNT_DTOR_INHERITED(VRControllerGVR, VRControllerHost);
+}
+
+void
+VRControllerGVR::Update(gvr_controller_state* aState, VRSystemManager* aManager)
+{
+  mPose.Clear();
+
+  if (gvr_controller_state_get_connection_state(aState) != GVR_CONTROLLER_CONNECTED) {
+    return;
+  }
+  const uint64_t previousPressMask = GetButtonPressed();
+  const uint64_t previousTouchMask = GetButtonTouched();
+  uint64_t currentPressMask = 0;
+  uint64_t currentTouchMask = 0;
+  // Index 0 is the dummy button so skip it.
+  for (int ix = 1; ix < GVR_CONTROLLER_BUTTON_COUNT; ix++) {
+    const uint64_t buttonMask = 0x01 << (ix - 1);
+    bool pressed = gvr_controller_state_get_button_state(aState, ix);
+    bool touched = pressed;
+    if (ix == GVR_CONTROLLER_BUTTON_CLICK) {
+       touched = gvr_controller_state_is_touching(aState);
+       double xAxis = 0.0;
+       double yAxis = 0.0;
+       if (touched) {
+         gvr_vec2f axes = gvr_controller_state_get_touch_pos(aState);
+         xAxis = (axes.x * 2.0) - 1.0;
+         yAxis = (axes.y * 2.0) - 1.0;
+       }
+       aManager->NewAxisMove(0, 0, xAxis);
+       aManager->NewAxisMove(0, 1, yAxis);
+    }
+    if (pressed) {
+      currentPressMask |= buttonMask;
+    }
+    if (touched) {
+      currentTouchMask |= buttonMask;
+    }
+    if (((currentPressMask & buttonMask) ^ (previousPressMask & buttonMask)) ||
+        ((currentTouchMask & buttonMask) ^ (previousTouchMask & buttonMask))) {
+      aManager->NewButtonEvent(0, ix - 1, pressed, touched, pressed ? 1.0 : 0.0);
+    }
+  }
+  SetButtonPressed(currentPressMask);
+  SetButtonTouched(currentTouchMask);
+
+  mPose.flags = dom::GamepadCapabilityFlags::Cap_Orientation | dom::GamepadCapabilityFlags::Cap_Position | dom::GamepadCapabilityFlags::Cap_LinearAcceleration;
+
+  gvr_quatf ori = gvr_controller_state_get_orientation(aState);
+  mPose.orientation[0] = ori.qx;
+  mPose.orientation[1] = ori.qy;
+  mPose.orientation[2] = ori.qz;
+  mPose.orientation[3] = ori.qw;
+  mPose.isOrientationValid = true;
+
+  gvr_vec3f acc = gvr_controller_state_get_accel(aState);
+  mPose.linearAcceleration[0] = acc.x;
+  mPose.linearAcceleration[1] = acc.y;
+  mPose.linearAcceleration[2] = acc.z;
+
+  gvr_vec3f vel = gvr_controller_state_get_gyro(aState);
+  mPose.angularVelocity[0] = vel.x;
+  mPose.angularVelocity[1] = vel.y;
+  mPose.angularVelocity[2] = vel.z;
+
+  gvr_vec3f pos =  gvr_controller_state_get_position(aState);
+  mPose.position[0] = pos.x;
+  mPose.position[1] = pos.y;
+  mPose.position[2] = pos.z;
+
+  aManager->NewPoseState(0, mPose);
+}
+
+/*static*/ already_AddRefed<VRSystemManagerGVR>
+VRSystemManagerGVR::Create()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gfxPrefs::VREnabled()) {
+    return nullptr;
+  }
+
+  RefPtr<VRSystemManagerGVR> manager = new VRSystemManagerGVR();
+  return manager.forget();
+}
+
+void
+VRSystemManagerGVR::Destroy()
+{
+
+}
+
+void
+VRSystemManagerGVR::Shutdown()
+{
+
+}
+
+bool
+VRSystemManagerGVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult)
+{
+  if (!GeckoVRManager::IsGVRPresent()) {
+    return false;
+  }
+
+  if (!mGVRHMD) {
+    mGVRHMD = new VRDisplayGVR();
+  }
+
+  aHMDResult.AppendElement(mGVRHMD);
+  return true;
+}
+
+bool
+VRSystemManagerGVR::GetIsPresenting()
+{
+  if (!mGVRHMD) {
+    return false;
+  }
+
+  VRDisplayInfo displayInfo(mGVRHMD->GetDisplayInfo());
+  return displayInfo.GetPresentingGroups() != kVRGroupNone;
+}
+
+void
+VRSystemManagerGVR::HandleInput()
+{
+  if (mGVRHMD) {
+    mGVRHMD->UpdateControllers(this);
+  }
+}
+
+void
+VRSystemManagerGVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
+{
+  if (mGVRHMD) {
+    mGVRHMD->GetControllers(aControllerResult);
+  }
+}
+
+void
+VRSystemManagerGVR::ScanForControllers()
+{
+  if (mGVRHMD) {
+    mGVRHMD->EnableControllers(true, this);
+  }
+}
+
+void
+VRSystemManagerGVR::RemoveControllers()
+{
+  if (mGVRHMD) {
+    mGVRHMD->EnableControllers(false, this);
+  }
+}
+
+void
+VRSystemManagerGVR::VibrateHaptic(uint32_t aControllerIdx,
+                                  uint32_t aHapticIndex,
+                                  double aIntensity,
+                                  double aDuration,
+                                  uint32_t aPromiseID)
+{
+
+}
+
+void
+VRSystemManagerGVR::StopVibrateHaptic(uint32_t aControllerIdx)
+{
+
+}
+
+VRSystemManagerGVR::VRSystemManagerGVR()
+  : mGVRHMD(nullptr)
+{
+
+}
+
+VRSystemManagerGVR::~VRSystemManagerGVR()
+{
+
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRGVR.h
@@ -0,0 +1,134 @@
+/* -*- 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_GVR_H
+#define GFX_VR_GVR_H
+
+#include "gfxVR.h"
+
+#include <memory>
+
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsTArray.h"
+#include "nsIRunnable.h"
+#include "nsIScreen.h"
+#include "nsCOMPtr.h"
+
+#include "VRDisplayHost.h"
+
+#pragma GCC system_header
+#pragma GCC visibility push(default)
+#include "vr/gvr/capi/include/gvr.h"
+#include "vr/gvr/capi/include/gvr_controller.h"
+#pragma GCC visibility pop
+
+
+namespace mozilla {
+namespace gl {
+class GLContextEGL;
+} // namespace gl
+namespace layers {
+class EGLImageDescriptor;
+} // namespace layers
+namespace gfx {
+namespace impl {
+
+class VRControllerGVR : public VRControllerHost
+{
+public:
+  explicit VRControllerGVR(dom::GamepadHand aHand, uint32_t aDisplayID);
+  virtual ~VRControllerGVR();
+  void Update(gvr_controller_state* aState, VRSystemManager* aManager);
+};
+
+class VRDisplayGVR : public VRDisplayHost
+{
+public:
+  VRDisplayGVR();
+
+  // BEGIN VRDisplayHost interface
+  void ZeroSensor() override;
+  void StartPresentation() override;
+  void StopPresentation() override;
+  bool SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
+                   const gfx::Rect& aLeftEyeRect,
+                   const gfx::Rect& aRightEyeRect) override;
+  void NotifyVSync() override;
+protected:
+  virtual VRHMDSensorState GetSensorState() override;
+  // END VRDisplayHost interface
+
+public:
+  void SetPaused(const bool aPaused);
+  void SetPresentingContext(void* aGVRPresentingContext);
+  void EnableControllers(const bool aEnable, VRSystemManager* aManager);
+  void UpdateControllers(VRSystemManager* aManager);
+  void GetControllers(nsTArray<RefPtr<VRControllerHost> >& aControllerResult);
+
+protected:
+  virtual ~VRDisplayGVR();
+  void UpdateHeadToEye(gvr_context* aContext, gfx::Quaternion* aRot = nullptr);
+  void UpdateViewport();
+  void RecreateSwapChain();
+
+  bool mIsPresenting;
+  bool mControllerAdded;
+
+  gfx::Matrix4x4 mHeadToEyes[2];
+  gvr_context* mPresentingContext;
+  gvr_controller_context* mControllerContext;
+  gvr_controller_state* mControllerState;
+  gvr_buffer_viewport_list* mViewportList;
+  gvr_buffer_viewport* mLeftViewport;
+  gvr_buffer_viewport* mRightViewport;
+  gvr_mat4f mHeadMatrix;
+  gvr_swap_chain* mSwapChain;
+  gvr_sizei mFrameBufferSize;
+
+  RefPtr<VRControllerGVR> mController;
+};
+
+
+} // namespace impl
+
+class VRSystemManagerGVR : public VRSystemManager
+{
+public:
+  static already_AddRefed<VRSystemManagerGVR> Create();
+
+  void Destroy() override;
+  void Shutdown() override;
+  bool GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
+  bool GetIsPresenting() override;
+  void HandleInput() override;
+  void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
+                      aControllerResult) override;
+  void ScanForControllers() override;
+  void RemoveControllers() override;
+  void VibrateHaptic(uint32_t aControllerIdx,
+                     uint32_t aHapticIndex,
+                     double aIntensity,
+                     double aDuration,
+                     uint32_t aPromiseID) override;
+  void StopVibrateHaptic(uint32_t aControllerIdx) override;
+
+protected:
+  VRSystemManagerGVR();
+  virtual ~VRSystemManagerGVR();
+
+private:
+  RefPtr<impl::VRDisplayGVR> mGVRHMD;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+
+#endif /* GFX_VR_GVR_H */
new file mode 100644
--- /dev/null
+++ b/gfx/vr/gfxVRGVRAPI.h
@@ -0,0 +1,17 @@
+/* -*- 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_GVR_API_H
+#define GFX_VR_GVR_API_H
+namespace mozilla {
+namespace gfx {
+
+void SetGVRPresentingContext(void* aGVRPresentingContext);
+void CleanupGVRNonPresentingContext();
+void SetGVRPaused(const bool aPaused);
+
+} // namespace gfx
+} // namespace mozilla
+#endif // GFX_VR_GVR_API_H
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -352,16 +352,27 @@ VRDisplayOSVR::SubmitFrame(MacIOSurface*
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect)
 {
   // XXX Add code to submit frame
   return false;
 }
 
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+
+bool
+VRDisplayOSVR::SubmitFrame(const mozilla::layers::EGLImageDescriptor*,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect)
+{
+  // XXX Add code to submit frame
+  return false;
+}
+
 #endif
 
 void
 VRDisplayOSVR::StartPresentation()
 {
   // XXX Add code to start VR Presentation
 }
 
--- a/gfx/vr/gfxVROSVR.h
+++ b/gfx/vr/gfxVROSVR.h
@@ -41,16 +41,20 @@ protected:
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #elif defined(XP_MACOSX)
   virtual bool SubmitFrame(MacIOSurface* aMacIOSurface,
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+  virtual bool SubmitFrame(const mozilla::layers::EGLImageDescriptor*,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayOSVR(OSVR_ClientContext* context,
                          OSVR_ClientInterface* iface,
                          OSVR_DisplayConfig* display);
 
 protected:
@@ -119,9 +123,9 @@ private:
   void InitializeClientContext();
   void InitializeDisplay();
   void InitializeInterface();
 };
 
 } // namespace gfx
 } // namespace mozilla
 
-#endif /* GFX_VR_OSVR_H */
\ No newline at end of file
+#endif /* GFX_VR_OSVR_H */
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -545,16 +545,26 @@ VRDisplayPuppet::SubmitFrame(MacIOSurfac
       MOZ_ASSERT(false, "No support for showing VR frames on MacOSX yet.");
       break;
     }
   }
 
   return false;
 }
 
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+
+bool
+VRDisplayPuppet::SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) {
+
+  return false;
+}
+
 #endif
 
 void
 VRDisplayPuppet::NotifyVSync()
 {
   // We update mIsConneced once per frame.
   mDisplayInfo.mIsConnected = true;
 
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -37,16 +37,20 @@ protected:
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #elif defined(XP_MACOSX)
   virtual bool SubmitFrame(MacIOSurface* aMacIOSurface,
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
+#elif defined(MOZ_ANDROID_GOOGLE_VR)
+  virtual bool SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayPuppet();
 
 protected:
   virtual ~VRDisplayPuppet();
   void Destroy();
@@ -138,9 +142,9 @@ private:
   // there can only be one
   RefPtr<impl::VRDisplayPuppet> mPuppetHMD;
   nsTArray<RefPtr<impl::VRControllerPuppet>> mPuppetController;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
-#endif  /* GFX_VR_PUPPET_H*/
\ No newline at end of file
+#endif  /* GFX_VR_PUPPET_H*/
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -40,16 +40,17 @@ struct ParamTraits<mozilla::gfx::VRDispl
     WriteParam(aMsg, aParam.mEyeResolution);
     WriteParam(aMsg, aParam.mIsConnected);
     WriteParam(aMsg, aParam.mIsMounted);
     WriteParam(aMsg, aParam.mPresentingGroups);
     WriteParam(aMsg, aParam.mGroupMask);
     WriteParam(aMsg, aParam.mStageSize);
     WriteParam(aMsg, aParam.mSittingToStandingTransform);
     WriteParam(aMsg, aParam.mFrameId);
+    WriteParam(aMsg, aParam.mPresentingGeneration);
     for (int i = 0; i < mozilla::gfx::VRDisplayInfo::NumEyes; i++) {
       WriteParam(aMsg, aParam.mEyeFOV[i]);
       WriteParam(aMsg, aParam.mEyeTranslation[i]);
     }
     for (int i = 0; i < mozilla::gfx::kVRMaxLatencyFrames; i++) {
       WriteParam(aMsg, aParam.mLastSensorState[i]);
     }
   }
@@ -62,17 +63,18 @@ struct ParamTraits<mozilla::gfx::VRDispl
         !ReadParam(aMsg, aIter, &(aResult->mCapabilityFlags)) ||
         !ReadParam(aMsg, aIter, &(aResult->mEyeResolution)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsConnected)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsMounted)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPresentingGroups)) ||
         !ReadParam(aMsg, aIter, &(aResult->mGroupMask)) ||
         !ReadParam(aMsg, aIter, &(aResult->mStageSize)) ||
         !ReadParam(aMsg, aIter, &(aResult->mSittingToStandingTransform)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFrameId))) {
+        !ReadParam(aMsg, aIter, &(aResult->mFrameId)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mPresentingGeneration))) {
       return false;
     }
     for (int i = 0; i < mozilla::gfx::VRDisplayInfo::NumEyes; i++) {
       if (!ReadParam(aMsg, aIter, &(aResult->mEyeFOV[i])) ||
           !ReadParam(aMsg, aIter, &(aResult->mEyeTranslation[i]))) {
         return false;
       }
     }
new file mode 100644
--- /dev/null
+++ b/gfx/vr/jni/gfxGVRJNI.cpp
@@ -0,0 +1,104 @@
+/* -*- 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 <jni.h>
+
+extern "C" __attribute__((visibility("default"))) jlong
+Java_com_google_vr_cardboard_DisplaySynchronizer_nativeCreate(
+    JNIEnv* env,
+    jobject jcaller,
+    jclass classLoader,
+    jobject appContext);
+
+// Step 2: method stubs.
+extern "C" __attribute__((visibility("default"))) void
+Java_com_google_vr_cardboard_DisplaySynchronizer_nativeDestroy(
+    JNIEnv* env,
+    jobject jcaller,
+    jlong nativeDisplaySynchronizer);
+
+extern "C" __attribute__((visibility("default"))) void
+Java_com_google_vr_cardboard_DisplaySynchronizer_nativeReset(
+    JNIEnv* env,
+    jobject jcaller,
+    jlong nativeDisplaySynchronizer,
+    jlong expectedInterval,
+    jlong vsyncOffset);
+
+extern "C" __attribute__((visibility("default"))) void
+Java_com_google_vr_cardboard_DisplaySynchronizer_nativeUpdate(
+    JNIEnv* env,
+    jobject jcaller,
+    jlong nativeDisplaySynchronizer,
+    jlong syncTime,
+    jint currentRotation);
+
+namespace {
+
+bool
+check(JNIEnv* env) {
+  if (env->ExceptionCheck()) {
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return false;
+  }
+  return true;
+}
+
+const char kDisplaySynchronizerClassPath[] = "com/google/vr/cardboard/DisplaySynchronizer";
+
+static const JNINativeMethod kMethodsDisplaySynchronizer[] = {
+    {"nativeCreate",
+     "("
+     "Ljava/lang/ClassLoader;"
+     "Landroid/content/Context;"
+     ")"
+     "J",
+     reinterpret_cast<void*>(
+         Java_com_google_vr_cardboard_DisplaySynchronizer_nativeCreate)},
+    {"nativeDestroy",
+     "("
+     "J"
+     ")"
+     "V",
+     reinterpret_cast<void*>(
+         Java_com_google_vr_cardboard_DisplaySynchronizer_nativeDestroy)},
+    {"nativeReset",
+     "("
+     "J"
+     "J"
+     "J"
+     ")"
+     "V",
+     reinterpret_cast<void*>(
+         Java_com_google_vr_cardboard_DisplaySynchronizer_nativeReset)},
+    {"nativeUpdate",
+     "("
+     "J"
+     "J"
+     "I"
+     ")"
+     "V",
+     reinterpret_cast<void*>(
+         Java_com_google_vr_cardboard_DisplaySynchronizer_nativeUpdate)},
+};
+}
+
+bool
+SetupGVRJNI(JNIEnv* env)
+{
+  jclass displaySynchronizerClazz = env->FindClass(kDisplaySynchronizerClassPath);
+  if (!check(env)) { return false; }
+  if (displaySynchronizerClazz == nullptr) {
+    return false;
+  }
+  env->RegisterNatives(displaySynchronizerClazz, kMethodsDisplaySynchronizer, sizeof(kMethodsDisplaySynchronizer) / sizeof(kMethodsDisplaySynchronizer[0]));
+  if (!check(env)) { return false; }
+  env->DeleteLocalRef(displaySynchronizerClazz);
+  if (!check(env)) { return false; }
+
+  return true;
+}
+
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -52,16 +52,24 @@ if CONFIG['OS_TARGET'] in ('WINNT', 'Lin
         'gfxVROpenVR.cpp',
     ]
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     SOURCES += [
         'gfxVROculus.cpp',
     ]
 
+if CONFIG['MOZ_ANDROID_GOOGLE_VR']:
+    SOURCES += [
+        'gfxVRGVR.cpp',
+        'jni/gfxGVRJNI.cpp',
+    ]
+    CXXFLAGS += ['-I%s' % CONFIG['MOZ_ANDROID_GOOGLE_VR_INCLUDE']]
+    LOCAL_INCLUDES += ['/widget/android']
+
 IPDL_SOURCES = [
     'ipc/PVRLayer.ipdl',
     'ipc/PVRManager.ipdl',
 ]
 
 # For building with the real SDK instead of our local hack
 #SOURCES += [
 #    'OVR_CAPI_Util.cpp',