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/. */
7 #include "mozilla/dom/XRSession.h"
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"
15 #include "XRRenderState.h"
16 #include "XRBoundedReferenceSpace.h"
18 #include "XRNativeOrigin.h"
19 #include "XRNativeOriginFixed.h"
20 #include "XRNativeOriginViewer.h"
21 #include "XRNativeOriginLocal.h"
22 #include "XRNativeOriginLocalFloor.h"
24 #include "XRViewerPose.h"
25 #include "VRLayerChild.h"
26 #include "XRInputSourceArray.h"
27 #include "nsGlobalWindowInner.h"
28 #include "nsIObserverService.h"
29 #include "nsISupportsPrimitives.h"
30 #include "nsRefreshDriver.h"
31 #include "VRDisplayClient.h"
32 #include "VRDisplayPresentation.h"
35 * Maximum instances of XRFrame and XRViewerPose objects
36 * created in the pool.
38 const uint32_t kMaxPoolSize
= 16;
40 namespace mozilla::dom
{
42 NS_IMPL_CYCLE_COLLECTION_CLASS(XRSession
)
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XRSession
,
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXRSystem
)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveRenderState
)
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingRenderState
)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputSources
)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mViewerPosePool
)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFramePool
)
53 for (uint32_t i
= 0; i
< tmp
->mFrameRequestCallbacks
.Length(); ++i
) {
54 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mFrameRequestCallbacks[i]");
55 cb
.NoteXPCOMChild(tmp
->mFrameRequestCallbacks
[i
].mCallback
);
58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
60 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XRSession
, DOMEventTargetHelper
)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mXRSystem
)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveRenderState
)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingRenderState
)
64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputSources
)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK(mViewerPosePool
)
66 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFramePool
)
68 tmp
->mFrameRequestCallbacks
.Clear();
70 // Don't need NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER because
71 // DOMEventTargetHelper does it for us.
72 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
74 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XRSession
, DOMEventTargetHelper
)
76 already_AddRefed
<XRSession
> XRSession::CreateInlineSession(
77 nsPIDOMWindowInner
* aWindow
, XRSystem
* aXRSystem
,
78 const nsTArray
<XRReferenceSpaceType
>& aEnabledReferenceSpaceTypes
) {
79 nsGlobalWindowInner
* win
= nsGlobalWindowInner::Cast(aWindow
);
83 Document
* doc
= aWindow
->GetExtantDoc();
87 nsPresContext
* context
= doc
->GetPresContext();
91 nsRefreshDriver
* driver
= context
->RefreshDriver();
96 RefPtr
<XRSession
> session
=
97 new XRSession(aWindow
, aXRSystem
, driver
, nullptr, gfx::kVRGroupContent
,
98 aEnabledReferenceSpaceTypes
);
99 driver
->AddRefreshObserver(session
, FlushType::Display
, "XR Session");
100 return session
.forget();
103 already_AddRefed
<XRSession
> XRSession::CreateImmersiveSession(
104 nsPIDOMWindowInner
* aWindow
, XRSystem
* aXRSystem
,
105 gfx::VRDisplayClient
* aClient
, uint32_t aPresentationGroup
,
106 const nsTArray
<XRReferenceSpaceType
>& aEnabledReferenceSpaceTypes
) {
107 RefPtr
<XRSession
> session
=
108 new XRSession(aWindow
, aXRSystem
, nullptr, aClient
, aPresentationGroup
,
109 aEnabledReferenceSpaceTypes
);
110 return session
.forget();
113 XRSession::XRSession(
114 nsPIDOMWindowInner
* aWindow
, XRSystem
* aXRSystem
,
115 nsRefreshDriver
* aRefreshDriver
, gfx::VRDisplayClient
* aClient
,
116 uint32_t aPresentationGroup
,
117 const nsTArray
<XRReferenceSpaceType
>& aEnabledReferenceSpaceTypes
)
118 : DOMEventTargetHelper(aWindow
),
119 mXRSystem(aXRSystem
),
122 mRefreshDriver(aRefreshDriver
),
123 mDisplayClient(aClient
),
124 mFrameRequestCallbackCounter(0),
125 mEnabledReferenceSpaceTypes(aEnabledReferenceSpaceTypes
.Clone()),
126 mViewerPosePoolIndex(0),
129 aClient
->SessionStarted(this);
131 mActiveRenderState
= new XRRenderState(aWindow
, this);
132 mStartTimeStamp
= TimeStamp::Now();
134 mDisplayPresentation
=
135 mDisplayClient
->BeginPresentation({}, aPresentationGroup
);
137 if (mDisplayClient
) {
138 mDisplayClient
->SetXRAPIMode(gfx::VRAPIMode::WebXR
);
140 // TODO: Handle XR input sources are no longer available cases.
141 // https://immersive-web.github.io/webxr/#dom-xrsession-inputsources
142 mInputSources
= new XRInputSourceArray(aWindow
);
145 XRSession::~XRSession() { MOZ_ASSERT(mShutdown
); }
147 gfx::VRDisplayClient
* XRSession::GetDisplayClient() const {
148 return mDisplayClient
;
151 JSObject
* XRSession::WrapObject(JSContext
* aCx
,
152 JS::Handle
<JSObject
*> aGivenProto
) {
153 return XRSession_Binding::Wrap(aCx
, this, aGivenProto
);
156 bool XRSession::IsEnded() const { return mEnded
; }
158 already_AddRefed
<Promise
> XRSession::End(ErrorResult
& aRv
) {
159 nsCOMPtr
<nsIGlobalObject
> global
= GetParentObject();
160 NS_ENSURE_TRUE(global
, nullptr);
162 ExitPresentInternal();
164 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
165 NS_ENSURE_TRUE(!aRv
.Failed(), nullptr);
167 promise
->MaybeResolve(JS::UndefinedHandleValue
);
169 return promise
.forget();
172 bool XRSession::IsImmersive() const {
173 // Only immersive sessions have a VRDisplayClient
174 return mDisplayClient
!= nullptr;
177 XRVisibilityState
XRSession::VisibilityState() const {
178 return XRVisibilityState::Visible
;
179 // TODO (Bug 1609771): Implement changing visibility state
182 // https://immersive-web.github.io/webxr/#poses-may-be-reported
183 // Given that an XRSession cannot be requested without explicit consent
184 // by the user, the only necessary check is whether the XRSession's
185 // visiblityState is 'visible'.
186 bool XRSession::CanReportPoses() const {
187 return VisibilityState() == XRVisibilityState::Visible
;
190 // https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate
191 void XRSession::UpdateRenderState(const XRRenderStateInit
& aNewState
,
194 aRv
.ThrowInvalidStateError(
195 "UpdateRenderState can not be called on an XRSession that has ended.");
198 if (aNewState
.mBaseLayer
.WasPassed() &&
199 aNewState
.mBaseLayer
.Value()->mSession
!= this) {
200 aRv
.ThrowInvalidStateError(
201 "The baseLayer passed in to UpdateRenderState must "
202 "belong to the XRSession that UpdateRenderState is "
206 if (aNewState
.mInlineVerticalFieldOfView
.WasPassed() && IsImmersive()) {
207 aRv
.ThrowInvalidStateError(
208 "The inlineVerticalFieldOfView can not be set on an "
209 "XRRenderState for an immersive XRSession.");
212 if (mPendingRenderState
== nullptr) {
213 mPendingRenderState
= new XRRenderState(*mActiveRenderState
);
215 if (aNewState
.mDepthNear
.WasPassed()) {
216 mPendingRenderState
->SetDepthNear(aNewState
.mDepthNear
.Value());
218 if (aNewState
.mDepthFar
.WasPassed()) {
219 mPendingRenderState
->SetDepthFar(aNewState
.mDepthFar
.Value());
221 if (aNewState
.mInlineVerticalFieldOfView
.WasPassed()) {
222 mPendingRenderState
->SetInlineVerticalFieldOfView(
223 aNewState
.mInlineVerticalFieldOfView
.Value());
225 if (aNewState
.mBaseLayer
.WasPassed()) {
226 mPendingRenderState
->SetBaseLayer(aNewState
.mBaseLayer
.Value());
230 XRRenderState
* XRSession::RenderState() { return mActiveRenderState
; }
232 XRInputSourceArray
* XRSession::InputSources() { return mInputSources
; }
234 Nullable
<float> XRSession::GetFrameRate() { return {}; }
236 void XRSession::GetSupportedFrameRates(JSContext
*,
237 JS::MutableHandle
<JSObject
*> aRetVal
) {
238 aRetVal
.set(nullptr);
241 // https://immersive-web.github.io/webxr/#apply-the-pending-render-state
242 void XRSession::ApplyPendingRenderState() {
243 if (mPendingRenderState
== nullptr) {
246 mActiveRenderState
= mPendingRenderState
;
247 mPendingRenderState
= nullptr;
249 // https://immersive-web.github.io/webxr/#minimum-inline-field-of-view
250 const double kMinimumInlineVerticalFieldOfView
= 0.0f
;
252 // https://immersive-web.github.io/webxr/#maximum-inline-field-of-view
253 const double kMaximumInlineVerticalFieldOfView
= M_PI
;
255 if (!mActiveRenderState
->GetInlineVerticalFieldOfView().IsNull()) {
257 mActiveRenderState
->GetInlineVerticalFieldOfView().Value();
258 if (verticalFOV
< kMinimumInlineVerticalFieldOfView
) {
259 verticalFOV
= kMinimumInlineVerticalFieldOfView
;
261 if (verticalFOV
> kMaximumInlineVerticalFieldOfView
) {
262 verticalFOV
= kMaximumInlineVerticalFieldOfView
;
264 mActiveRenderState
->SetInlineVerticalFieldOfView(verticalFOV
);
267 // Our minimum near plane value is set to a small value close but not equal to
268 // zero (kEpsilon) The maximum far plane is infinite.
269 const float kEpsilon
= 0.00001f
;
270 double depthNear
= mActiveRenderState
->DepthNear();
271 double depthFar
= mActiveRenderState
->DepthFar();
272 if (depthNear
< 0.0f
) {
275 if (depthFar
< 0.0f
) {
278 // Ensure at least a small distance between the near and far planes
279 if (fabs(depthFar
- depthNear
) < kEpsilon
) {
280 depthFar
= depthNear
+ kEpsilon
;
282 mActiveRenderState
->SetDepthNear(depthNear
);
283 mActiveRenderState
->SetDepthFar(depthFar
);
285 XRWebGLLayer
* baseLayer
= mActiveRenderState
->GetBaseLayer();
287 if (!IsImmersive() && baseLayer
->mCompositionDisabled
) {
288 mActiveRenderState
->SetCompositionDisabled(true);
289 mActiveRenderState
->SetOutputCanvas(baseLayer
->GetCanvas());
291 mActiveRenderState
->SetCompositionDisabled(false);
292 mActiveRenderState
->SetOutputCanvas(nullptr);
293 mDisplayPresentation
->UpdateXRWebGLLayer(baseLayer
);
298 void XRSession::WillRefresh(mozilla::TimeStamp aTime
) {
299 // Inline sessions are driven by nsRefreshDriver directly,
300 // unlike immersive sessions, which are driven VRDisplayClient.
301 if (!IsImmersive() && !mXRSystem
->HasActiveImmersiveSession()) {
302 nsGlobalWindowInner
* win
= nsGlobalWindowInner::Cast(GetOwner());
304 if (JSObject
* obj
= win
->AsGlobal()->GetGlobalJSObject()) {
305 js::NotifyAnimationActivity(obj
);
312 void XRSession::StartFrame() {
313 if (mShutdown
|| mEnded
) {
316 ApplyPendingRenderState();
318 XRWebGLLayer
* baseLayer
= mActiveRenderState
->GetBaseLayer();
323 if (!IsImmersive() && mActiveRenderState
->GetOutputCanvas() == nullptr) {
327 // Determine timestamp for the callbacks
328 TimeStamp nowTime
= TimeStamp::Now();
329 mozilla::TimeDuration duration
= nowTime
- mStartTimeStamp
;
330 DOMHighResTimeStamp timeStamp
= duration
.ToMilliseconds();
332 // Create an XRFrame for the callbacks
333 RefPtr
<XRFrame
> frame
= PooledFrame();
334 frame
->StartAnimationFrame();
336 baseLayer
->StartAnimationFrame();
337 nsTArray
<XRFrameRequest
> callbacks
;
338 callbacks
.AppendElements(mFrameRequestCallbacks
);
339 mFrameRequestCallbacks
.Clear();
340 for (auto& callback
: callbacks
) {
341 callback
.Call(timeStamp
, *frame
);
344 baseLayer
->EndAnimationFrame();
345 frame
->EndAnimationFrame();
346 if (mDisplayPresentation
) {
347 mDisplayPresentation
->SubmitFrame();
351 void XRSession::ExitPresent() { ExitPresentInternal(); }
353 already_AddRefed
<Promise
> XRSession::RequestReferenceSpace(
354 const XRReferenceSpaceType
& aReferenceSpaceType
, ErrorResult
& aRv
) {
355 nsCOMPtr
<nsIGlobalObject
> global
= GetParentObject();
356 NS_ENSURE_TRUE(global
, nullptr);
358 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
359 NS_ENSURE_TRUE(!aRv
.Failed(), nullptr);
361 if (!mEnabledReferenceSpaceTypes
.Contains(aReferenceSpaceType
)) {
362 promise
->MaybeRejectWithNotSupportedError(nsLiteralCString(
363 "Requested XRReferenceSpaceType not available for the XRSession."));
364 return promise
.forget();
366 RefPtr
<XRReferenceSpace
> space
;
367 RefPtr
<XRNativeOrigin
> nativeOrigin
;
368 if (mDisplayClient
) {
369 switch (aReferenceSpaceType
) {
370 case XRReferenceSpaceType::Viewer
:
371 nativeOrigin
= new XRNativeOriginViewer(mDisplayClient
);
373 case XRReferenceSpaceType::Local
:
374 nativeOrigin
= new XRNativeOriginLocal(mDisplayClient
);
376 case XRReferenceSpaceType::Local_floor
:
377 case XRReferenceSpaceType::Bounded_floor
:
378 nativeOrigin
= new XRNativeOriginLocalFloor(mDisplayClient
);
381 nativeOrigin
= new XRNativeOriginFixed(gfx::PointDouble3D());
385 // We currently only support XRReferenceSpaceType::Viewer when
386 // there is no XR hardware. In this case, the native origin
387 // will always be at {0, 0, 0} which will always be the same
388 // as the 'tracked' position of the non-existant pose.
389 MOZ_ASSERT(aReferenceSpaceType
== XRReferenceSpaceType::Viewer
);
390 nativeOrigin
= new XRNativeOriginFixed(gfx::PointDouble3D());
392 if (aReferenceSpaceType
== XRReferenceSpaceType::Bounded_floor
) {
393 space
= new XRBoundedReferenceSpace(GetParentObject(), this, nativeOrigin
);
395 space
= new XRReferenceSpace(GetParentObject(), this, nativeOrigin
,
396 aReferenceSpaceType
);
399 promise
->MaybeResolve(space
);
400 return promise
.forget();
403 already_AddRefed
<Promise
> XRSession::UpdateTargetFrameRate(float aRate
,
405 nsCOMPtr
<nsIGlobalObject
> global
= GetParentObject();
406 NS_ENSURE_TRUE(global
, nullptr);
408 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
409 NS_ENSURE_TRUE(!aRv
.Failed(), nullptr);
412 promise
->MaybeRejectWithInvalidStateError(
413 "UpdateTargetFrameRate can not be called on an XRSession that has "
415 return promise
.forget();
418 // https://immersive-web.github.io/webxr/#dom-xrsession-updatetargetframerate
419 // TODO: Validate the rate with the frame rates supported from the device.
420 // We add a no op for now to avoid JS exceptions related to undefined method.
421 // The spec states that user agent MAY use rate to calculate a new display
422 // frame rate, so it's fine to let the default frame rate for now.
424 promise
->MaybeResolve(JS::UndefinedHandleValue
);
425 return promise
.forget();
428 XRRenderState
* XRSession::GetActiveRenderState() const {
429 return mActiveRenderState
;
432 void XRSession::XRFrameRequest::Call(const DOMHighResTimeStamp
& aTimeStamp
,
434 RefPtr
<mozilla::dom::XRFrameRequestCallback
> callback
= mCallback
;
435 callback
->Call(aTimeStamp
, aFrame
);
438 int32_t XRSession::RequestAnimationFrame(XRFrameRequestCallback
& aCallback
,
439 ErrorResult
& aError
) {
444 int32_t handle
= ++mFrameRequestCallbackCounter
;
446 mFrameRequestCallbacks
.AppendElement(XRFrameRequest(aCallback
, handle
));
451 void XRSession::CancelAnimationFrame(int32_t aHandle
, ErrorResult
& aError
) {
452 mFrameRequestCallbacks
.RemoveElementSorted(aHandle
);
455 void XRSession::Shutdown() {
457 ExitPresentInternal();
458 mViewerPosePool
.Clear();
459 mViewerPosePoolIndex
= 0;
462 mActiveRenderState
= nullptr;
463 mPendingRenderState
= nullptr;
464 mFrameRequestCallbacks
.Clear();
466 // Unregister from nsRefreshObserver
467 if (mRefreshDriver
) {
468 mRefreshDriver
->RemoveRefreshObserver(this, FlushType::Display
);
469 mRefreshDriver
= nullptr;
473 void XRSession::ExitPresentInternal() {
475 mInputSources
->Clear(this);
477 if (mDisplayClient
) {
478 mDisplayClient
->SessionEnded(this);
482 mXRSystem
->SessionEnded(this);
485 if (mActiveRenderState
) {
486 mActiveRenderState
->SessionEnded();
489 if (mPendingRenderState
) {
490 mPendingRenderState
->SessionEnded();
493 mDisplayPresentation
= nullptr;
497 XRSessionEventInit init
;
498 init
.mBubbles
= false;
499 init
.mCancelable
= false;
500 init
.mSession
= this;
501 RefPtr
<XRSessionEvent
> event
=
502 XRSessionEvent::Constructor(this, u
"end"_ns
, init
);
504 event
->SetTrusted(true);
505 this->DispatchEvent(*event
);
509 void XRSession::DisconnectFromOwner() {
510 MOZ_ASSERT(NS_IsMainThread());
512 DOMEventTargetHelper::DisconnectFromOwner();
515 void XRSession::LastRelease() {
516 // We don't want to wait for the GC to free up the presentation
517 // for use in other documents, so we do this in LastRelease().
521 RefPtr
<XRViewerPose
> XRSession::PooledViewerPose(
522 const gfx::Matrix4x4Double
& aTransform
, bool aEmulatedPosition
) {
523 RefPtr
<XRViewerPose
> pose
;
524 if (mViewerPosePool
.Length() > mViewerPosePoolIndex
) {
525 pose
= mViewerPosePool
.ElementAt(mViewerPosePoolIndex
);
526 pose
->Transform()->Update(aTransform
);
527 pose
->SetEmulatedPosition(aEmulatedPosition
);
529 RefPtr
<XRRigidTransform
> transform
=
530 new XRRigidTransform(static_cast<EventTarget
*>(this), aTransform
);
531 nsTArray
<RefPtr
<XRView
>> views
;
533 views
.AppendElement(new XRView(GetParentObject(), XREye::Left
));
534 views
.AppendElement(new XRView(GetParentObject(), XREye::Right
));
536 views
.AppendElement(new XRView(GetParentObject(), XREye::None
));
538 pose
= new XRViewerPose(static_cast<EventTarget
*>(this), transform
,
539 aEmulatedPosition
, views
);
540 mViewerPosePool
.AppendElement(pose
);
543 mViewerPosePoolIndex
++;
544 if (mViewerPosePoolIndex
>= kMaxPoolSize
) {
545 mViewerPosePoolIndex
= 0;
551 RefPtr
<XRFrame
> XRSession::PooledFrame() {
552 RefPtr
<XRFrame
> frame
;
553 if (mFramePool
.Length() > mFramePoolIndex
) {
554 frame
= mFramePool
.ElementAt(mFramePoolIndex
);
556 frame
= new XRFrame(GetParentObject(), this);
557 mFramePool
.AppendElement(frame
);
563 } // namespace mozilla::dom