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