Bug 1306486 - Ensure that VRDisplay.getPose and VRDisplay.getFrameData return the same values until the next VRDisplay.submitFrame draft
authorkearwood
Thu, 06 Oct 2016 16:32:16 -0700
changeset 424918 437a34a5798452ddef7648d8786ac0d249eec677
parent 424914 7452437b3ab571b1d60aed4e973d82a1471f72b2
child 533795 9ed65d709f31102b6cc67df880034f040f124e88
push id32289
push userkgilbert@mozilla.com
push dateThu, 13 Oct 2016 19:56:01 +0000
bugs1306486
milestone52.0a1
Bug 1306486 - Ensure that VRDisplay.getPose and VRDisplay.getFrameData return the same values until the next VRDisplay.submitFrame MozReview-Commit-ID: BrShhMhT87a
dom/vr/VRDisplay.cpp
dom/vr/VRDisplay.h
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -499,30 +499,51 @@ VRDisplay::Capabilities()
 }
 
 VRStageParameters*
 VRDisplay::GetStageParameters()
 {
   return mStageParameters;
 }
 
+void
+VRDisplay::UpdateFrameInfo()
+{
+  /**
+   * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData
+   * must return the same values until the next VRDisplay.submitFrame.
+   *
+   * mFrameInfo is marked dirty at the end of the frame or start of a new
+   * composition and lazily created here in order to receive mid-frame
+   * pose-prediction updates while still ensuring conformance to the WebVR spec
+   * requirements.
+   *
+   * If we are not presenting WebVR content, the frame will never end and we should
+   * return the latest frame data always.
+   */
+  if (mFrameInfo.IsDirty() || !mPresentation) {
+    gfx::VRHMDSensorState state = mClient->GetSensorState();
+    const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
+    mFrameInfo.Update(info, state, mDepthNear, mDepthFar);
+  }
+}
+
 bool
 VRDisplay::GetFrameData(VRFrameData& aFrameData)
 {
-  gfx::VRHMDSensorState state = mClient->GetSensorState();
-  const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
-  aFrameData.Update(info, state, mDepthNear, mDepthFar);
+  UpdateFrameInfo();
+  aFrameData.Update(mFrameInfo);
   return true;
 }
 
 already_AddRefed<VRPose>
 VRDisplay::GetPose()
 {
-  gfx::VRHMDSensorState state = mClient->GetSensorState();
-  RefPtr<VRPose> obj = new VRPose(GetParentObject(), state);
+  UpdateFrameInfo();
+  RefPtr<VRPose> obj = new VRPose(GetParentObject(), mFrameInfo.mVRState);
 
   return obj.forget();
 }
 
 void
 VRDisplay::ResetPose()
 {
   mClient->ZeroSensor();
@@ -544,16 +565,17 @@ VRDisplay::RequestPresent(const nsTArray
   NS_ENSURE_TRUE(obs, nullptr);
 
   if (mClient->GetIsPresenting()) {
     // Only one presentation allowed per VRDisplay
     // on a first-come-first-serve basis.
     promise->MaybeRejectWithUndefined();
   } else {
     mPresentation = mClient->BeginPresentation(aLayers);
+    mFrameInfo.Clear();
 
     nsresult rv = obs->AddObserver(this, "inner-window-destroyed", false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mPresentation = nullptr;
       promise->MaybeRejectWithUndefined();
     } else {
       promise->MaybeResolve(JS::UndefinedHandleValue);
     }
@@ -632,16 +654,17 @@ VRDisplay::SubmitFrame(const Optional<No
 {
   if (mPresentation) {
     if (aPose.WasPassed()) {
       mPresentation->SubmitFrame(aPose.Value().FrameID());
     } else {
       mPresentation->SubmitFrame(0);
     }
   }
+  mFrameInfo.Clear();
 }
 
 int32_t
 VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback,
 ErrorResult& aError)
 {
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
 
@@ -760,65 +783,74 @@ VRFrameData::LazyCreateMatrix(JS::Heap<J
     JS::ExposeObjectToActiveJS(aArray);
   }
   aRetval.set(aArray);
 }
 
 double
 VRFrameData::Timestamp() const
 {
-  return mVRState.timestamp * 1000.0f; // Converting from seconds to milliseconds
+  // Converting from seconds to milliseconds
+  return mFrameInfo.mVRState.timestamp * 1000.0f;
 }
 
 void
 VRFrameData::GetLeftProjectionMatrix(JSContext* aCx,
                                      JS::MutableHandle<JSObject*> aRetval,
                                      ErrorResult& aRv)
 {
-  LazyCreateMatrix(mLeftProjectionMatrix, mLeftProjection, aCx, aRetval, aRv);
+  LazyCreateMatrix(mLeftProjectionMatrix, mFrameInfo.mLeftProjection, aCx,
+                   aRetval, aRv);
 }
 
 void
 VRFrameData::GetLeftViewMatrix(JSContext* aCx,
                                JS::MutableHandle<JSObject*> aRetval,
                                ErrorResult& aRv)
 {
-  LazyCreateMatrix(mLeftViewMatrix, mLeftView, aCx, aRetval, aRv);
+  LazyCreateMatrix(mLeftViewMatrix, mFrameInfo.mLeftView, aCx, aRetval, aRv);
 }
 
 void
 VRFrameData::GetRightProjectionMatrix(JSContext* aCx,
                                       JS::MutableHandle<JSObject*> aRetval,
                                       ErrorResult& aRv)
 {
-  LazyCreateMatrix(mRightProjectionMatrix, mRightProjection, aCx, aRetval, aRv);
+  LazyCreateMatrix(mRightProjectionMatrix, mFrameInfo.mRightProjection, aCx,
+                   aRetval, aRv);
 }
 
 void
 VRFrameData::GetRightViewMatrix(JSContext* aCx,
                                 JS::MutableHandle<JSObject*> aRetval,
                                 ErrorResult& aRv)
 {
-  LazyCreateMatrix(mRightViewMatrix, mRightView, aCx, aRetval, aRv);
+  LazyCreateMatrix(mRightViewMatrix, mFrameInfo.mRightView, aCx, aRetval, aRv);
 }
 
 void
-VRFrameData::Update(const gfx::VRDisplayInfo& aInfo,
-                    const gfx::VRHMDSensorState& aState,
-                    float aDepthNear,
-                    float aDepthFar)
+VRFrameData::Update(const VRFrameInfo& aFrameInfo)
 {
-  mVRState = aState;
+  mFrameInfo = aFrameInfo;
 
   mLeftProjectionMatrix = nullptr;
   mLeftViewMatrix = nullptr;
   mRightProjectionMatrix = nullptr;
   mRightViewMatrix = nullptr;
 
-  mPose = new VRPose(GetParentObject(), aState);
+  mPose = new VRPose(GetParentObject(), mFrameInfo.mVRState);
+}
+
+void
+VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
+                    const gfx::VRHMDSensorState& aState,
+                    float aDepthNear,
+                    float aDepthFar)
+{
+  mVRState = aState;
 
   gfx::Quaternion qt;
   if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) {
     qt.x = mVRState.orientation[0];
     qt.y = mVRState.orientation[1];
     qt.z = mVRState.orientation[2];
     qt.w = mVRState.orientation[3];
   }
@@ -843,13 +875,29 @@ VRFrameData::Update(const gfx::VRDisplay
   if (fabs(aDepthFar - aDepthNear) < kEpsilon) {
     aDepthFar = aDepthNear + kEpsilon;
   }
 
   const gfx::VRFieldOfView leftFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Left];
   mLeftProjection = leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
   const gfx::VRFieldOfView rightFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Right];
   mRightProjection = rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
+}
 
