Bug 1293793 - Ensure VRFrameData.timestamp is a monotonically increasing value draft
authorKearwood Gilbert <kgilbert@mozilla.com>
Fri, 03 Mar 2017 15:17:19 -0800
changeset 493439 c4430bce6814681878e499891cd9222976992e8e
parent 493295 942165e408f0241cf543c368db00fefe22a9997a
child 547863 5db041df9ee85384611333c2df482d7731d1d8db
push id47763
push userbmo:kgilbert@mozilla.com
push dateFri, 03 Mar 2017 23:29:09 +0000
bugs1293793
milestone54.0a1
Bug 1293793 - Ensure VRFrameData.timestamp is a monotonically increasing value - Oculus and OSVR VRFrameData.timestamp values were already returning correct timestamps using their respective API's timestamp functions. - OpenVR is now using timestamp values returned by the OpenVR API. - A pseudo-random base for VRFrameData.timestamp has been implemented in order to avoid leaking details related to how long the user has been using their VR headset before hitting a page. - More details on timestamp base within code comments... MozReview-Commit-ID: 7VdiRn7l8Rb
dom/vr/VRDisplay.cpp
dom/vr/VRDisplay.h
gfx/vr/gfxVROpenVR.cpp
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -748,16 +748,33 @@ VRFrameData::Update(const VRFrameInfo& a
 
 void
 VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
                     const gfx::VRHMDSensorState& aState,
                     float aDepthNear,
                     float aDepthFar)
 {
   mVRState = aState;
+  if (mTimeStampOffset == 0.0f) {
+    /**
+     * A mTimeStampOffset value of 0.0f indicates that this is the first iteration
+     * and an offset has not yet been set.
+     *
+     * Generate a value for mTimeStampOffset such that if aState.timestamp is
+     * monotonically increasing, aState.timestamp + mTimeStampOffset will never
+     * be a negative number and will start at a pseudo-random offset
+     * between 1000.0f and 11000.0f seconds.
+     *
+     * We use a pseudo random offset rather than 0.0f just to discourage users
+     * from making the assumption that the timestamp returned in the WebVR API
+     * has a base of 0, which is not necessarily true in all UA's.
+     */
+    mTimeStampOffset = float(rand()) / RAND_MAX * 10000.0f + 1000.0f - aState.timestamp;
+  }
+  mVRState.timestamp = aState.timestamp + mTimeStampOffset;
 
   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];
   }
@@ -785,16 +802,17 @@ VRFrameInfo::Update(const gfx::VRDisplay
 
   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()
+ : mTimeStampOffset(0.0f)
 {
   mVRState.Clear();
 }
 
 bool
 VRFrameInfo::IsDirty()
 {
   return mVRState.timestamp == 0;
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -145,16 +145,23 @@ struct VRFrameInfo
   bool IsDirty();
 
   gfx::VRHMDSensorState mVRState;
   gfx::Matrix4x4 mLeftProjection;
   gfx::Matrix4x4 mLeftView;
   gfx::Matrix4x4 mRightProjection;
   gfx::Matrix4x4 mRightView;
 
+  /**
+   * In order to avoid leaking information related to the duration of
+   * the user's VR session, we re-base timestamps.
+   * mTimeStampOffset is added to the actual timestamp returned by the
+   * underlying VR platform API when returned through WebVR API's.
+   */
+  double mTimeStampOffset;
 };
 
 class VRFrameData final : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFrameData)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFrameData)
 
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -292,17 +292,25 @@ VRDisplayOpenVR::GetSensorState(double t
   PollEvents();
 
   ::vr::TrackedDevicePose_t poses[::vr::k_unMaxTrackedDeviceCount];
   // Note: We *must* call WaitGetPoses in order for any rendering to happen at all
   mVRCompositor->WaitGetPoses(poses, ::vr::k_unMaxTrackedDeviceCount, nullptr, 0);
 
   VRHMDSensorState result;
   result.Clear();
-  result.timestamp = PR_Now();
+
+  ::vr::Compositor_FrameTiming timing;
+  timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
+  if (mVRCompositor->GetFrameTiming(&timing)) {
+    result.timestamp = timing.m_flSystemTimeInSeconds;
+  } else {
+    // This should not happen, but log it just in case
+    NS_WARNING("OpenVR - IVRCompositor::GetFrameTiming failed");
+  }
 
   if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
       poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
       poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == ::vr::TrackingResult_Running_OK)
   {
     const ::vr::TrackedDevicePose_t& pose = poses[::vr::k_unTrackedDeviceIndex_Hmd];
 
     gfx::Matrix4x4 m;