Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / vr / XRSession.cpp
blob41c73031c33e2c32f9cd6f5075afcb2a7f86332a
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"
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 "nsGlobalWindowInner.h"
28 #include "nsIObserverService.h"
29 #include "nsISupportsPrimitives.h"
30 #include "nsRefreshDriver.h"
31 #include "VRDisplayClient.h"
32 #include "VRDisplayPresentation.h"
34 /**
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,
45 DOMEventTargetHelper)
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);
80 if (!win) {
81 return nullptr;
83 Document* doc = aWindow->GetExtantDoc();
84 if (!doc) {
85 return nullptr;
87 nsPresContext* context = doc->GetPresContext();
88 if (!context) {
89 return nullptr;
91 nsRefreshDriver* driver = context->RefreshDriver();
92 if (!driver) {
93 return nullptr;
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),
120 mShutdown(false),
121 mEnded(false),
122 mRefreshDriver(aRefreshDriver),
123 mDisplayClient(aClient),
124 mFrameRequestCallbackCounter(0),
125 mEnabledReferenceSpaceTypes(aEnabledReferenceSpaceTypes.Clone()),
126 mViewerPosePoolIndex(0),
127 mFramePoolIndex(0) {
128 if (aClient) {
129 aClient->SessionStarted(this);
131 mActiveRenderState = new XRRenderState(aWindow, this);
132 mStartTimeStamp = TimeStamp::Now();
133 if (IsImmersive()) {
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,
192 ErrorResult& aRv) {
193 if (mEnded) {
194 aRv.ThrowInvalidStateError(
195 "UpdateRenderState can not be called on an XRSession that has ended.");
196 return;
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 "
203 "being called on.");
204 return;
206 if (aNewState.mInlineVerticalFieldOfView.WasPassed() && IsImmersive()) {
207 aRv.ThrowInvalidStateError(
208 "The inlineVerticalFieldOfView can not be set on an "
209 "XRRenderState for an immersive XRSession.");
210 return;
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) {
244 return;
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()) {
256 double verticalFOV =
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) {
273 depthNear = 0.0f;
275 if (depthFar < 0.0f) {
276 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();
286 if (baseLayer) {
287 if (!IsImmersive() && baseLayer->mCompositionDisabled) {
288 mActiveRenderState->SetCompositionDisabled(true);
289 mActiveRenderState->SetOutputCanvas(baseLayer->GetCanvas());
290 } else {
291 mActiveRenderState->SetCompositionDisabled(false);
292 mActiveRenderState->SetOutputCanvas(nullptr);
293 mDisplayPresentation->UpdateXRWebGLLayer(baseLayer);
295 } // if (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());
303 if (win) {
304 if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
305 js::NotifyAnimationActivity(obj);
308 StartFrame();
312 void XRSession::StartFrame() {
313 if (mShutdown || mEnded) {
314 return;
316 ApplyPendingRenderState();
318 XRWebGLLayer* baseLayer = mActiveRenderState->GetBaseLayer();
319 if (!baseLayer) {
320 return;
323 if (!IsImmersive() && mActiveRenderState->GetOutputCanvas() == nullptr) {
324 return;
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);
372 break;
373 case XRReferenceSpaceType::Local:
374 nativeOrigin = new XRNativeOriginLocal(mDisplayClient);
375 break;
376 case XRReferenceSpaceType::Local_floor:
377 case XRReferenceSpaceType::Bounded_floor:
378 nativeOrigin = new XRNativeOriginLocalFloor(mDisplayClient);
379 break;
380 default:
381 nativeOrigin = new XRNativeOriginFixed(gfx::PointDouble3D());
382 break;
384 } else {
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);
394 } else {
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,
404 ErrorResult& aRv) {
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);
411 if (mEnded) {
412 promise->MaybeRejectWithInvalidStateError(
413 "UpdateTargetFrameRate can not be called on an XRSession that has "
414 "ended.");
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,
433 XRFrame& aFrame) {
434 RefPtr<mozilla::dom::XRFrameRequestCallback> callback = mCallback;
435 callback->Call(aTimeStamp, aFrame);
438 int32_t XRSession::RequestAnimationFrame(XRFrameRequestCallback& aCallback,
439 ErrorResult& aError) {
440 if (mShutdown) {
441 return 0;
444 int32_t handle = ++mFrameRequestCallbackCounter;
446 mFrameRequestCallbacks.AppendElement(XRFrameRequest(aCallback, handle));
448 return handle;
451 void XRSession::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError) {
452 mFrameRequestCallbacks.RemoveElementSorted(aHandle);
455 void XRSession::Shutdown() {
456 mShutdown = true;
457 ExitPresentInternal();
458 mViewerPosePool.Clear();
459 mViewerPosePoolIndex = 0;
460 mFramePool.Clear();
461 mFramePoolIndex = 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() {
474 if (mInputSources) {
475 mInputSources->Clear(this);
477 if (mDisplayClient) {
478 mDisplayClient->SessionEnded(this);
481 if (mXRSystem) {
482 mXRSystem->SessionEnded(this);
485 if (mActiveRenderState) {
486 mActiveRenderState->SessionEnded();
489 if (mPendingRenderState) {
490 mPendingRenderState->SessionEnded();
493 mDisplayPresentation = nullptr;
494 if (!mEnded) {
495 mEnded = true;
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());
511 Shutdown();
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().
518 Shutdown();
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);
528 } else {
529 RefPtr<XRRigidTransform> transform =
530 new XRRigidTransform(static_cast<EventTarget*>(this), aTransform);
531 nsTArray<RefPtr<XRView>> views;
532 if (IsImmersive()) {
533 views.AppendElement(new XRView(GetParentObject(), XREye::Left));
534 views.AppendElement(new XRView(GetParentObject(), XREye::Right));
535 } else {
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;
548 return pose;
551 RefPtr<XRFrame> XRSession::PooledFrame() {
552 RefPtr<XRFrame> frame;
553 if (mFramePool.Length() > mFramePoolIndex) {
554 frame = mFramePool.ElementAt(mFramePoolIndex);
555 } else {
556 frame = new XRFrame(GetParentObject(), this);
557 mFramePool.AppendElement(frame);
560 return frame;
563 } // namespace mozilla::dom