+VRFrameInfo::VRFrameInfo()
+{
+  mVRState.Clear();
+}
+
+bool
+VRFrameInfo::IsDirty()
+{
+  return mVRState.timestamp == 0;
+}
+
+void
+VRFrameInfo::Clear()
+{
+  mVRState.Clear();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -140,30 +140,46 @@ protected:
   JS::Heap<JSObject*> mLinearVelocity;
   JS::Heap<JSObject*> mLinearAcceleration;
   JS::Heap<JSObject*> mOrientation;
   JS::Heap<JSObject*> mAngularVelocity;
   JS::Heap<JSObject*> mAngularAcceleration;
 
 };
 
+struct VRFrameInfo
+{
+  VRFrameInfo();
+
+  void Update(const gfx::VRDisplayInfo& aInfo,
+    const gfx::VRHMDSensorState& aState,
+    float aDepthNear,
+    float aDepthFar);
+
+  void Clear();
+  bool IsDirty();
+
+  gfx::VRHMDSensorState mVRState;
+  gfx::Matrix4x4 mLeftProjection;
+  gfx::Matrix4x4 mLeftView;
+  gfx::Matrix4x4 mRightProjection;
+  gfx::Matrix4x4 mRightView;
+};
+
 class VRFrameData final : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFrameData)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFrameData)
 
   explicit VRFrameData(nsISupports* aParent);
   static already_AddRefed<VRFrameData> Constructor(const GlobalObject& aGlobal,
                                                    ErrorResult& aRv);
 
