Bug 1413362 - part 2: Gecko gfx/vr GVR WebVR implementation r=kip,daoshengmu draft
authorRandall Barker <rbarker@mozilla.com>
Tue, 31 Oct 2017 17:25:40 -0700
changeset 693025 11ffa2271383d63b367ebf734fc3117d8535999d
parent 693024 8faeae89d6d126f435d94fc6f99a5c0458d2f7c8
child 693026 c7da5c36058c5c54938a7920903d9e96425be2b6
push id87675
push userbmo:rbarker@mozilla.com
push dateFri, 03 Nov 2017 21:24:57 +0000
reviewerskip, daoshengmu
bugs1413362
milestone58.0a1
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,793 @@
+/* -*- 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);
+  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)
+{
+  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];
+    FillMatrix(mHeadToEyes[eyeIndex], eye);
+  }
+}
+
+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.right, (float)fov.bottom, (float)fov.left);
+
+  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.right, (float)fov.bottom, (float)fov.left);
+
+  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);
+  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',