Blob Blame History Raw
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <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;
};

}  // namespace

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;
}

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];
    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, 0,
      mController->GetHand() == dom::GamepadHand::Right
          ? GVR_CONTROLLER_RIGHT_HANDED
          : GVR_CONTROLLER_LEFT_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() {}

void VRSystemManagerGVR::Enumerate() {
  if (!GeckoVRManager::IsGVRPresent()) {
    return;
  }

  if (!mGVRHMD) {
    mGVRHMD = new VRDisplayGVR();
  }
}

bool VRSystemManagerGVR::ShouldInhibitEnumeration() {
  if (VRSystemManager::ShouldInhibitEnumeration()) {
    return true;
  }
  if (mGVRHMD) {
    // When we find an a VR device, don't
    // allow any further enumeration as it
    // may get picked up redundantly by other
    // API's.
    return true;
  }
  return false;
}

void VRSystemManagerGVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) {
  if (mGVRHMD) {
    aHMDResult.AppendElement(mGVRHMD);
  }
}

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,
                                       const VRManagerPromise& aPromise) {}

void VRSystemManagerGVR::StopVibrateHaptic(uint32_t aControllerIdx) {}

VRSystemManagerGVR::VRSystemManagerGVR() : mGVRHMD(nullptr) {}

VRSystemManagerGVR::~VRSystemManagerGVR() {}