-  void Update(const gfx::VRDisplayInfo& aInfo,
-              const gfx::VRHMDSensorState& aState,
-              float aDepthNear,
-              float aDepthFar);
+  void Update(const VRFrameInfo& aFrameInfo);
 
   // WebIDL Members
   double Timestamp() const;
   void GetLeftProjectionMatrix(JSContext* aCx,
                                JS::MutableHandle<JSObject*> aRetval,
                                ErrorResult& aRv);
   void GetLeftViewMatrix(JSContext* aCx,
                          JS::MutableHandle<JSObject*> aRetval,
@@ -180,28 +196,23 @@ public:
   // WebIDL Boilerplate
   nsISupports* GetParentObject() const { return mParent; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 protected:
   ~VRFrameData();
   nsCOMPtr<nsISupports> mParent;
 
-  gfx::VRHMDSensorState mVRState;
+  VRFrameInfo mFrameInfo;
   RefPtr<VRPose> mPose;
   JS::Heap<JSObject*> mLeftProjectionMatrix;
   JS::Heap<JSObject*> mLeftViewMatrix;
   JS::Heap<JSObject*> mRightProjectionMatrix;
   JS::Heap<JSObject*> mRightViewMatrix;
 
-  gfx::Matrix4x4 mLeftProjection;
-  gfx::Matrix4x4 mLeftView;
-  gfx::Matrix4x4 mRightProjection;
-  gfx::Matrix4x4 mRightView;
-
   void LazyCreateMatrix(JS::Heap<JSObject*>& aArray, gfx::Matrix4x4& aMat,
                         JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
                         ErrorResult& aRv);
 };
 
 class VRStageParameters final : public nsWrapperCache
 {
 public:
@@ -327,27 +338,37 @@ public:
   void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
 
 protected:
   VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient);
   virtual ~VRDisplay();
   virtual void LastRelease() override;
 
   void ExitPresentInternal();
+  void UpdateFrameInfo();
 
   RefPtr<gfx::VRDisplayClient> mClient;
 
   uint32_t mDisplayId;
   nsString mDisplayName;
 
   RefPtr<VRDisplayCapabilities> mCapabilities;
   RefPtr<VRStageParameters> mStageParameters;
 
   double mDepthNear;
   double mDepthFar;
 
   RefPtr<gfx::VRDisplayPresentation> mPresentation;
+
+  /**
+  * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData
+  * must return the same values until the next VRDisplay.submitFrame.
+  * mFrameInfo is updated only on the first call to either function within one
+  * frame.  Subsequent calls before the next SubmitFrame or ExitPresent call
+  * will use these cached values.
+  */
+  VRFrameInfo mFrameInfo;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif