1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/dom/XRSession.h"
8 
9 #include "mozilla/dom/XRSessionEvent.h"
10 #include "mozilla/dom/XRInputSourceEvent.h"
11 #include "mozilla/EventDispatcher.h"
12 #include "mozilla/dom/DocumentInlines.h"
13 #include "mozilla/dom/Promise.h"
14 #include "XRSystem.h"
15 #include "XRRenderState.h"
16 #include "XRBoundedReferenceSpace.h"
17 #include "XRFrame.h"
18 #include "XRNativeOrigin.h"
19 #include "XRNativeOriginFixed.h"
20 #include "XRNativeOriginViewer.h"
21 #include "XRNativeOriginLocal.h"
22 #include "XRNativeOriginLocalFloor.h"
23 #include "XRView.h"
24 #include "XRViewerPose.h"
25 #include "VRLayerChild.h"
26 #include "XRInputSourceArray.h"
27 #include "nsGlobalWindow.h"
28 #include "nsIObserverService.h"
29 #include "nsISupportsPrimitives.h"
30 #include "nsRefreshDriver.h"
31 #include "VRDisplayClient.h"
32 #include "VRDisplayPresentation.h"
33 
34 /**
35  * Maximum instances of XRFrame and XRViewerPose objects
36  * created in the pool.
37  */
38 const uint32_t kMaxPoolSize = 16;
39 
40 namespace mozilla {
41 namespace dom {
42 
43 NS_IMPL_CYCLE_COLLECTION_CLASS(XRSession)
44 
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XRSession,
46                                                   DOMEventTargetHelper)
47   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXRSystem)
48   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveRenderState)
49   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingRenderState)
50   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputSources)
51   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mViewerPosePool)
52   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFramePool)
53 
54   for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
55     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
56     cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
57   }
58 
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
60 
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XRSession, DOMEventTargetHelper)
62   NS_IMPL_CYCLE_COLLECTION_UNLINK(mXRSystem)
63   NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveRenderState)
64   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingRenderState)
65   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputSources)
66   NS_IMPL_CYCLE_COLLECTION_UNLINK(mViewerPosePool)
67   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFramePool)
68 
69   tmp->mFrameRequestCallbacks.Clear();
70 
71   // Don't need NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER because
72   // DOMEventTargetHelper does it for us.
73 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
74 
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XRSession,DOMEventTargetHelper)75 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XRSession, DOMEventTargetHelper)
76 
77 already_AddRefed<XRSession> XRSession::CreateInlineSession(
78     nsPIDOMWindowInner* aWindow, XRSystem* aXRSystem,
79     const nsTArray<XRReferenceSpaceType>& aEnabledReferenceSpaceTypes) {
80   nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
81   if (!win) {
82     return nullptr;
83   }
84   Document* doc = aWindow->GetExtantDoc();
85   if (!doc) {
86     return nullptr;
87   }
88   nsPresContext* context = doc->GetPresContext();
89   if (!context) {
90     return nullptr;
91   }
92   nsRefreshDriver* driver = context->RefreshDriver();
93   if (!driver) {
94     return nullptr;
95   }
96 
97   RefPtr<XRSession> session =
98       new XRSession(aWindow, aXRSystem, driver, nullptr, gfx::kVRGroupContent,
99                     aEnabledReferenceSpaceTypes);
100   driver->AddRefreshObserver(session, FlushType::Display, "XR Session");
101   return session.forget();
102 }
103 
CreateImmersiveSession(nsPIDOMWindowInner * aWindow,XRSystem * aXRSystem,gfx::VRDisplayClient * aClient,uint32_t aPresentationGroup,const nsTArray<XRReferenceSpaceType> & aEnabledReferenceSpaceTypes)104 already_AddRefed<XRSession> XRSession::CreateImmersiveSession(
105     nsPIDOMWindowInner* aWindow, XRSystem* aXRSystem,
106     gfx::VRDisplayClient* aClient, uint32_t aPresentationGroup,
107     const nsTArray<XRReferenceSpaceType>& aEnabledReferenceSpaceTypes) {
108   RefPtr<XRSession> session =
109       new XRSession(aWindow, aXRSystem, nullptr, aClient, aPresentationGroup,
110                     aEnabledReferenceSpaceTypes);
111   return session.forget();
112 }
113 
XRSession(nsPIDOMWindowInner * aWindow,XRSystem * aXRSystem,nsRefreshDriver * aRefreshDriver,gfx::VRDisplayClient * aClient,uint32_t aPresentationGroup,const nsTArray<XRReferenceSpaceType> & aEnabledReferenceSpaceTypes)114 XRSession::XRSession(
115     nsPIDOMWindowInner* aWindow, XRSystem* aXRSystem,
116     nsRefreshDriver* aRefreshDriver, gfx::VRDisplayClient* aClient,
117     uint32_t aPresentationGroup,
118     const nsTArray<XRReferenceSpaceType>& aEnabledReferenceSpaceTypes)
119     : DOMEventTargetHelper(aWindow),
120       mXRSystem(aXRSystem),
121       mShutdown(false),
122       mEnded(false),
123       mRefreshDriver(aRefreshDriver),
124       mDisplayClient(aClient),
125       mFrameRequestCallbackCounter(0),
126       mEnabledReferenceSpaceTypes(aEnabledReferenceSpaceTypes.Clone()),
127       mViewerPosePoolIndex(0),
128       mFramePoolIndex(0) {
129   if (aClient) {
130     aClient->SessionStarted(this);
131   }
132   mActiveRenderState = new XRRenderState(aWindow, this);
133   mStartTimeStamp = TimeStamp::Now();
134   if (IsImmersive()) {
135     mDisplayPresentation =
136         mDisplayClient->BeginPresentation({}, aPresentationGroup);
137   }
138   if (mDisplayClient) {
139     mDisplayClient->SetXRAPIMode(gfx::VRAPIMode::WebXR);
140   }
141   // TODO: Handle XR input sources are no longer available cases.
142   // https://immersive-web.github.io/webxr/#dom-xrsession-inputsources
143   mInputSources = new XRInputSourceArray(aWindow);
144 }
145 
~XRSession()146 XRSession::~XRSession() { MOZ_ASSERT(mShutdown); }
147 
GetDisplayClient() const148 gfx::VRDisplayClient* XRSession::GetDisplayClient() const {
149   return mDisplayClient;
150 }
151 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)152 JSObject* XRSession::WrapObject(JSContext* aCx,
153                                 JS::Handle<JSObject*> aGivenProto) {
154   return XRSession_Binding::Wrap(aCx, this, aGivenProto);
155 }
156 
IsEnded() const157 bool XRSession::IsEnded() const { return mEnded; }
158 
End(ErrorResult & aRv)159 already_AddRefed<Promise> XRSession::End(ErrorResult& aRv) {
160   nsCOMPtr<nsIGlobalObject> global = GetParentObject();
161   NS_ENSURE_TRUE(global, nullptr);
162 
163   ExitPresentInternal();
164 
165   RefPtr<Promise> promise = Promise::Create(global, aRv);
166   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
167 
168   promise->MaybeResolve(JS::UndefinedHandleValue);
169 
170   return promise.forget();
171 }
172 
IsImmersive() const173 bool XRSession::IsImmersive() const {
174   // Only immersive sessions have a VRDisplayClient
175   return mDisplayClient != nullptr;
176 }
177 
VisibilityState()178 XRVisibilityState XRSession::VisibilityState() {
179   return XRVisibilityState::Visible;
180   // TODO (Bug 1609771): Implement changing visibility state
181 }
182 
183 // https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate
UpdateRenderState(const XRRenderStateInit & aNewState,ErrorResult & aRv)184 void XRSession::UpdateRenderState(const XRRenderStateInit& aNewState,
185                                   ErrorResult& aRv) {
186   if (mEnded) {
187     aRv.ThrowInvalidStateError(
188         "UpdateRenderState can not be called on an XRSession that has ended.");
189     return;
190   }
191   if (aNewState.mBaseLayer.WasPassed() &&
192       aNewState.mBaseLayer.Value()->mSession != this) {
193     aRv.ThrowInvalidStateError(
194         "The baseLayer passed in to UpdateRenderState must "
195         "belong to the XRSession that UpdateRenderState is "
196         "being called on.");
197     return;
198   }
199   if (aNewState.mInlineVerticalFieldOfView.WasPassed() && IsImmersive()) {
200     aRv.ThrowInvalidStateError(
201         "The inlineVerticalFieldOfView can not be set on an "
202         "XRRenderState for an immersive XRSession.");
203     return;
204   }
205   if (mPendingRenderState == nullptr) {
206     mPendingRenderState = new XRRenderState(*mActiveRenderState);
207   }
208   if (aNewState.mDepthNear.WasPassed()) {
209     mPendingRenderState->SetDepthNear(aNewState.mDepthNear.Value());
210   }
211   if (aNewState.mDepthFar.WasPassed()) {
212     mPendingRenderState->SetDepthFar(aNewState.mDepthFar.Value());
213   }
214   if (aNewState.mInlineVerticalFieldOfView.WasPassed()) {
215     mPendingRenderState->SetInlineVerticalFieldOfView(
216         aNewState.mInlineVerticalFieldOfView.Value());
217   }
218   if (aNewState.mBaseLayer.WasPassed()) {
219     mPendingRenderState->SetBaseLayer(aNewState.mBaseLayer.Value());
220   }
221 }
222 
RenderState()223 XRRenderState* XRSession::RenderState() { return mActiveRenderState; }
224 
InputSources()225 XRInputSourceArray* XRSession::InputSources() { return mInputSources; }
226 
227 // https://immersive-web.github.io/webxr/#apply-the-pending-render-state
ApplyPendingRenderState()228 void XRSession::ApplyPendingRenderState() {
229   if (mPendingRenderState == nullptr) {
230     return;
231   }
232   mActiveRenderState = mPendingRenderState;
233   mPendingRenderState = nullptr;
234 
235   // https://immersive-web.github.io/webxr/#minimum-inline-field-of-view
236   const double kMinimumInlineVerticalFieldOfView = 0.0f;
237 
238   // https://immersive-web.github.io/webxr/#maximum-inline-field-of-view
239   const double kMaximumInlineVerticalFieldOfView = M_PI;
240 
241   if (!mActiveRenderState->GetInlineVerticalFieldOfView().IsNull()) {
242     double verticalFOV =
243         mActiveRenderState->GetInlineVerticalFieldOfView().Value();
244     if (verticalFOV < kMinimumInlineVerticalFieldOfView) {
245       verticalFOV = kMinimumInlineVerticalFieldOfView;
246     }
247     if (verticalFOV > kMaximumInlineVerticalFieldOfView) {
248       verticalFOV = kMaximumInlineVerticalFieldOfView;
249     }
250     mActiveRenderState->SetInlineVerticalFieldOfView(verticalFOV);
251   }
252 
253   // Our minimum near plane value is set to a small value close but not equal to
254   // zero (kEpsilon) The maximum far plane is infinite.
255   const float kEpsilon = 0.00001f;
256   double depthNear = mActiveRenderState->DepthNear();
257   double depthFar = mActiveRenderState->DepthFar();
258   if (depthNear < 0.0f) {
259     depthNear = 0.0f;
260   }
261   if (depthFar < 0.0f) {
262     depthFar = 0.0f;
263   }
264   // Ensure at least a small distance between the near and far planes
265   if (fabs(depthFar - depthNear) < kEpsilon) {
266     depthFar = depthNear + kEpsilon;
267   }
268   mActiveRenderState->SetDepthNear(depthNear);
269   mActiveRenderState->SetDepthFar(depthFar);
270 
271   XRWebGLLayer* baseLayer = mActiveRenderState->GetBaseLayer();
272   if (baseLayer) {
273     if (!IsImmersive() && baseLayer->mCompositionDisabled) {
274       mActiveRenderState->SetCompositionDisabled(true);
275       mActiveRenderState->SetOutputCanvas(baseLayer->GetCanvas());
276     } else {
277       mActiveRenderState->SetCompositionDisabled(false);
278       mActiveRenderState->SetOutputCanvas(nullptr);
279       mDisplayPresentation->UpdateXRWebGLLayer(baseLayer);
280     }
281   }  // if (baseLayer)
282 }
283 
WillRefresh(mozilla::TimeStamp aTime)284 void XRSession::WillRefresh(mozilla::TimeStamp aTime) {
285   // Inline sessions are driven by nsRefreshDriver directly,
286   // unlike immersive sessions, which are driven VRDisplayClient.
287   if (!IsImmersive() && !mXRSystem->HasActiveImmersiveSession()) {
288     nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(GetOwner());
289     if (win) {
290       if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
291         js::NotifyAnimationActivity(obj);
292       }
293     }
294     StartFrame();
295   }
296 }
297 
StartFrame()298 void XRSession::StartFrame() {
299   if (mShutdown || mEnded) {
300     return;
301   }
302   ApplyPendingRenderState();
303 
304   XRWebGLLayer* baseLayer = mActiveRenderState->GetBaseLayer();
305   if (!baseLayer) {
306     return;
307   }
308 
309   if (!IsImmersive() && mActiveRenderState->GetOutputCanvas() == nullptr) {
310     return;
311   }
312 
313   // Determine timestamp for the callbacks
314   TimeStamp nowTime = TimeStamp::Now();
315   mozilla::TimeDuration duration = nowTime - mStartTimeStamp;
316   DOMHighResTimeStamp timeStamp = duration.ToMilliseconds();
317 
318   // Create an XRFrame for the callbacks
319   RefPtr<XRFrame> frame = PooledFrame();
320   frame->StartAnimationFrame();
321 
322   baseLayer->StartAnimationFrame();
323   nsTArray<XRFrameRequest> callbacks;
324   callbacks.AppendElements(mFrameRequestCallbacks);
325   mFrameRequestCallbacks.Clear();
326   for (auto& callback : callbacks) {
327     callback.Call(timeStamp, *frame);
328   }
329 
330   baseLayer->EndAnimationFrame();
331   frame->EndAnimationFrame();
332   if (mDisplayPresentation) {
333     mDisplayPresentation->SubmitFrame();
334   }
335 }
336 
ExitPresent()337 void XRSession::ExitPresent() { ExitPresentInternal(); }
338 
RequestReferenceSpace(const XRReferenceSpaceType & aReferenceSpaceType,ErrorResult & aRv)339 already_AddRefed<Promise> XRSession::RequestReferenceSpace(
340     const XRReferenceSpaceType& aReferenceSpaceType, ErrorResult& aRv) {
341   nsCOMPtr<nsIGlobalObject> global = GetParentObject();
342   NS_ENSURE_TRUE(global, nullptr);
343 
344   RefPtr<Promise> promise = Promise::Create(global, aRv);
345   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
346 
347   if (!mEnabledReferenceSpaceTypes.Contains(aReferenceSpaceType)) {
348     promise->MaybeRejectWithNotSupportedError(nsLiteralCString(
349         "Requested XRReferenceSpaceType not available for the XRSession."));
350     return promise.forget();
351   }
352   RefPtr<XRReferenceSpace> space;
353   RefPtr<XRNativeOrigin> nativeOrigin;
354   if (mDisplayClient) {
355     switch (aReferenceSpaceType) {
356       case XRReferenceSpaceType::Viewer:
357         nativeOrigin = new XRNativeOriginViewer(mDisplayClient);
358         break;
359       case XRReferenceSpaceType::Local:
360         nativeOrigin = new XRNativeOriginLocal(mDisplayClient);
361         break;
362       case XRReferenceSpaceType::Local_floor:
363       case XRReferenceSpaceType::Bounded_floor:
364         nativeOrigin = new XRNativeOriginLocalFloor(mDisplayClient);
365         break;
366       default:
367         nativeOrigin = new XRNativeOriginFixed(gfx::PointDouble3D());
368         break;
369     }
370   } else {
371     // We currently only support XRReferenceSpaceType::Viewer when
372     // there is no XR hardware.  In this case, the native origin
373     // will always be at {0, 0, 0} which will always be the same
374     // as the 'tracked' position of the non-existant pose.
375     MOZ_ASSERT(aReferenceSpaceType == XRReferenceSpaceType::Viewer);
376     nativeOrigin = new XRNativeOriginFixed(gfx::PointDouble3D());
377   }
378   if (aReferenceSpaceType == XRReferenceSpaceType::Bounded_floor) {
379     space = new XRBoundedReferenceSpace(GetParentObject(), this, nativeOrigin);
380   } else {
381     space = new XRReferenceSpace(GetParentObject(), this, nativeOrigin,
382                                  aReferenceSpaceType);
383   }
384 
385   promise->MaybeResolve(space);
386   return promise.forget();
387 }
388 
GetActiveRenderState() const389 XRRenderState* XRSession::GetActiveRenderState() const {
390   return mActiveRenderState;
391 }
392 
Call(const DOMHighResTimeStamp & aTimeStamp,XRFrame & aFrame)393 void XRSession::XRFrameRequest::Call(const DOMHighResTimeStamp& aTimeStamp,
394                                      XRFrame& aFrame) {
395   RefPtr<mozilla::dom::XRFrameRequestCallback> callback = mCallback;
396   callback->Call(aTimeStamp, aFrame);
397 }
398 
RequestAnimationFrame(XRFrameRequestCallback & aCallback,ErrorResult & aError)399 int32_t XRSession::RequestAnimationFrame(XRFrameRequestCallback& aCallback,
400                                          ErrorResult& aError) {
401   if (mShutdown) {
402     return 0;
403   }
404 
405   int32_t handle = ++mFrameRequestCallbackCounter;
406 
407   mFrameRequestCallbacks.AppendElement(XRFrameRequest(aCallback, handle));
408 
409   return handle;
410 }
411 
CancelAnimationFrame(int32_t aHandle,ErrorResult & aError)412 void XRSession::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError) {
413   mFrameRequestCallbacks.RemoveElementSorted(aHandle);
414 }
415 
Shutdown()416 void XRSession::Shutdown() {
417   mShutdown = true;
418   ExitPresentInternal();
419   mViewerPosePool.Clear();
420   mViewerPosePoolIndex = 0;
421   mFramePool.Clear();
422   mFramePoolIndex = 0;
423   mActiveRenderState = nullptr;
424   mPendingRenderState = nullptr;
425   mFrameRequestCallbacks.Clear();
426 
427   // Unregister from nsRefreshObserver
428   if (mRefreshDriver) {
429     mRefreshDriver->RemoveRefreshObserver(this, FlushType::Display);
430     mRefreshDriver = nullptr;
431   }
432 }
433 
ExitPresentInternal()434 void XRSession::ExitPresentInternal() {
435   if (mInputSources) {
436     mInputSources->Clear(this);
437   }
438   if (mDisplayClient) {
439     mDisplayClient->SessionEnded(this);
440   }
441 
442   if (mXRSystem) {
443     mXRSystem->SessionEnded(this);
444   }
445 
446   if (mActiveRenderState) {
447     mActiveRenderState->SessionEnded();
448   }
449 
450   if (mPendingRenderState) {
451     mPendingRenderState->SessionEnded();
452   }
453 
454   mDisplayPresentation = nullptr;
455   if (!mEnded) {
456     mEnded = true;
457 
458     XRSessionEventInit init;
459     init.mBubbles = false;
460     init.mCancelable = false;
461     init.mSession = this;
462     RefPtr<XRSessionEvent> event =
463         XRSessionEvent::Constructor(this, u"end"_ns, init);
464 
465     event->SetTrusted(true);
466     this->DispatchEvent(*event);
467   }
468 }
469 
DisconnectFromOwner()470 void XRSession::DisconnectFromOwner() {
471   MOZ_ASSERT(NS_IsMainThread());
472   Shutdown();
473   DOMEventTargetHelper::DisconnectFromOwner();
474 }
475 
LastRelease()476 void XRSession::LastRelease() {
477   // We don't want to wait for the GC to free up the presentation
478   // for use in other documents, so we do this in LastRelease().
479   Shutdown();
480 }
481 
PooledViewerPose(const gfx::Matrix4x4Double & aTransform,bool aEmulatedPosition)482 RefPtr<XRViewerPose> XRSession::PooledViewerPose(
483     const gfx::Matrix4x4Double& aTransform, bool aEmulatedPosition) {
484   RefPtr<XRViewerPose> pose;
485   if (mViewerPosePool.Length() > mViewerPosePoolIndex) {
486     pose = mViewerPosePool.ElementAt(mViewerPosePoolIndex);
487     pose->Transform()->Update(aTransform);
488     pose->SetEmulatedPosition(aEmulatedPosition);
489   } else {
490     RefPtr<XRRigidTransform> transform = new XRRigidTransform(this, aTransform);
491     nsTArray<RefPtr<XRView>> views;
492     if (IsImmersive()) {
493       views.AppendElement(new XRView(GetParentObject(), XREye::Left));
494       views.AppendElement(new XRView(GetParentObject(), XREye::Right));
495     } else {
496       views.AppendElement(new XRView(GetParentObject(), XREye::None));
497     }
498     pose = new XRViewerPose(this, transform, aEmulatedPosition, views);
499     mViewerPosePool.AppendElement(pose);
500   }
501 
502   mViewerPosePoolIndex++;
503   if (mViewerPosePoolIndex >= kMaxPoolSize) {
504     mViewerPosePoolIndex = 0;
505   }
506 
507   return pose;
508 }
509 
PooledFrame()510 RefPtr<XRFrame> XRSession::PooledFrame() {
511   RefPtr<XRFrame> frame;
512   if (mFramePool.Length() > mFramePoolIndex) {
513     frame = mFramePool.ElementAt(mFramePoolIndex);
514   } else {
515     frame = new XRFrame(GetParentObject(), this);
516     mFramePool.AppendElement(frame);
517   }
518 
519   return frame;
520 }
521 
522 }  // namespace dom
523 }  // namespace mozilla
524