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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "GeckoProfiler.h"
10 #include "VRManagerParent.h"
14 #include "mozilla/ClearOnShutdown.h"
15 #include "mozilla/dom/VRDisplay.h"
16 #include "mozilla/dom/GamepadEventTypes.h"
17 #include "mozilla/layers/TextureHost.h"
18 #include "mozilla/layers/CompositorThread.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/Services.h"
21 #include "mozilla/StaticPrefs_dom.h"
22 #include "mozilla/Telemetry.h"
23 #include "mozilla/Unused.h"
24 #include "nsIObserverService.h"
29 #include "ipc/VRLayerParent.h"
30 #if !defined(MOZ_WIDGET_ANDROID)
31 # include "VRServiceHost.h"
35 # include "CompositorD3D11.h"
36 # include "TextureD3D11.h"
38 # include "gfxWindowsPlatform.h"
39 # include "mozilla/gfx/DeviceManagerDx.h"
40 #elif defined(XP_MACOSX)
41 # include "mozilla/gfx/MacIOSurface.h"
43 #elif defined(MOZ_WIDGET_ANDROID)
46 # include "GeckoVRManager.h"
47 # include "mozilla/java/GeckoSurfaceTextureWrappers.h"
48 # include "mozilla/layers/CompositorThread.h"
49 #endif // defined(MOZ_WIDGET_ANDROID)
51 using namespace mozilla
;
52 using namespace mozilla::gfx
;
53 using namespace mozilla::layers
;
54 using namespace mozilla::gl
;
56 using mozilla::dom::GamepadHandle
;
58 namespace mozilla::gfx
{
61 * When VR content is active, we run the tasks at 1ms
62 * intervals, enabling multiple events to be processed
63 * per frame, such as haptic feedback pulses.
65 const uint32_t kVRActiveTaskInterval
= 1; // milliseconds
68 * When VR content is inactive, we run the tasks at 100ms
69 * intervals, enabling VR display enumeration and
70 * presentation startup to be relatively responsive
71 * while not consuming unnecessary resources.
73 const uint32_t kVRIdleTaskInterval
= 100; // milliseconds
76 * Max frame duration before the watchdog submits a new one.
77 * Probably we can get rid of this when we enforce that SubmitFrame can only be
78 * called in a VRDisplay loop.
80 const double kVRMaxFrameSubmitDuration
= 4000.0f
; // milliseconds
82 static StaticRefPtr
<VRManager
> sVRManagerSingleton
;
84 static bool ValidVRManagerProcess() {
85 return XRE_IsParentProcess() || XRE_IsGPUProcess();
89 VRManager
* VRManager::Get() {
90 MOZ_ASSERT(sVRManagerSingleton
!= nullptr);
91 MOZ_ASSERT(ValidVRManagerProcess());
93 return sVRManagerSingleton
;
97 VRManager
* VRManager::MaybeGet() {
98 MOZ_ASSERT(ValidVRManagerProcess());
100 return sVRManagerSingleton
;
103 Atomic
<uint32_t> VRManager::sDisplayBase(0);
106 uint32_t VRManager::AllocateDisplayID() { return ++sDisplayBase
; }
109 void VRManager::ManagerInit() {
110 MOZ_ASSERT(NS_IsMainThread());
112 if (!ValidVRManagerProcess()) {
116 // Enable gamepad extensions while VR is enabled.
117 // Preference only can be set at the Parent process.
118 if (StaticPrefs::dom_vr_enabled() && XRE_IsParentProcess()) {
119 Preferences::SetBool("dom.gamepad.extensions.enabled", true);
122 if (sVRManagerSingleton
== nullptr) {
123 sVRManagerSingleton
= new VRManager();
124 ClearOnShutdown(&sVRManagerSingleton
);
128 VRManager::VRManager()
129 : mState(VRManagerState::Disabled
),
130 mAccumulator100ms(0.0f
),
131 mRuntimeDetectionRequested(false),
132 mRuntimeDetectionCompleted(false),
133 mEnumerationRequested(false),
134 mEnumerationCompleted(false),
135 mVRDisplaysRequested(false),
136 mVRDisplaysRequestedNonFocus(false),
137 mVRControllersRequested(false),
138 mFrameStarted(false),
140 mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
141 mCurrentSubmitTask(nullptr),
142 mLastSubmittedFrameId(0),
143 mLastStartedFrame(0),
144 mRuntimeSupportFlags(VRDisplayCapabilityFlags::Cap_None
),
147 mHapticPulseRemaining
{},
149 mLastUpdateDisplayInfo
{},
152 MOZ_ASSERT(sVRManagerSingleton
== nullptr);
153 MOZ_ASSERT(NS_IsMainThread());
154 MOZ_ASSERT(ValidVRManagerProcess());
156 #if !defined(MOZ_WIDGET_ANDROID)
157 // XRE_IsGPUProcess() is helping us to check some platforms like
158 // Win 7 try which are not using GPU process but VR process is enabled.
160 StaticPrefs::dom_vr_process_enabled_AtStartup() && XRE_IsGPUProcess();
161 VRServiceHost::Init(mVRProcessEnabled
);
162 mServiceHost
= VRServiceHost::Get();
163 // We must shutdown before VRServiceHost, which is cleared
164 // on ShutdownPhase::XPCOMShutdownFinal, potentially before VRManager.
165 // We hold a reference to VRServiceHost to ensure it stays
166 // alive until we have shut down.
168 // For Android, there is no VRProcess available and no VR service is
169 // created, so default to false.
170 mVRProcessEnabled
= false;
171 #endif // !defined(MOZ_WIDGET_ANDROID)
173 nsCOMPtr
<nsIObserverService
> service
= services::GetObserverService();
175 service
->AddObserver(this, "application-background", false);
176 service
->AddObserver(this, "application-foreground", false);
180 void VRManager::OpenShmem() {
181 if (mShmem
== nullptr) {
182 mShmem
= new VRShMem(nullptr, true /*aRequiresMutex*/);
184 #if !defined(MOZ_WIDGET_ANDROID)
185 mShmem
->CreateShMem(mVRProcessEnabled
/*aCreateOnSharedMemory*/);
186 // The VR Service accesses all hardware from a separate process
187 // and replaces the other VRManager when enabled.
188 // If the VR process is not enabled, create an in-process VRService.
189 if (!mVRProcessEnabled
) {
190 // If the VR process is disabled, attempt to create a
191 // VR service within the current process
192 mServiceHost
->CreateService(mShmem
->GetExternalShmem());
196 mShmem
->CreateShMemForAndroid();
199 mShmem
->ClearShMem();
202 // Reset local information for new connection
203 mDisplayInfo
.Clear();
204 mLastUpdateDisplayInfo
.Clear();
205 mFrameStarted
= false;
206 mBrowserState
.Clear();
207 mLastSensorState
.Clear();
208 mEnumerationCompleted
= false;
209 mDisplayInfo
.mGroupMask
= kVRGroupContent
;
212 void VRManager::CloseShmem() {
213 if (mShmem
!= nullptr) {
214 mShmem
->CloseShMem();
220 VRManager::~VRManager() {
221 MOZ_ASSERT(NS_IsMainThread());
222 MOZ_ASSERT(mState
== VRManagerState::Disabled
);
224 nsCOMPtr
<nsIObserverService
> service
= services::GetObserverService();
226 service
->RemoveObserver(this, "application-background");
227 service
->RemoveObserver(this, "application-foreground");
230 #if !defined(MOZ_WIDGET_ANDROID)
231 mServiceHost
->Shutdown();
236 void VRManager::AddLayer(VRLayerParent
* aLayer
) {
237 mLayers
.AppendElement(aLayer
);
238 mDisplayInfo
.mPresentingGroups
|= aLayer
->GetGroup();
239 if (mLayers
.Length() == 1) {
243 // Ensure that the content process receives the change immediately
244 if (mState
!= VRManagerState::Enumeration
&&
245 mState
!= VRManagerState::RuntimeDetection
) {
246 DispatchVRDisplayInfoUpdate();
250 void VRManager::RemoveLayer(VRLayerParent
* aLayer
) {
251 mLayers
.RemoveElement(aLayer
);
252 if (mLayers
.Length() == 0) {
255 mDisplayInfo
.mPresentingGroups
= 0;
256 for (auto layer
: mLayers
) {
257 mDisplayInfo
.mPresentingGroups
|= layer
->GetGroup();
260 // Ensure that the content process receives the change immediately
261 if (mState
!= VRManagerState::Enumeration
&&
262 mState
!= VRManagerState::RuntimeDetection
) {
263 DispatchVRDisplayInfoUpdate();
267 void VRManager::AddVRManagerParent(VRManagerParent
* aVRManagerParent
) {
268 mVRManagerParents
.Insert(aVRManagerParent
);
271 void VRManager::RemoveVRManagerParent(VRManagerParent
* aVRManagerParent
) {
272 mVRManagerParents
.Remove(aVRManagerParent
);
273 if (mVRManagerParents
.IsEmpty()) {
278 void VRManager::UpdateRequestedDevices() {
279 bool bHaveEventListener
= false;
280 bool bHaveEventListenerNonFocus
= false;
281 bool bHaveControllerListener
= false;
283 for (VRManagerParent
* vmp
: mVRManagerParents
) {
284 bHaveEventListener
|= vmp
->HaveEventListener() && vmp
->GetVRActiveStatus();
285 bHaveEventListenerNonFocus
|=
286 vmp
->HaveEventListener() && !vmp
->GetVRActiveStatus();
287 bHaveControllerListener
|= vmp
->HaveControllerListener();
290 mVRDisplaysRequested
= bHaveEventListener
;
291 mVRDisplaysRequestedNonFocus
= bHaveEventListenerNonFocus
;
292 // We only currently allow controllers to be used when
293 // also activating a VR display
294 mVRControllersRequested
= mVRDisplaysRequested
&& bHaveControllerListener
;
298 * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz).
299 * This must be called even when no WebVR site is active.
300 * If we don't have a 2d display attached to the system, we can call this
301 * at the VR display's native refresh rate.
303 void VRManager::NotifyVsync(const TimeStamp
& aVsyncTimestamp
) {
304 if (mState
!= VRManagerState::Active
) {
308 * If the display isn't presenting, refresh the sensors and trigger
309 * VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
311 if (mDisplayInfo
.mPresentingGroups
== 0) {
316 void VRManager::StartTasks() {
318 mTaskInterval
= GetOptimalTaskInterval();
319 mTaskTimer
= NS_NewTimer();
320 mTaskTimer
->SetTarget(CompositorThread());
321 mTaskTimer
->InitWithNamedFuncCallback(
322 TaskTimerCallback
, this, mTaskInterval
,
323 nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP
,
324 "VRManager::TaskTimerCallback");
328 void VRManager::StopTasks() {
330 mTaskTimer
->Cancel();
331 mTaskTimer
= nullptr;
336 void VRManager::TaskTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
338 * It is safe to use the pointer passed in aClosure to reference the
339 * VRManager object as the timer is canceled in VRManager::Destroy.
340 * VRManager::Destroy set mState to VRManagerState::Disabled, which
341 * is asserted in the VRManager destructor, guaranteeing that this
342 * functions runs if and only if the VRManager object is valid.
344 VRManager
* self
= static_cast<VRManager
*>(aClosure
);
347 if (self
->mAppPaused
) {
348 // When the apps goes the background (e.g. Android) we should stop the
351 self
->mState
= VRManagerState::Idle
;
355 void VRManager::RunTasks() {
356 // Will be called once every 1ms when a VR presentation
357 // is active or once per vsync when a VR presentation is
360 if (mState
== VRManagerState::Disabled
) {
361 // We may have been destroyed but still have messages
362 // in the queue from mTaskTimer. Bail out to avoid
367 TimeStamp now
= TimeStamp::Now();
368 double lastTickMs
= mAccumulator100ms
;
369 double deltaTime
= 0.0f
;
370 if (!mLastTickTime
.IsNull()) {
371 deltaTime
= (now
- mLastTickTime
).ToMilliseconds();
373 mAccumulator100ms
+= deltaTime
;
376 if (deltaTime
> 0.0f
&& floor(mAccumulator100ms
) != floor(lastTickMs
)) {
377 // Even if more than 1 ms has passed, we will only
378 // execute Run1msTasks() once.
379 Run1msTasks(deltaTime
);
382 if (floor(mAccumulator100ms
* 0.1f
) != floor(lastTickMs
* 0.1f
)) {
383 // Even if more than 10 ms has passed, we will only
384 // execute Run10msTasks() once.
388 if (mAccumulator100ms
>= 100.0f
) {
389 // Even if more than 100 ms has passed, we will only
390 // execute Run100msTasks() once.
392 mAccumulator100ms
= fmod(mAccumulator100ms
, 100.0f
);
395 uint32_t optimalTaskInterval
= GetOptimalTaskInterval();
396 if (mTaskTimer
&& optimalTaskInterval
!= mTaskInterval
) {
397 mTaskTimer
->SetDelay(optimalTaskInterval
);
398 mTaskInterval
= optimalTaskInterval
;
402 uint32_t VRManager::GetOptimalTaskInterval() {
404 * When either VR content is detected or VR hardware
405 * has already been activated, we schedule tasks more
408 bool wantGranularTasks
= mVRDisplaysRequested
|| mVRControllersRequested
||
409 mDisplayInfo
.mDisplayID
!= 0;
410 if (wantGranularTasks
) {
411 return kVRActiveTaskInterval
;
414 return kVRIdleTaskInterval
;
418 * Run1msTasks() is guaranteed not to be
419 * called more than once within 1ms.
420 * When VR is not active, this will be
421 * called once per VSync if it wasn't
422 * called within the last 1ms.
424 void VRManager::Run1msTasks(double aDeltaTime
) { UpdateHaptics(aDeltaTime
); }
427 * Run10msTasks() is guaranteed not to be
428 * called more than once within 10ms.
429 * When VR is not active, this will be
430 * called once per VSync if it wasn't
431 * called within the last 10ms.
433 void VRManager::Run10msTasks() {
434 UpdateRequestedDevices();
436 ExpireNavigationTransition();
442 * Run100msTasks() is guaranteed not to be
443 * called more than once within 100ms.
444 * When VR is not active, this will be
445 * called once per VSync if it wasn't
446 * called within the last 100ms.
448 void VRManager::Run100msTasks() {
449 // We must continually refresh the VR display enumeration to check
450 // for events that we must fire such as Window.onvrdisplayconnect
451 // Note that enumeration itself may activate display hardware, such
452 // as Oculus, so we only do this when we know we are displaying content
453 // that is looking for VR displays.
454 #if !defined(MOZ_WIDGET_ANDROID)
455 mServiceHost
->Refresh();
456 CheckForPuppetCompletion();
458 ProcessManagerState();
461 void VRManager::CheckForInactiveTimeout() {
462 // Shut down the VR devices when not in use
463 if (mVRDisplaysRequested
|| mVRDisplaysRequestedNonFocus
||
464 mVRControllersRequested
|| mEnumerationRequested
||
465 mRuntimeDetectionRequested
|| mState
== VRManagerState::Enumeration
||
466 mState
== VRManagerState::RuntimeDetection
) {
467 // We are using a VR device, keep it alive
468 mLastActiveTime
= TimeStamp::Now();
469 } else if (mLastActiveTime
.IsNull()) {
472 TimeDuration duration
= TimeStamp::Now() - mLastActiveTime
;
473 if (duration
.ToMilliseconds() > StaticPrefs::dom_vr_inactive_timeout()) {
475 // We must not throttle the next enumeration request
476 // after an idle timeout, as it may result in the
477 // user needing to refresh the browser to detect
478 // VR hardware when leaving and returning to a VR
480 mLastDisplayEnumerationTime
= TimeStamp();
485 void VRManager::CheckForShutdown() {
486 // Check for remote end shutdown
487 if (mDisplayInfo
.mDisplayState
.shutdown
) {
492 #if !defined(MOZ_WIDGET_ANDROID)
493 void VRManager::CheckForPuppetCompletion() {
494 // Notify content process about completion of puppet test resets
495 if (mState
!= VRManagerState::Active
) {
496 for (const auto& key
: mManagerParentsWaitingForPuppetReset
) {
497 Unused
<< key
->SendNotifyPuppetResetComplete();
499 mManagerParentsWaitingForPuppetReset
.Clear();
501 // Notify content process about completion of puppet test scripts
502 if (mManagerParentRunningPuppet
) {
503 mServiceHost
->CheckForPuppetCompletion();
507 void VRManager::NotifyPuppetComplete() {
508 // Notify content process about completion of puppet test scripts
509 if (mManagerParentRunningPuppet
) {
510 Unused
<< mManagerParentRunningPuppet
511 ->SendNotifyPuppetCommandBufferCompleted(true);
512 mManagerParentRunningPuppet
= nullptr;
516 #endif // !defined(MOZ_WIDGET_ANDROID)
518 void VRManager::StartFrame() {
519 if (mState
!= VRManagerState::Active
) {
522 AUTO_PROFILER_TRACING_MARKER("VR", "GetSensorState", OTHER
);
525 * Do not start more VR frames until the last submitted frame is already
526 * processed, or the last has stalled for more than
527 * kVRMaxFrameSubmitDuration milliseconds.
529 TimeStamp now
= TimeStamp::Now();
530 const TimeStamp lastFrameStart
=
531 mLastFrameStart
[mDisplayInfo
.mFrameId
% kVRMaxLatencyFrames
];
532 const bool isPresenting
= mLastUpdateDisplayInfo
.GetPresentingGroups() != 0;
534 lastFrameStart
.IsNull() ? 0.0 : (now
- lastFrameStart
).ToMilliseconds();
535 if (isPresenting
&& mLastStartedFrame
> 0 &&
536 mDisplayInfo
.mDisplayState
.lastSubmittedFrameId
< mLastStartedFrame
&&
537 duration
< kVRMaxFrameSubmitDuration
) {
541 mDisplayInfo
.mFrameId
++;
542 size_t bufferIndex
= mDisplayInfo
.mFrameId
% kVRMaxLatencyFrames
;
543 mDisplayInfo
.mLastSensorState
[bufferIndex
] = mLastSensorState
;
544 mLastFrameStart
[bufferIndex
] = now
;
545 mFrameStarted
= true;
546 mLastStartedFrame
= mDisplayInfo
.mFrameId
;
548 DispatchVRDisplayInfoUpdate();
551 void VRManager::DetectRuntimes() {
552 if (mState
== VRManagerState::RuntimeDetection
) {
553 // Runtime detection has already been started.
554 // This additional request will also receive the
555 // result from the first request.
559 // Detect XR runtimes to determine if they are
560 // capable of supporting VR or AR sessions, while
561 // avoiding activating any XR devices or persistent
562 // background software.
563 if (mRuntimeDetectionCompleted
) {
564 // We have already detected runtimes, so we can
565 // immediately respond with the same results.
566 // This will require the user to restart the browser
567 // after installing or removing an XR device
569 DispatchRuntimeCapabilitiesUpdate();
572 mRuntimeDetectionRequested
= true;
573 ProcessManagerState();
576 void VRManager::EnumerateDevices() {
577 if (mState
== VRManagerState::Enumeration
||
578 (mRuntimeDetectionCompleted
&&
579 (mVRDisplaysRequested
|| mEnumerationRequested
))) {
580 // Enumeration has already been started.
581 // This additional request will also receive the
582 // result from the first request.
585 // Activate XR runtimes and enumerate XR devices.
586 mEnumerationRequested
= true;
587 ProcessManagerState();
590 void VRManager::ProcessManagerState() {
592 case VRManagerState::Disabled
:
593 ProcessManagerState_Disabled();
595 case VRManagerState::Idle
:
596 ProcessManagerState_Idle();
598 case VRManagerState::RuntimeDetection
:
599 ProcessManagerState_DetectRuntimes();
601 case VRManagerState::Enumeration
:
602 ProcessManagerState_Enumeration();
604 case VRManagerState::Active
:
605 ProcessManagerState_Active();
607 case VRManagerState::Stopping
:
608 ProcessManagerState_Stopping();
611 CheckForInactiveTimeout();
615 void VRManager::ProcessManagerState_Disabled() {
616 MOZ_ASSERT(mState
== VRManagerState::Disabled
);
618 if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) {
622 if (mRuntimeDetectionRequested
|| mEnumerationRequested
||
623 mVRDisplaysRequested
) {
625 mState
= VRManagerState::Idle
;
629 void VRManager::ProcessManagerState_Stopping() {
630 MOZ_ASSERT(mState
== VRManagerState::Stopping
);
633 * In the case of Desktop, the VRService shuts itself down.
634 * Before it's finished stopping, it sets a flag in the ShMem
635 * to let VRManager know that it's done. VRManager watches for
636 * this flag and transitions out of the VRManagerState::Stopping
637 * state to VRManagerState::Idle.
639 #if defined(MOZ_WIDGET_ANDROID)
640 // On Android, the VR service never actually shuts
641 // down or requests VRManager to stop.
643 #endif // defined(MOZ_WIDGET_ANDROID)
646 void VRManager::ProcessManagerState_Idle_StartEnumeration() {
647 MOZ_ASSERT(mState
== VRManagerState::Idle
);
649 if (!mEarliestRestartTime
.IsNull() &&
650 mEarliestRestartTime
> TimeStamp::Now()) {
651 // When the VR Service shuts down it informs us of how long we
652 // must wait until we can re-start it.
653 // We must wait until mEarliestRestartTime before attempting
654 // to enumerate again.
659 * Throttle the rate of enumeration to the interval set in
660 * VRDisplayEnumerateInterval
662 if (!mLastDisplayEnumerationTime
.IsNull()) {
663 TimeDuration duration
= TimeStamp::Now() - mLastDisplayEnumerationTime
;
664 if (duration
.ToMilliseconds() <
665 StaticPrefs::dom_vr_display_enumerate_interval()) {
671 * If we get this far, don't try again until
672 * the VRDisplayEnumerateInterval elapses
674 mLastDisplayEnumerationTime
= TimeStamp::Now();
678 mEnumerationRequested
= false;
679 // We must block until enumeration has completed in order
680 // to signal that the WebVR promise should be resolved at the
682 #if defined(MOZ_WIDGET_ANDROID)
683 // In Android, we need to make sure calling
684 // GeckoVRManager::SetExternalContext() from an external VR service
685 // before doing enumeration.
686 if (!mShmem
->GetExternalShmem()) {
687 mShmem
->CreateShMemForAndroid();
689 if (mShmem
->GetExternalShmem()) {
690 mState
= VRManagerState::Enumeration
;
692 // Not connected to shmem, so no devices to enumerate.
693 mDisplayInfo
.Clear();
694 DispatchVRDisplayInfoUpdate();
701 * We must start the VR Service thread
702 * and VR Process before enumeration.
703 * We don't want to start this until we will
704 * actualy enumerate, to avoid continuously
705 * re-launching the thread/process when
706 * no hardware is found or a VR software update
709 mServiceHost
->StartService();
710 mState
= VRManagerState::Enumeration
;
711 #endif // MOZ_WIDGET_ANDROID
714 void VRManager::ProcessManagerState_Idle_StartRuntimeDetection() {
715 MOZ_ASSERT(mState
== VRManagerState::Idle
);
718 mBrowserState
.detectRuntimesOnly
= true;
719 mRuntimeDetectionRequested
= false;
721 // We must block until enumeration has completed in order
722 // to signal that the WebVR promise should be resolved at the
724 #if defined(MOZ_WIDGET_ANDROID)
725 // In Android, we need to make sure calling
726 // GeckoVRManager::SetExternalContext() from an external VR service
727 // before doing enumeration.
728 if (!mShmem
->GetExternalShmem()) {
729 mShmem
->CreateShMemForAndroid();
731 if (mShmem
->GetExternalShmem()) {
732 mState
= VRManagerState::RuntimeDetection
;
734 // Not connected to shmem, so no runtimes to detect.
735 mRuntimeSupportFlags
= VRDisplayCapabilityFlags::Cap_None
;
736 mRuntimeDetectionCompleted
= true;
737 DispatchRuntimeCapabilitiesUpdate();
744 * We must start the VR Service thread
745 * and VR Process before enumeration.
746 * We don't want to start this until we will
747 * actualy enumerate, to avoid continuously
748 * re-launching the thread/process when
749 * no hardware is found or a VR software update
752 mServiceHost
->StartService();
753 mState
= VRManagerState::RuntimeDetection
;
754 #endif // MOZ_WIDGET_ANDROID
757 void VRManager::ProcessManagerState_Idle() {
758 MOZ_ASSERT(mState
== VRManagerState::Idle
);
760 if (!mRuntimeDetectionCompleted
) {
761 // Check if we should start detecting runtimes
762 // We must alwasy detect runtimes before doing anything
763 // else with the VR process.
764 // This will happen only once per browser startup.
765 if (mRuntimeDetectionRequested
|| mEnumerationRequested
) {
766 ProcessManagerState_Idle_StartRuntimeDetection();
771 // Check if we should start activating enumerating XR hardware
772 if (mRuntimeDetectionCompleted
&&
773 (mVRDisplaysRequested
|| mEnumerationRequested
)) {
774 ProcessManagerState_Idle_StartEnumeration();
778 void VRManager::ProcessManagerState_DetectRuntimes() {
779 MOZ_ASSERT(mState
== VRManagerState::RuntimeDetection
);
780 MOZ_ASSERT(mShmem
!= nullptr);
783 if (mEnumerationCompleted
) {
785 * When mBrowserState.detectRuntimesOnly is set, the
786 * VRService and VR process will shut themselves down
787 * automatically after detecting runtimes.
788 * mEnumerationCompleted is also used in this case,
789 * but to mean "enumeration of runtimes" not
790 * "enumeration of VR devices".
792 * We set mState to `VRManagerState::Stopping`
793 * to make sure that we don't try to do anything
794 * else with the active VRService until it has stopped.
795 * We must start another one when an XR session will be
798 * This logic is optimized for the WebXR design, but still
799 * works for WebVR so it can continue to function until
800 * deprecated and removed.
802 mState
= VRManagerState::Stopping
;
803 mRuntimeSupportFlags
= mDisplayInfo
.mDisplayState
.capabilityFlags
&
804 (VRDisplayCapabilityFlags::Cap_ImmersiveVR
|
805 VRDisplayCapabilityFlags::Cap_ImmersiveAR
|
806 VRDisplayCapabilityFlags::Cap_Inline
);
807 mRuntimeDetectionCompleted
= true;
808 DispatchRuntimeCapabilitiesUpdate();
812 void VRManager::ProcessManagerState_Enumeration() {
813 MOZ_ASSERT(mState
== VRManagerState::Enumeration
);
814 MOZ_ASSERT(mShmem
!= nullptr);
817 if (mEnumerationCompleted
) {
818 if (mDisplayInfo
.mDisplayState
.isConnected
) {
819 mDisplayInfo
.mDisplayID
= VRManager::AllocateDisplayID();
820 mState
= VRManagerState::Active
;
822 mDisplayInfo
.Clear();
823 mState
= VRManagerState::Stopping
;
825 DispatchVRDisplayInfoUpdate();
829 void VRManager::ProcessManagerState_Active() {
830 MOZ_ASSERT(mState
== VRManagerState::Active
);
832 if (mDisplayInfo
!= mLastUpdateDisplayInfo
) {
833 // While the display is active, send continuous updates
834 DispatchVRDisplayInfoUpdate();
838 void VRManager::DispatchVRDisplayInfoUpdate() {
839 for (VRManagerParent
* vmp
: mVRManagerParents
) {
840 Unused
<< vmp
->SendUpdateDisplayInfo(mDisplayInfo
);
842 mLastUpdateDisplayInfo
= mDisplayInfo
;
845 void VRManager::DispatchRuntimeCapabilitiesUpdate() {
846 VRDisplayCapabilityFlags flags
= mRuntimeSupportFlags
;
847 if (StaticPrefs::dom_vr_always_support_vr()) {
848 flags
|= VRDisplayCapabilityFlags::Cap_ImmersiveVR
;
851 if (StaticPrefs::dom_vr_always_support_ar()) {
852 flags
|= VRDisplayCapabilityFlags::Cap_ImmersiveAR
;
855 for (VRManagerParent
* vmp
: mVRManagerParents
) {
856 Unused
<< vmp
->SendUpdateRuntimeCapabilities(flags
);
860 void VRManager::StopAllHaptics() {
861 if (mState
!= VRManagerState::Active
) {
864 for (size_t i
= 0; i
< mozilla::ArrayLength(mBrowserState
.hapticState
); i
++) {
870 void VRManager::VibrateHaptic(GamepadHandle aGamepadHandle
,
871 uint32_t aHapticIndex
, double aIntensity
,
873 const VRManagerPromise
& aPromise
)
876 if (mState
!= VRManagerState::Active
) {
879 // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
880 // ranges based on displayID. We must translate this to the indexes
881 // understood by VRDisplayExternal.
882 uint32_t controllerBaseIndex
=
883 kVRControllerMaxCount
* mDisplayInfo
.mDisplayID
;
884 uint32_t controllerIndex
= aGamepadHandle
.GetValue() - controllerBaseIndex
;
886 TimeStamp now
= TimeStamp::Now();
887 size_t bestSlotIndex
= 0;
888 // Default to an empty slot, or the slot holding the oldest haptic pulse
889 for (size_t i
= 0; i
< mozilla::ArrayLength(mBrowserState
.hapticState
); i
++) {
890 const VRHapticState
& state
= mBrowserState
.hapticState
[i
];
891 if (state
.inputFrameID
== 0) {
892 // Unused slot, use it
896 if (mHapticPulseRemaining
[i
] < mHapticPulseRemaining
[bestSlotIndex
]) {
897 // If no empty slots are available, fall back to overriding
898 // the pulse which is ending soonest.
902 // Override the last pulse on the same actuator if present.
903 for (size_t i
= 0; i
< mozilla::ArrayLength(mBrowserState
.hapticState
); i
++) {
904 const VRHapticState
& state
= mBrowserState
.hapticState
[i
];
905 if (state
.inputFrameID
== 0) {
906 // This is an empty slot -- no match
909 if (state
.controllerIndex
== controllerIndex
&&
910 state
.hapticIndex
== aHapticIndex
) {
911 // Found pulse on same actuator -- let's override it.
915 ClearHapticSlot(bestSlotIndex
);
917 // Populate the selected slot with new haptic state
918 size_t bufferIndex
= mDisplayInfo
.mFrameId
% kVRMaxLatencyFrames
;
919 VRHapticState
& bestSlot
= mBrowserState
.hapticState
[bestSlotIndex
];
920 bestSlot
.inputFrameID
=
921 mDisplayInfo
.mLastSensorState
[bufferIndex
].inputFrameID
;
922 bestSlot
.controllerIndex
= controllerIndex
;
923 bestSlot
.hapticIndex
= aHapticIndex
;
924 bestSlot
.pulseStart
= (float)(now
- mLastFrameStart
[bufferIndex
]).ToSeconds();
925 bestSlot
.pulseDuration
=
926 (float)aDuration
* 0.001f
; // Convert from ms to seconds
927 bestSlot
.pulseIntensity
= (float)aIntensity
;
929 mHapticPulseRemaining
[bestSlotIndex
] = aDuration
;
930 MOZ_ASSERT(bestSlotIndex
<= mHapticPromises
.Length());
931 if (bestSlotIndex
== mHapticPromises
.Length()) {
932 mHapticPromises
.AppendElement(
933 UniquePtr
<VRManagerPromise
>(new VRManagerPromise(aPromise
)));
935 mHapticPromises
[bestSlotIndex
] =
936 UniquePtr
<VRManagerPromise
>(new VRManagerPromise(aPromise
));
941 void VRManager::StopVibrateHaptic(GamepadHandle aGamepadHandle
) {
942 if (mState
!= VRManagerState::Active
) {
945 // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
946 // ranges based on displayID. We must translate this to the indexes
947 // understood by VRDisplayExternal.
948 uint32_t controllerBaseIndex
=
949 kVRControllerMaxCount
* mDisplayInfo
.mDisplayID
;
950 uint32_t controllerIndex
= aGamepadHandle
.GetValue() - controllerBaseIndex
;
952 for (size_t i
= 0; i
< mozilla::ArrayLength(mBrowserState
.hapticState
); i
++) {
953 VRHapticState
& state
= mBrowserState
.hapticState
[i
];
954 if (state
.controllerIndex
== controllerIndex
) {
955 memset(&state
, 0, sizeof(VRHapticState
));
961 void VRManager::NotifyVibrateHapticCompleted(const VRManagerPromise
& aPromise
) {
962 aPromise
.mParent
->SendReplyGamepadVibrateHaptic(aPromise
.mPromiseID
);
965 void VRManager::StartVRNavigation(const uint32_t& aDisplayID
) {
966 if (mState
!= VRManagerState::Active
) {
970 * We only support a single VRSession with a single VR display at a
971 * time; however, due to the asynchronous nature of the API, it's possible
972 * that the previously used VR display was a different one than the one now
973 * allocated. We catch these cases to avoid automatically activating the new
974 * VR displays. This situation is expected to be very rare and possibly never
975 * seen. Perhaps further simplification could be made in the content process
976 * code which passes around displayID's that may no longer be needed.
978 if (mDisplayInfo
.GetDisplayID() != aDisplayID
) {
981 mBrowserState
.navigationTransitionActive
= true;
982 mVRNavigationTransitionEnd
= TimeStamp();
986 void VRManager::StopVRNavigation(const uint32_t& aDisplayID
,
987 const TimeDuration
& aTimeout
) {
988 if (mState
!= VRManagerState::Active
) {
991 if (mDisplayInfo
.GetDisplayID() != aDisplayID
) {
994 if (aTimeout
.ToMilliseconds() <= 0) {
995 mBrowserState
.navigationTransitionActive
= false;
996 mVRNavigationTransitionEnd
= TimeStamp();
999 mVRNavigationTransitionEnd
= TimeStamp::Now() + aTimeout
;
1002 #if !defined(MOZ_WIDGET_ANDROID)
1004 bool VRManager::RunPuppet(const nsTArray
<uint64_t>& aBuffer
,
1005 VRManagerParent
* aManagerParent
) {
1006 if (!StaticPrefs::dom_vr_puppet_enabled()) {
1007 // Sanity check to ensure that a compromised content process
1008 // can't use this to escalate permissions.
1011 if (mManagerParentRunningPuppet
!= nullptr) {
1012 // Only one parent may run a puppet at a time
1015 mManagerParentRunningPuppet
= aManagerParent
;
1016 mServiceHost
->PuppetSubmit(aBuffer
);
1020 void VRManager::ResetPuppet(VRManagerParent
* aManagerParent
) {
1021 if (!StaticPrefs::dom_vr_puppet_enabled()) {
1025 mManagerParentsWaitingForPuppetReset
.Insert(aManagerParent
);
1026 if (mManagerParentRunningPuppet
!= nullptr) {
1027 Unused
<< mManagerParentRunningPuppet
1028 ->SendNotifyPuppetCommandBufferCompleted(false);
1029 mManagerParentRunningPuppet
= nullptr;
1031 mServiceHost
->PuppetReset();
1032 // In the event that we are shut down, the task timer won't be running
1033 // to trigger CheckForPuppetCompletion.
1034 // In this case, CheckForPuppetCompletion() would immediately resolve
1035 // the promises for mManagerParentsWaitingForPuppetReset.
1036 // We can simply call it once here to handle that case.
1037 CheckForPuppetCompletion();
1040 #endif // !defined(MOZ_WIDGET_ANDROID)
1042 void VRManager::PullState(
1043 const std::function
<bool()>& aWaitCondition
/* = nullptr */) {
1044 if (mShmem
!= nullptr) {
1045 mShmem
->PullSystemState(mDisplayInfo
.mDisplayState
, mLastSensorState
,
1046 mDisplayInfo
.mControllerState
,
1047 mEnumerationCompleted
, aWaitCondition
);
1051 void VRManager::PushState(bool aNotifyCond
) {
1052 if (mShmem
!= nullptr) {
1053 mShmem
->PushBrowserState(mBrowserState
, aNotifyCond
);
1057 void VRManager::Destroy() {
1058 if (mState
== VRManagerState::Disabled
) {
1063 mState
= VRManagerState::Disabled
;
1066 void VRManager::Shutdown() {
1067 if (mState
== VRManagerState::Disabled
|| mState
== VRManagerState::Idle
) {
1071 if (mDisplayInfo
.mDisplayState
.shutdown
) {
1072 // Shutdown was requested by VR Service, so we must throttle
1073 // as requested by the VR Service
1074 TimeStamp now
= TimeStamp::Now();
1075 mEarliestRestartTime
=
1076 now
+ TimeDuration::FromMilliseconds(
1077 (double)mDisplayInfo
.mDisplayState
.minRestartInterval
);
1082 CancelCurrentSubmitTask();
1083 ShutdownSubmitThread();
1085 mDisplayInfo
.Clear();
1086 mEnumerationCompleted
= false;
1088 if (mState
== VRManagerState::RuntimeDetection
) {
1090 * We have failed to detect runtimes before shutting down.
1091 * Ensure that promises are resolved
1093 * This call to DispatchRuntimeCapabilitiesUpdate will only
1094 * happen when we have failed to detect runtimes. In that case,
1095 * mRuntimeSupportFlags will be 0 and send the correct message
1096 * to the content process.
1098 * When we are successful, we store the result in mRuntimeSupportFlags
1099 * and never try again unless the browser is restarted. mRuntimeSupportFlags
1100 * is never reset back to 0 in that case but we will never re-enter the
1101 * VRManagerState::RuntimeDetection state and hit this code path again.
1103 DispatchRuntimeCapabilitiesUpdate();
1106 if (mState
== VRManagerState::Enumeration
) {
1107 // We have failed to enumerate VR devices before shutting down.
1108 // Ensure that promises are resolved
1109 DispatchVRDisplayInfoUpdate();
1112 #if !defined(MOZ_WIDGET_ANDROID)
1113 mServiceHost
->StopService();
1115 mState
= VRManagerState::Idle
;
1117 // We will close Shmem in the DTOR to avoid
1118 // mSubmitThread is still running but its shmem
1119 // has been released.
1122 void VRManager::ShutdownVRManagerParents() {
1123 // Close removes the CanvasParent from the set so take a copy first.
1124 const auto parents
= ToTArray
<nsTArray
<VRManagerParent
*>>(mVRManagerParents
);
1125 for (RefPtr
<VRManagerParent
> vrManagerParent
: parents
) {
1126 vrManagerParent
->Close();
1129 MOZ_DIAGNOSTIC_ASSERT(mVRManagerParents
.IsEmpty(),
1130 "Closing should have cleared all entries.");
1133 void VRManager::CheckWatchDog() {
1135 * We will trigger a new frame immediately after a successful frame
1136 * texture submission. If content fails to call VRDisplay.submitFrame
1137 * after dom.vr.display.rafMaxDuration milliseconds has elapsed since the
1138 * last VRDisplay.requestAnimationFrame, we act as a "watchdog" and
1139 * kick-off a new VRDisplay.requestAnimationFrame to avoid a render loop
1140 * stall and to give content a chance to recover.
1142 * If the lower level VR platform API's are rejecting submitted frames,
1143 * such as when the Oculus "Health and Safety Warning" is displayed,
1144 * we will not kick off the next frame immediately after
1145 * VRDisplay.submitFrame as it would result in an unthrottled render loop
1146 * that would free run at potentially extreme frame rates. To ensure that
1147 * content has a chance to resume its presentation when the frames are
1148 * accepted once again, we rely on this "watchdog" to act as a VR refresh
1149 * driver cycling at a rate defined by dom.vr.display.rafMaxDuration.
1151 * This number must be larger than the slowest expected frame time during
1152 * normal VR presentation, but small enough not to break content that
1153 * makes assumptions of reasonably minimal VSync rate.
1155 * The slowest expected refresh rate for a VR display currently is an
1156 * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
1157 * A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a
1158 * 20hz rate, which avoids inadvertent triggering of the watchdog during
1159 * Oculus ASW even if every second frame is dropped.
1161 if (mState
!= VRManagerState::Active
) {
1164 bool bShouldStartFrame
= false;
1166 // If content fails to call VRDisplay.submitFrame, we must eventually
1167 // time-out and trigger a new frame.
1168 TimeStamp lastFrameStart
=
1169 mLastFrameStart
[mDisplayInfo
.mFrameId
% kVRMaxLatencyFrames
];
1170 if (lastFrameStart
.IsNull()) {
1171 bShouldStartFrame
= true;
1173 TimeDuration duration
= TimeStamp::Now() - lastFrameStart
;
1174 if (duration
.ToMilliseconds() >
1175 StaticPrefs::dom_vr_display_rafMaxDuration()) {
1176 bShouldStartFrame
= true;
1180 if (bShouldStartFrame
) {
1185 void VRManager::ExpireNavigationTransition() {
1186 if (mState
!= VRManagerState::Active
) {
1189 if (!mVRNavigationTransitionEnd
.IsNull() &&
1190 TimeStamp::Now() > mVRNavigationTransitionEnd
) {
1191 mBrowserState
.navigationTransitionActive
= false;
1195 void VRManager::UpdateHaptics(double aDeltaTime
) {
1196 if (mState
!= VRManagerState::Active
) {
1199 bool bNeedPush
= false;
1200 // Check for any haptic pulses that have ended and clear them
1201 for (size_t i
= 0; i
< mozilla::ArrayLength(mBrowserState
.hapticState
); i
++) {
1202 const VRHapticState
& state
= mBrowserState
.hapticState
[i
];
1203 if (state
.inputFrameID
== 0) {
1204 // Nothing in this slot
1207 mHapticPulseRemaining
[i
] -= aDeltaTime
;
1208 if (mHapticPulseRemaining
[i
] <= 0.0f
) {
1209 // The pulse has finished
1219 void VRManager::ClearHapticSlot(size_t aSlot
) {
1220 MOZ_ASSERT(aSlot
< mozilla::ArrayLength(mBrowserState
.hapticState
));
1221 memset(&mBrowserState
.hapticState
[aSlot
], 0, sizeof(VRHapticState
));
1222 mHapticPulseRemaining
[aSlot
] = 0.0f
;
1223 if (aSlot
< mHapticPromises
.Length() && mHapticPromises
[aSlot
]) {
1224 NotifyVibrateHapticCompleted(*(mHapticPromises
[aSlot
]));
1225 mHapticPromises
[aSlot
] = nullptr;
1229 void VRManager::ShutdownSubmitThread() {
1230 if (mSubmitThread
) {
1231 mSubmitThread
->Shutdown();
1232 mSubmitThread
= nullptr;
1236 void VRManager::StartPresentation() {
1237 if (mState
!= VRManagerState::Active
) {
1240 if (mBrowserState
.presentationActive
) {
1244 mTelemetry
.mPresentationStart
= TimeStamp::Now();
1246 // Indicate that we are ready to start immersive mode
1247 mBrowserState
.presentationActive
= true;
1248 mBrowserState
.layerState
[0].type
= VRLayerType::LayerType_Stereo_Immersive
;
1251 mDisplayInfo
.mDisplayState
.lastSubmittedFrameId
= 0;
1252 if (mDisplayInfo
.mDisplayState
.reportsDroppedFrames
) {
1253 mTelemetry
.mLastDroppedFrameCount
=
1254 mDisplayInfo
.mDisplayState
.droppedFrameCount
;
1257 mLastSubmittedFrameId
= 0;
1258 mLastStartedFrame
= 0;
1261 void VRManager::StopPresentation() {
1262 if (mState
!= VRManagerState::Active
) {
1265 if (!mBrowserState
.presentationActive
) {
1269 // Indicate that we have stopped immersive mode
1270 mBrowserState
.presentationActive
= false;
1271 memset(mBrowserState
.layerState
, 0,
1272 sizeof(VRLayerState
) * mozilla::ArrayLength(mBrowserState
.layerState
));
1276 Telemetry::HistogramID timeSpentID
= Telemetry::HistogramCount
;
1277 Telemetry::HistogramID droppedFramesID
= Telemetry::HistogramCount
;
1280 if (mDisplayInfo
.mDisplayState
.eightCC
==
1281 GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) {
1282 // Oculus Desktop API
1283 timeSpentID
= Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS
;
1284 droppedFramesID
= Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS
;
1286 } else if (mDisplayInfo
.mDisplayState
.eightCC
==
1287 GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
1289 timeSpentID
= Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR
;
1290 droppedFramesID
= Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR
;
1295 const TimeDuration duration
=
1296 TimeStamp::Now() - mTelemetry
.mPresentationStart
;
1297 Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN
, viewIn
);
1298 Telemetry::Accumulate(timeSpentID
, duration
.ToMilliseconds());
1299 const uint32_t droppedFramesPerSec
=
1300 (uint32_t)((double)(mDisplayInfo
.mDisplayState
.droppedFrameCount
-
1301 mTelemetry
.mLastDroppedFrameCount
) /
1302 duration
.ToSeconds());
1303 Telemetry::Accumulate(droppedFramesID
, droppedFramesPerSec
);
1307 bool VRManager::IsPresenting() {
1309 return mDisplayInfo
.mPresentingGroups
!= 0;
1314 void VRManager::SetGroupMask(uint32_t aGroupMask
) {
1315 if (mState
!= VRManagerState::Active
) {
1318 mDisplayInfo
.mGroupMask
= aGroupMask
;
1321 void VRManager::SubmitFrame(VRLayerParent
* aLayer
,
1322 const layers::SurfaceDescriptor
& aTexture
,
1323 uint64_t aFrameId
, const gfx::Rect
& aLeftEyeRect
,
1324 const gfx::Rect
& aRightEyeRect
) {
1325 if (mState
!= VRManagerState::Active
) {
1328 MonitorAutoLock
lock(mCurrentSubmitTaskMonitor
);
1329 if ((mDisplayInfo
.mGroupMask
& aLayer
->GetGroup()) == 0) {
1330 // Suppress layers hidden by the group mask
1334 // Ensure that we only accept the first SubmitFrame call per RAF cycle.
1335 if (!mFrameStarted
|| aFrameId
!= mDisplayInfo
.mFrameId
) {
1340 * Do not queue more submit frames until the last submitted frame is
1341 * already processed and the new WebGL texture is ready.
1343 if (mLastSubmittedFrameId
> 0 &&
1344 mLastSubmittedFrameId
!=
1345 mDisplayInfo
.mDisplayState
.lastSubmittedFrameId
) {
1346 mLastStartedFrame
= 0;
1350 mLastSubmittedFrameId
= aFrameId
;
1352 mFrameStarted
= false;
1354 RefPtr
<CancelableRunnable
> task
= NewCancelableRunnableMethod
<
1355 StoreCopyPassByConstLRef
<layers::SurfaceDescriptor
>, uint64_t,
1356 StoreCopyPassByConstLRef
<gfx::Rect
>, StoreCopyPassByConstLRef
<gfx::Rect
>>(
1357 "gfx::VRManager::SubmitFrameInternal", this,
1358 &VRManager::SubmitFrameInternal
, aTexture
, aFrameId
, aLeftEyeRect
,
1361 if (!mCurrentSubmitTask
) {
1362 mCurrentSubmitTask
= task
;
1363 #if !defined(MOZ_WIDGET_ANDROID)
1364 if (!mSubmitThread
) {
1365 mSubmitThread
= new VRThread("VR_SubmitFrame"_ns
);
1367 mSubmitThread
->Start();
1368 mSubmitThread
->PostTask(task
.forget());
1370 CompositorThread()->Dispatch(task
.forget());
1371 #endif // defined(MOZ_WIDGET_ANDROID)
1375 bool VRManager::SubmitFrame(const layers::SurfaceDescriptor
& aTexture
,
1376 uint64_t aFrameId
, const gfx::Rect
& aLeftEyeRect
,
1377 const gfx::Rect
& aRightEyeRect
) {
1378 if (mState
!= VRManagerState::Active
) {
1381 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
1382 MOZ_ASSERT(mBrowserState
.layerState
[0].type
==
1383 VRLayerType::LayerType_Stereo_Immersive
);
1384 VRLayer_Stereo_Immersive
& layer
=
1385 mBrowserState
.layerState
[0].layer_stereo_immersive
;
1387 switch (aTexture
.type()) {
1388 # if defined(XP_WIN)
1389 case SurfaceDescriptor::TSurfaceDescriptorD3D10
: {
1390 const SurfaceDescriptorD3D10
& surf
=
1391 aTexture
.get_SurfaceDescriptorD3D10();
1392 auto handle
= surf
.handle()->ClonePlatformHandle();
1394 VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor
;
1395 layer
.textureHandle
= (void*)handle
.release();
1396 layer
.textureSize
.width
= surf
.size().width
;
1397 layer
.textureSize
.height
= surf
.size().height
;
1399 # elif defined(XP_MACOSX)
1400 case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface
: {
1401 // MacIOSurface ptr can't be fetched or used at different threads.
1402 // Both of fetching and using this MacIOSurface are at the VRService
1404 const auto& desc
= aTexture
.get_SurfaceDescriptorMacIOSurface();
1405 layer
.textureType
= VRLayerTextureType::LayerTextureType_MacIOSurface
;
1406 layer
.textureHandle
= desc
.surfaceId();
1407 RefPtr
<MacIOSurface
> surf
= MacIOSurface::LookupSurface(
1408 desc
.surfaceId(), !desc
.isOpaque(), desc
.yUVColorSpace());
1410 layer
.textureSize
.width
= surf
->GetDevicePixelWidth();
1411 layer
.textureSize
.height
= surf
->GetDevicePixelHeight();
1414 # elif defined(MOZ_WIDGET_ANDROID)
1415 case SurfaceDescriptor::TSurfaceTextureDescriptor
: {
1416 const SurfaceTextureDescriptor
& desc
=
1417 aTexture
.get_SurfaceTextureDescriptor();
1418 java::GeckoSurfaceTexture::LocalRef surfaceTexture
=
1419 java::GeckoSurfaceTexture::Lookup(desc
.handle());
1420 if (!surfaceTexture
) {
1421 NS_WARNING("VRManager::SubmitFrame failed to get a SurfaceTexture");
1425 VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture
;
1426 layer
.textureHandle
= desc
.handle();
1427 layer
.textureSize
.width
= desc
.size().width
;
1428 layer
.textureSize
.height
= desc
.size().height
;
1437 layer
.frameId
= aFrameId
;
1438 layer
.inputFrameId
=
1439 mDisplayInfo
.mLastSensorState
[mDisplayInfo
.mFrameId
% kVRMaxLatencyFrames
]
1442 layer
.leftEyeRect
.x
= aLeftEyeRect
.x
;
1443 layer
.leftEyeRect
.y
= aLeftEyeRect
.y
;
1444 layer
.leftEyeRect
.width
= aLeftEyeRect
.width
;
1445 layer
.leftEyeRect
.height
= aLeftEyeRect
.height
;
1446 layer
.rightEyeRect
.x
= aRightEyeRect
.x
;
1447 layer
.rightEyeRect
.y
= aRightEyeRect
.y
;
1448 layer
.rightEyeRect
.width
= aRightEyeRect
.width
;
1449 layer
.rightEyeRect
.height
= aRightEyeRect
.height
;
1454 return (mDisplayInfo
.mDisplayState
.lastSubmittedFrameId
>= aFrameId
) ||
1455 mDisplayInfo
.mDisplayState
.suppressFrames
||
1456 !mDisplayInfo
.mDisplayState
.isConnected
;
1459 if (mDisplayInfo
.mDisplayState
.suppressFrames
||
1460 !mDisplayInfo
.mDisplayState
.isConnected
) {
1461 // External implementation wants to supress frames, service has shut
1462 // down or hardware has been disconnected.
1466 return mDisplayInfo
.mDisplayState
.lastSubmittedFrameSuccessful
;
1468 MOZ_ASSERT(false); // Not implmented for this platform
1473 void VRManager::SubmitFrameInternal(const layers::SurfaceDescriptor
& aTexture
,
1475 const gfx::Rect
& aLeftEyeRect
,
1476 const gfx::Rect
& aRightEyeRect
) {
1477 #if !defined(MOZ_WIDGET_ANDROID)
1478 MOZ_ASSERT(mSubmitThread
->GetThread() == NS_GetCurrentThread());
1479 #endif // !defined(MOZ_WIDGET_ANDROID)
1480 AUTO_PROFILER_TRACING_MARKER("VR", "SubmitFrameAtVRDisplayExternal", OTHER
);
1483 MonitorAutoLock
lock(mCurrentSubmitTaskMonitor
);
1485 if (!SubmitFrame(aTexture
, aFrameId
, aLeftEyeRect
, aRightEyeRect
)) {
1486 mCurrentSubmitTask
= nullptr;
1489 mCurrentSubmitTask
= nullptr;
1492 #if defined(XP_WIN) || defined(XP_MACOSX)
1495 * Trigger the next VSync immediately after we are successfully
1496 * submitting frames. As SubmitFrame is responsible for throttling
1497 * the render loop, if we don't successfully call it, we shouldn't trigger
1498 * StartFrame immediately, as it will run unbounded.
1499 * If StartFrame is not called here due to SubmitFrame failing, the
1500 * fallback "watchdog" code in VRManager::NotifyVSync() will cause
1501 * frames to continue at a lower refresh rate until frame submission
1504 CompositorThread()->Dispatch(NewRunnableMethod("gfx::VRManager::StartFrame",
1505 this, &VRManager::StartFrame
));
1506 #elif defined(MOZ_WIDGET_ANDROID)
1507 // We are already in the CompositorThreadHolder event loop on Android.
1512 void VRManager::CancelCurrentSubmitTask() {
1513 MonitorAutoLock
lock(mCurrentSubmitTaskMonitor
);
1514 if (mCurrentSubmitTask
) {
1515 mCurrentSubmitTask
->Cancel();
1516 mCurrentSubmitTask
= nullptr;
1520 //-----------------------------------------------------------------------------
1521 // VRManager::nsIObserver
1522 //-----------------------------------------------------------------------------
1525 VRManager::Observe(nsISupports
* subject
, const char* topic
,
1526 const char16_t
* data
) {
1527 if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) {
1531 if (!strcmp(topic
, "application-background")) {
1532 // StopTasks() is called later in the timer thread based on this flag to
1533 // avoid threading issues.
1535 } else if (!strcmp(topic
, "application-foreground") && mAppPaused
) {
1537 // When the apps goes the foreground (e.g. Android) we should restart the
1544 NS_IMPL_ISUPPORTS(VRManager
, nsIObserver
)
1546 } // namespace mozilla::gfx