Bug 1699062 - Flatten toolkit/themes/*/global/alerts/. r=desktop-theme-reviewers,dao
[gecko.git] / gfx / vr / VRManager.cpp
blobb08880db5705e9a35a4006873858b5bc8e9b292a
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/. */
7 #include "VRManager.h"
9 #include "GeckoProfiler.h"
10 #include "VRManagerParent.h"
11 #include "VRShMem.h"
12 #include "VRThread.h"
13 #include "gfxVR.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"
26 #include "gfxVR.h"
27 #include <cstring>
29 #include "ipc/VRLayerParent.h"
30 #if !defined(MOZ_WIDGET_ANDROID)
31 # include "VRServiceHost.h"
32 #endif
34 #ifdef XP_WIN
35 # include "CompositorD3D11.h"
36 # include "TextureD3D11.h"
37 # include <d3d11.h>
38 # include "gfxWindowsPlatform.h"
39 # include "mozilla/gfx/DeviceManagerDx.h"
40 #elif defined(XP_MACOSX)
41 # include "mozilla/gfx/MacIOSurface.h"
42 # include <errno.h>
43 #elif defined(MOZ_WIDGET_ANDROID)
44 # include <string.h>
45 # include <pthread.h>
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 {
60 /**
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
67 /**
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
75 /**
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();
88 /* static */
89 VRManager* VRManager::Get() {
90 MOZ_ASSERT(sVRManagerSingleton != nullptr);
91 MOZ_ASSERT(ValidVRManagerProcess());
93 return sVRManagerSingleton;
96 /* static */
97 VRManager* VRManager::MaybeGet() {
98 MOZ_ASSERT(ValidVRManagerProcess());
100 return sVRManagerSingleton;
103 Atomic<uint32_t> VRManager::sDisplayBase(0);
105 /* static */
106 uint32_t VRManager::AllocateDisplayID() { return ++sDisplayBase; }
108 /*static*/
109 void VRManager::ManagerInit() {
110 MOZ_ASSERT(NS_IsMainThread());
112 if (!ValidVRManagerProcess()) {
113 return;
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),
139 mTaskInterval(0),
140 mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
141 mCurrentSubmitTask(nullptr),
142 mLastSubmittedFrameId(0),
143 mLastStartedFrame(0),
144 mRuntimeSupportFlags(VRDisplayCapabilityFlags::Cap_None),
145 mAppPaused(false),
146 mShmem(nullptr),
147 mHapticPulseRemaining{},
148 mDisplayInfo{},
149 mLastUpdateDisplayInfo{},
150 mBrowserState{},
151 mLastSensorState{} {
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.
159 mVRProcessEnabled =
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.
167 #else
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();
174 if (service) {
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());
193 return;
195 #else
196 mShmem->CreateShMemForAndroid();
197 #endif
198 } else {
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();
215 delete mShmem;
216 mShmem = nullptr;
220 VRManager::~VRManager() {
221 MOZ_ASSERT(NS_IsMainThread());
222 MOZ_ASSERT(mState == VRManagerState::Disabled);
224 nsCOMPtr<nsIObserverService> service = services::GetObserverService();
225 if (service) {
226 service->RemoveObserver(this, "application-background");
227 service->RemoveObserver(this, "application-foreground");
230 #if !defined(MOZ_WIDGET_ANDROID)
231 mServiceHost->Shutdown();
232 #endif
233 CloseShmem();
236 void VRManager::AddLayer(VRLayerParent* aLayer) {
237 mLayers.AppendElement(aLayer);
238 mDisplayInfo.mPresentingGroups |= aLayer->GetGroup();
239 if (mLayers.Length() == 1) {
240 StartPresentation();
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) {
253 StopPresentation();
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.PutEntry(aVRManagerParent);
271 void VRManager::RemoveVRManagerParent(VRManagerParent* aVRManagerParent) {
272 mVRManagerParents.RemoveEntry(aVRManagerParent);
273 if (mVRManagerParents.IsEmpty()) {
274 Destroy();
278 void VRManager::UpdateRequestedDevices() {
279 bool bHaveEventListener = false;
280 bool bHaveEventListenerNonFocus = false;
281 bool bHaveControllerListener = false;
283 for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
284 VRManagerParent* vmp = iter.Get()->GetKey();
285 bHaveEventListener |= vmp->HaveEventListener() && vmp->GetVRActiveStatus();
286 bHaveEventListenerNonFocus |=
287 vmp->HaveEventListener() && !vmp->GetVRActiveStatus();
288 bHaveControllerListener |= vmp->HaveControllerListener();
291 mVRDisplaysRequested = bHaveEventListener;
292 mVRDisplaysRequestedNonFocus = bHaveEventListenerNonFocus;
293 // We only currently allow controllers to be used when
294 // also activating a VR display
295 mVRControllersRequested = mVRDisplaysRequested && bHaveControllerListener;
299 * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz).
300 * This must be called even when no WebVR site is active.
301 * If we don't have a 2d display attached to the system, we can call this
302 * at the VR display's native refresh rate.
304 void VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) {
305 if (mState != VRManagerState::Active) {
306 return;
309 * If the display isn't presenting, refresh the sensors and trigger
310 * VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
312 if (mDisplayInfo.mPresentingGroups == 0) {
313 StartFrame();
317 void VRManager::StartTasks() {
318 if (!mTaskTimer) {
319 mTaskInterval = GetOptimalTaskInterval();
320 mTaskTimer = NS_NewTimer();
321 mTaskTimer->SetTarget(CompositorThread());
322 mTaskTimer->InitWithNamedFuncCallback(
323 TaskTimerCallback, this, mTaskInterval,
324 nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
325 "VRManager::TaskTimerCallback");
329 void VRManager::StopTasks() {
330 if (mTaskTimer) {
331 mTaskTimer->Cancel();
332 mTaskTimer = nullptr;
336 /*static*/
337 void VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure) {
339 * It is safe to use the pointer passed in aClosure to reference the
340 * VRManager object as the timer is canceled in VRManager::Destroy.
341 * VRManager::Destroy set mState to VRManagerState::Disabled, which
342 * is asserted in the VRManager destructor, guaranteeing that this
343 * functions runs if and only if the VRManager object is valid.
345 VRManager* self = static_cast<VRManager*>(aClosure);
346 self->RunTasks();
348 if (self->mAppPaused) {
349 // When the apps goes the background (e.g. Android) we should stop the
350 // tasks.
351 self->StopTasks();
352 self->mState = VRManagerState::Idle;
356 void VRManager::RunTasks() {
357 // Will be called once every 1ms when a VR presentation
358 // is active or once per vsync when a VR presentation is
359 // not active.
361 if (mState == VRManagerState::Disabled) {
362 // We may have been destroyed but still have messages
363 // in the queue from mTaskTimer. Bail out to avoid
364 // running them.
365 return;
368 TimeStamp now = TimeStamp::Now();
369 double lastTickMs = mAccumulator100ms;
370 double deltaTime = 0.0f;
371 if (!mLastTickTime.IsNull()) {
372 deltaTime = (now - mLastTickTime).ToMilliseconds();
374 mAccumulator100ms += deltaTime;
375 mLastTickTime = now;
377 if (deltaTime > 0.0f && floor(mAccumulator100ms) != floor(lastTickMs)) {
378 // Even if more than 1 ms has passed, we will only
379 // execute Run1msTasks() once.
380 Run1msTasks(deltaTime);
383 if (floor(mAccumulator100ms * 0.1f) != floor(lastTickMs * 0.1f)) {
384 // Even if more than 10 ms has passed, we will only
385 // execute Run10msTasks() once.
386 Run10msTasks();
389 if (mAccumulator100ms >= 100.0f) {
390 // Even if more than 100 ms has passed, we will only
391 // execute Run100msTasks() once.
392 Run100msTasks();
393 mAccumulator100ms = fmod(mAccumulator100ms, 100.0f);
396 uint32_t optimalTaskInterval = GetOptimalTaskInterval();
397 if (mTaskTimer && optimalTaskInterval != mTaskInterval) {
398 mTaskTimer->SetDelay(optimalTaskInterval);
399 mTaskInterval = optimalTaskInterval;
403 uint32_t VRManager::GetOptimalTaskInterval() {
405 * When either VR content is detected or VR hardware
406 * has already been activated, we schedule tasks more
407 * frequently.
409 bool wantGranularTasks = mVRDisplaysRequested || mVRControllersRequested ||
410 mDisplayInfo.mDisplayID != 0;
411 if (wantGranularTasks) {
412 return kVRActiveTaskInterval;
415 return kVRIdleTaskInterval;
419 * Run1msTasks() is guaranteed not to be
420 * called more than once within 1ms.
421 * When VR is not active, this will be
422 * called once per VSync if it wasn't
423 * called within the last 1ms.
425 void VRManager::Run1msTasks(double aDeltaTime) { UpdateHaptics(aDeltaTime); }
428 * Run10msTasks() is guaranteed not to be
429 * called more than once within 10ms.
430 * When VR is not active, this will be
431 * called once per VSync if it wasn't
432 * called within the last 10ms.
434 void VRManager::Run10msTasks() {
435 UpdateRequestedDevices();
436 CheckWatchDog();
437 ExpireNavigationTransition();
438 PullState();
439 PushState();
443 * Run100msTasks() is guaranteed not to be
444 * called more than once within 100ms.
445 * When VR is not active, this will be
446 * called once per VSync if it wasn't
447 * called within the last 100ms.
449 void VRManager::Run100msTasks() {
450 // We must continually refresh the VR display enumeration to check
451 // for events that we must fire such as Window.onvrdisplayconnect
452 // Note that enumeration itself may activate display hardware, such
453 // as Oculus, so we only do this when we know we are displaying content
454 // that is looking for VR displays.
455 #if !defined(MOZ_WIDGET_ANDROID)
456 mServiceHost->Refresh();
457 CheckForPuppetCompletion();
458 #endif
459 ProcessManagerState();
462 void VRManager::CheckForInactiveTimeout() {
463 // Shut down the VR devices when not in use
464 if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus ||
465 mVRControllersRequested || mEnumerationRequested ||
466 mRuntimeDetectionRequested || mState == VRManagerState::Enumeration ||
467 mState == VRManagerState::RuntimeDetection) {
468 // We are using a VR device, keep it alive
469 mLastActiveTime = TimeStamp::Now();
470 } else if (mLastActiveTime.IsNull()) {
471 Shutdown();
472 } else {
473 TimeDuration duration = TimeStamp::Now() - mLastActiveTime;
474 if (duration.ToMilliseconds() > StaticPrefs::dom_vr_inactive_timeout()) {
475 Shutdown();
476 // We must not throttle the next enumeration request
477 // after an idle timeout, as it may result in the
478 // user needing to refresh the browser to detect
479 // VR hardware when leaving and returning to a VR
480 // site.
481 mLastDisplayEnumerationTime = TimeStamp();
486 void VRManager::CheckForShutdown() {
487 // Check for remote end shutdown
488 if (mDisplayInfo.mDisplayState.shutdown) {
489 Shutdown();
493 #if !defined(MOZ_WIDGET_ANDROID)
494 void VRManager::CheckForPuppetCompletion() {
495 // Notify content process about completion of puppet test resets
496 if (mState != VRManagerState::Active) {
497 for (auto iter = mManagerParentsWaitingForPuppetReset.Iter(); !iter.Done();
498 iter.Next()) {
499 Unused << iter.Get()->GetKey()->SendNotifyPuppetResetComplete();
501 mManagerParentsWaitingForPuppetReset.Clear();
503 // Notify content process about completion of puppet test scripts
504 if (mManagerParentRunningPuppet) {
505 mServiceHost->CheckForPuppetCompletion();
509 void VRManager::NotifyPuppetComplete() {
510 // Notify content process about completion of puppet test scripts
511 if (mManagerParentRunningPuppet) {
512 Unused << mManagerParentRunningPuppet
513 ->SendNotifyPuppetCommandBufferCompleted(true);
514 mManagerParentRunningPuppet = nullptr;
518 #endif // !defined(MOZ_WIDGET_ANDROID)
520 void VRManager::StartFrame() {
521 if (mState != VRManagerState::Active) {
522 return;
524 AUTO_PROFILER_TRACING_MARKER("VR", "GetSensorState", OTHER);
527 * Do not start more VR frames until the last submitted frame is already
528 * processed, or the last has stalled for more than
529 * kVRMaxFrameSubmitDuration milliseconds.
531 TimeStamp now = TimeStamp::Now();
532 const TimeStamp lastFrameStart =
533 mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
534 const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
535 double duration =
536 lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
537 if (isPresenting && mLastStartedFrame > 0 &&
538 mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame &&
539 duration < kVRMaxFrameSubmitDuration) {
540 return;
543 mDisplayInfo.mFrameId++;
544 size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
545 mDisplayInfo.mLastSensorState[bufferIndex] = mLastSensorState;
546 mLastFrameStart[bufferIndex] = now;
547 mFrameStarted = true;
548 mLastStartedFrame = mDisplayInfo.mFrameId;
550 DispatchVRDisplayInfoUpdate();
553 void VRManager::DetectRuntimes() {
554 if (mState == VRManagerState::RuntimeDetection) {
555 // Runtime detection has already been started.
556 // This additional request will also receive the
557 // result from the first request.
558 return;
561 // Detect XR runtimes to determine if they are
562 // capable of supporting VR or AR sessions, while
563 // avoiding activating any XR devices or persistent
564 // background software.
565 if (mRuntimeDetectionCompleted) {
566 // We have already detected runtimes, so we can
567 // immediately respond with the same results.
568 // This will require the user to restart the browser
569 // after installing or removing an XR device
570 // runtime.
571 DispatchRuntimeCapabilitiesUpdate();
572 return;
574 mRuntimeDetectionRequested = true;
575 ProcessManagerState();
578 void VRManager::EnumerateDevices() {
579 if (mState == VRManagerState::Enumeration ||
580 (mRuntimeDetectionCompleted &&
581 (mVRDisplaysRequested || mEnumerationRequested))) {
582 // Enumeration has already been started.
583 // This additional request will also receive the
584 // result from the first request.
585 return;
587 // Activate XR runtimes and enumerate XR devices.
588 mEnumerationRequested = true;
589 ProcessManagerState();
592 void VRManager::ProcessManagerState() {
593 switch (mState) {
594 case VRManagerState::Disabled:
595 ProcessManagerState_Disabled();
596 break;
597 case VRManagerState::Idle:
598 ProcessManagerState_Idle();
599 break;
600 case VRManagerState::RuntimeDetection:
601 ProcessManagerState_DetectRuntimes();
602 break;
603 case VRManagerState::Enumeration:
604 ProcessManagerState_Enumeration();
605 break;
606 case VRManagerState::Active:
607 ProcessManagerState_Active();
608 break;
609 case VRManagerState::Stopping:
610 ProcessManagerState_Stopping();
611 break;
613 CheckForInactiveTimeout();
614 CheckForShutdown();
617 void VRManager::ProcessManagerState_Disabled() {
618 MOZ_ASSERT(mState == VRManagerState::Disabled);
620 if (!StaticPrefs::dom_vr_enabled()) {
621 return;
624 if (mRuntimeDetectionRequested || mEnumerationRequested ||
625 mVRDisplaysRequested) {
626 StartTasks();
627 mState = VRManagerState::Idle;
631 void VRManager::ProcessManagerState_Stopping() {
632 MOZ_ASSERT(mState == VRManagerState::Stopping);
633 PullState();
635 * In the case of Desktop, the VRService shuts itself down.
636 * Before it's finished stopping, it sets a flag in the ShMem
637 * to let VRManager know that it's done. VRManager watches for
638 * this flag and transitions out of the VRManagerState::Stopping
639 * state to VRManagerState::Idle.
641 #if defined(MOZ_WIDGET_ANDROID)
642 // On Android, the VR service never actually shuts
643 // down or requests VRManager to stop.
644 Shutdown();
645 #endif // defined(MOZ_WIDGET_ANDROID)
648 void VRManager::ProcessManagerState_Idle_StartEnumeration() {
649 MOZ_ASSERT(mState == VRManagerState::Idle);
651 if (!mEarliestRestartTime.IsNull() &&
652 mEarliestRestartTime > TimeStamp::Now()) {
653 // When the VR Service shuts down it informs us of how long we
654 // must wait until we can re-start it.
655 // We must wait until mEarliestRestartTime before attempting
656 // to enumerate again.
657 return;
661 * Throttle the rate of enumeration to the interval set in
662 * VRDisplayEnumerateInterval
664 if (!mLastDisplayEnumerationTime.IsNull()) {
665 TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime;
666 if (duration.ToMilliseconds() <
667 StaticPrefs::dom_vr_display_enumerate_interval()) {
668 return;
673 * If we get this far, don't try again until
674 * the VRDisplayEnumerateInterval elapses
676 mLastDisplayEnumerationTime = TimeStamp::Now();
678 OpenShmem();
680 mEnumerationRequested = false;
681 // We must block until enumeration has completed in order
682 // to signal that the WebVR promise should be resolved at the
683 // right time.
684 #if defined(MOZ_WIDGET_ANDROID)
685 // In Android, we need to make sure calling
686 // GeckoVRManager::SetExternalContext() from an external VR service
687 // before doing enumeration.
688 if (!mShmem->GetExternalShmem()) {
689 mShmem->CreateShMemForAndroid();
691 if (mShmem->GetExternalShmem()) {
692 mState = VRManagerState::Enumeration;
693 } else {
694 // Not connected to shmem, so no devices to enumerate.
695 mDisplayInfo.Clear();
696 DispatchVRDisplayInfoUpdate();
698 #else
700 PushState();
703 * We must start the VR Service thread
704 * and VR Process before enumeration.
705 * We don't want to start this until we will
706 * actualy enumerate, to avoid continuously
707 * re-launching the thread/process when
708 * no hardware is found or a VR software update
709 * is in progress
711 mServiceHost->StartService();
712 mState = VRManagerState::Enumeration;
713 #endif // MOZ_WIDGET_ANDROID
716 void VRManager::ProcessManagerState_Idle_StartRuntimeDetection() {
717 MOZ_ASSERT(mState == VRManagerState::Idle);
719 OpenShmem();
720 mBrowserState.detectRuntimesOnly = true;
721 mRuntimeDetectionRequested = false;
723 // We must block until enumeration has completed in order
724 // to signal that the WebVR promise should be resolved at the
725 // right time.
726 #if defined(MOZ_WIDGET_ANDROID)
727 // In Android, we need to make sure calling
728 // GeckoVRManager::SetExternalContext() from an external VR service
729 // before doing enumeration.
730 if (!mShmem->GetExternalShmem()) {
731 mShmem->CreateShMemForAndroid();
733 if (mShmem->GetExternalShmem()) {
734 mState = VRManagerState::RuntimeDetection;
735 } else {
736 // Not connected to shmem, so no runtimes to detect.
737 mRuntimeSupportFlags = VRDisplayCapabilityFlags::Cap_None;
738 mRuntimeDetectionCompleted = true;
739 DispatchRuntimeCapabilitiesUpdate();
741 #else
743 PushState();
746 * We must start the VR Service thread
747 * and VR Process before enumeration.
748 * We don't want to start this until we will
749 * actualy enumerate, to avoid continuously
750 * re-launching the thread/process when
751 * no hardware is found or a VR software update
752 * is in progress
754 mServiceHost->StartService();
755 mState = VRManagerState::RuntimeDetection;
756 #endif // MOZ_WIDGET_ANDROID
759 void VRManager::ProcessManagerState_Idle() {
760 MOZ_ASSERT(mState == VRManagerState::Idle);
762 if (!mRuntimeDetectionCompleted) {
763 // Check if we should start detecting runtimes
764 // We must alwasy detect runtimes before doing anything
765 // else with the VR process.
766 // This will happen only once per browser startup.
767 if (mRuntimeDetectionRequested || mEnumerationRequested) {
768 ProcessManagerState_Idle_StartRuntimeDetection();
770 return;
773 // Check if we should start activating enumerating XR hardware
774 if (mRuntimeDetectionCompleted &&
775 (mVRDisplaysRequested || mEnumerationRequested)) {
776 ProcessManagerState_Idle_StartEnumeration();
780 void VRManager::ProcessManagerState_DetectRuntimes() {
781 MOZ_ASSERT(mState == VRManagerState::RuntimeDetection);
782 MOZ_ASSERT(mShmem != nullptr);
784 PullState();
785 if (mEnumerationCompleted) {
787 * When mBrowserState.detectRuntimesOnly is set, the
788 * VRService and VR process will shut themselves down
789 * automatically after detecting runtimes.
790 * mEnumerationCompleted is also used in this case,
791 * but to mean "enumeration of runtimes" not
792 * "enumeration of VR devices".
794 * We set mState to `VRManagerState::Stopping`
795 * to make sure that we don't try to do anything
796 * else with the active VRService until it has stopped.
797 * We must start another one when an XR session will be
798 * requested.
800 * This logic is optimized for the WebXR design, but still
801 * works for WebVR so it can continue to function until
802 * deprecated and removed.
804 mState = VRManagerState::Stopping;
805 mRuntimeSupportFlags = mDisplayInfo.mDisplayState.capabilityFlags &
806 (VRDisplayCapabilityFlags::Cap_ImmersiveVR |
807 VRDisplayCapabilityFlags::Cap_ImmersiveAR |
808 VRDisplayCapabilityFlags::Cap_Inline);
809 mRuntimeDetectionCompleted = true;
810 DispatchRuntimeCapabilitiesUpdate();
814 void VRManager::ProcessManagerState_Enumeration() {
815 MOZ_ASSERT(mState == VRManagerState::Enumeration);
816 MOZ_ASSERT(mShmem != nullptr);
818 PullState();
819 if (mEnumerationCompleted) {
820 if (mDisplayInfo.mDisplayState.isConnected) {
821 mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID();
822 mState = VRManagerState::Active;
823 } else {
824 mDisplayInfo.Clear();
825 mState = VRManagerState::Stopping;
827 DispatchVRDisplayInfoUpdate();
831 void VRManager::ProcessManagerState_Active() {
832 MOZ_ASSERT(mState == VRManagerState::Active);
834 if (mDisplayInfo != mLastUpdateDisplayInfo) {
835 // While the display is active, send continuous updates
836 DispatchVRDisplayInfoUpdate();
840 void VRManager::DispatchVRDisplayInfoUpdate() {
841 for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
842 Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(mDisplayInfo);
844 mLastUpdateDisplayInfo = mDisplayInfo;
847 void VRManager::DispatchRuntimeCapabilitiesUpdate() {
848 VRDisplayCapabilityFlags flags = mRuntimeSupportFlags;
849 if (StaticPrefs::dom_vr_always_support_vr()) {
850 flags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR;
853 if (StaticPrefs::dom_vr_always_support_ar()) {
854 flags |= VRDisplayCapabilityFlags::Cap_ImmersiveAR;
857 for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
858 Unused << iter.Get()->GetKey()->SendUpdateRuntimeCapabilities(flags);
862 void VRManager::StopAllHaptics() {
863 for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
864 ClearHapticSlot(i);
866 PushState();
869 void VRManager::VibrateHaptic(GamepadHandle aGamepadHandle,
870 uint32_t aHapticIndex, double aIntensity,
871 double aDuration,
872 const VRManagerPromise& aPromise)
875 if (mState != VRManagerState::Active) {
876 return;
878 // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
879 // ranges based on displayID. We must translate this to the indexes
880 // understood by VRDisplayExternal.
881 uint32_t controllerBaseIndex =
882 kVRControllerMaxCount * mDisplayInfo.mDisplayID;
883 uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex;
885 TimeStamp now = TimeStamp::Now();
886 size_t bestSlotIndex = 0;
887 // Default to an empty slot, or the slot holding the oldest haptic pulse
888 for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
889 const VRHapticState& state = mBrowserState.hapticState[i];
890 if (state.inputFrameID == 0) {
891 // Unused slot, use it
892 bestSlotIndex = i;
893 break;
895 if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) {
896 // If no empty slots are available, fall back to overriding
897 // the pulse which is ending soonest.
898 bestSlotIndex = i;
901 // Override the last pulse on the same actuator if present.
902 for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
903 const VRHapticState& state = mBrowserState.hapticState[i];
904 if (state.inputFrameID == 0) {
905 // This is an empty slot -- no match
906 continue;
908 if (state.controllerIndex == controllerIndex &&
909 state.hapticIndex == aHapticIndex) {
910 // Found pulse on same actuator -- let's override it.
911 bestSlotIndex = i;
914 ClearHapticSlot(bestSlotIndex);
916 // Populate the selected slot with new haptic state
917 size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
918 VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
919 bestSlot.inputFrameID =
920 mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
921 bestSlot.controllerIndex = controllerIndex;
922 bestSlot.hapticIndex = aHapticIndex;
923 bestSlot.pulseStart = (float)(now - mLastFrameStart[bufferIndex]).ToSeconds();
924 bestSlot.pulseDuration =
925 (float)aDuration * 0.001f; // Convert from ms to seconds
926 bestSlot.pulseIntensity = (float)aIntensity;
928 mHapticPulseRemaining[bestSlotIndex] = aDuration;
929 MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
930 if (bestSlotIndex == mHapticPromises.Length()) {
931 mHapticPromises.AppendElement(
932 UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
933 } else {
934 mHapticPromises[bestSlotIndex] =
935 UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise));
937 PushState();
940 void VRManager::StopVibrateHaptic(GamepadHandle aGamepadHandle) {
941 if (mState != VRManagerState::Active) {
942 return;
944 // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
945 // ranges based on displayID. We must translate this to the indexes
946 // understood by VRDisplayExternal.
947 uint32_t controllerBaseIndex =
948 kVRControllerMaxCount * mDisplayInfo.mDisplayID;
949 uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex;
951 for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
952 VRHapticState& state = mBrowserState.hapticState[i];
953 if (state.controllerIndex == controllerIndex) {
954 memset(&state, 0, sizeof(VRHapticState));
957 PushState();
960 void VRManager::NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise) {
961 aPromise.mParent->SendReplyGamepadVibrateHaptic(aPromise.mPromiseID);
964 void VRManager::StartVRNavigation(const uint32_t& aDisplayID) {
965 if (mState != VRManagerState::Active) {
966 return;
969 * We only support a single VRSession with a single VR display at a
970 * time; however, due to the asynchronous nature of the API, it's possible
971 * that the previously used VR display was a different one than the one now
972 * allocated. We catch these cases to avoid automatically activating the new
973 * VR displays. This situation is expected to be very rare and possibly never
974 * seen. Perhaps further simplification could be made in the content process
975 * code which passes around displayID's that may no longer be needed.
977 if (mDisplayInfo.GetDisplayID() != aDisplayID) {
978 return;
980 mBrowserState.navigationTransitionActive = true;
981 mVRNavigationTransitionEnd = TimeStamp();
982 PushState();
985 void VRManager::StopVRNavigation(const uint32_t& aDisplayID,
986 const TimeDuration& aTimeout) {
987 if (mState != VRManagerState::Active) {
988 return;
990 if (mDisplayInfo.GetDisplayID() != aDisplayID) {
991 return;
993 if (aTimeout.ToMilliseconds() <= 0) {
994 mBrowserState.navigationTransitionActive = false;
995 mVRNavigationTransitionEnd = TimeStamp();
996 PushState();
998 mVRNavigationTransitionEnd = TimeStamp::Now() + aTimeout;
1001 #if !defined(MOZ_WIDGET_ANDROID)
1003 bool VRManager::RunPuppet(const nsTArray<uint64_t>& aBuffer,
1004 VRManagerParent* aManagerParent) {
1005 if (!StaticPrefs::dom_vr_puppet_enabled()) {
1006 // Sanity check to ensure that a compromised content process
1007 // can't use this to escalate permissions.
1008 return false;
1010 if (mManagerParentRunningPuppet != nullptr) {
1011 // Only one parent may run a puppet at a time
1012 return false;
1014 mManagerParentRunningPuppet = aManagerParent;
1015 mServiceHost->PuppetSubmit(aBuffer);
1016 return true;
1019 void VRManager::ResetPuppet(VRManagerParent* aManagerParent) {
1020 mManagerParentsWaitingForPuppetReset.PutEntry(aManagerParent);
1021 if (mManagerParentRunningPuppet != nullptr) {
1022 Unused << mManagerParentRunningPuppet
1023 ->SendNotifyPuppetCommandBufferCompleted(false);
1024 mManagerParentRunningPuppet = nullptr;
1026 mServiceHost->PuppetReset();
1027 // In the event that we are shut down, the task timer won't be running
1028 // to trigger CheckForPuppetCompletion.
1029 // In this case, CheckForPuppetCompletion() would immediately resolve
1030 // the promises for mManagerParentsWaitingForPuppetReset.
1031 // We can simply call it once here to handle that case.
1032 CheckForPuppetCompletion();
1035 #endif // !defined(MOZ_WIDGET_ANDROID)
1037 void VRManager::PullState(
1038 const std::function<bool()>& aWaitCondition /* = nullptr */) {
1039 if (mShmem != nullptr) {
1040 mShmem->PullSystemState(mDisplayInfo.mDisplayState, mLastSensorState,
1041 mDisplayInfo.mControllerState,
1042 mEnumerationCompleted, aWaitCondition);
1046 void VRManager::PushState(bool aNotifyCond) {
1047 if (mShmem != nullptr) {
1048 mShmem->PushBrowserState(mBrowserState, aNotifyCond);
1052 void VRManager::Destroy() {
1053 if (mState == VRManagerState::Disabled) {
1054 return;
1056 Shutdown();
1057 StopTasks();
1058 mState = VRManagerState::Disabled;
1061 void VRManager::Shutdown() {
1062 if (mState == VRManagerState::Disabled || mState == VRManagerState::Idle) {
1063 return;
1066 if (mDisplayInfo.mDisplayState.shutdown) {
1067 // Shutdown was requested by VR Service, so we must throttle
1068 // as requested by the VR Service
1069 TimeStamp now = TimeStamp::Now();
1070 mEarliestRestartTime =
1071 now + TimeDuration::FromMilliseconds(
1072 (double)mDisplayInfo.mDisplayState.minRestartInterval);
1075 StopAllHaptics();
1076 StopPresentation();
1077 CancelCurrentSubmitTask();
1078 ShutdownSubmitThread();
1080 mDisplayInfo.Clear();
1081 mEnumerationCompleted = false;
1083 if (mState == VRManagerState::RuntimeDetection) {
1085 * We have failed to detect runtimes before shutting down.
1086 * Ensure that promises are resolved
1088 * This call to DispatchRuntimeCapabilitiesUpdate will only
1089 * happen when we have failed to detect runtimes. In that case,
1090 * mRuntimeSupportFlags will be 0 and send the correct message
1091 * to the content process.
1093 * When we are successful, we store the result in mRuntimeSupportFlags
1094 * and never try again unless the browser is restarted. mRuntimeSupportFlags
1095 * is never reset back to 0 in that case but we will never re-enter the
1096 * VRManagerState::RuntimeDetection state and hit this code path again.
1098 DispatchRuntimeCapabilitiesUpdate();
1101 if (mState == VRManagerState::Enumeration) {
1102 // We have failed to enumerate VR devices before shutting down.
1103 // Ensure that promises are resolved
1104 DispatchVRDisplayInfoUpdate();
1107 #if !defined(MOZ_WIDGET_ANDROID)
1108 mServiceHost->StopService();
1109 #endif
1110 mState = VRManagerState::Idle;
1112 // We will close Shmem in the DTOR to avoid
1113 // mSubmitThread is still running but its shmem
1114 // has been released.
1117 void VRManager::ShutdownVRManagerParents() {
1118 // Close removes the CanvasParent from the set so take a copy first.
1119 VRManagerParentSet vrManagerParents;
1120 for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
1121 vrManagerParents.PutEntry(iter.Get()->GetKey());
1124 for (auto iter = vrManagerParents.Iter(); !iter.Done(); iter.Next()) {
1125 iter.Get()->GetKey()->Close();
1126 iter.Remove();
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) {
1162 return;
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;
1172 } else {
1173 TimeDuration duration = TimeStamp::Now() - lastFrameStart;
1174 if (duration.ToMilliseconds() >
1175 StaticPrefs::dom_vr_display_rafMaxDuration()) {
1176 bShouldStartFrame = true;
1180 if (bShouldStartFrame) {
1181 StartFrame();
1185 void VRManager::ExpireNavigationTransition() {
1186 if (mState != VRManagerState::Active) {
1187 return;
1189 if (!mVRNavigationTransitionEnd.IsNull() &&
1190 TimeStamp::Now() > mVRNavigationTransitionEnd) {
1191 mBrowserState.navigationTransitionActive = false;
1195 void VRManager::UpdateHaptics(double aDeltaTime) {
1196 if (mState != VRManagerState::Active) {
1197 return;
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
1205 continue;
1207 mHapticPulseRemaining[i] -= aDeltaTime;
1208 if (mHapticPulseRemaining[i] <= 0.0f) {
1209 // The pulse has finished
1210 ClearHapticSlot(i);
1211 bNeedPush = true;
1214 if (bNeedPush) {
1215 PushState();
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) {
1238 return;
1240 if (mBrowserState.presentationActive) {
1241 return;
1243 mTelemetry.Clear();
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;
1249 PushState();
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) {
1263 return;
1265 if (!mBrowserState.presentationActive) {
1266 return;
1269 // Indicate that we have stopped immersive mode
1270 mBrowserState.presentationActive = false;
1271 memset(mBrowserState.layerState, 0,
1272 sizeof(VRLayerState) * mozilla::ArrayLength(mBrowserState.layerState));
1274 PushState(true);
1276 Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount;
1277 Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount;
1278 int viewIn = 0;
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;
1285 viewIn = 1;
1286 } else if (mDisplayInfo.mDisplayState.eightCC ==
1287 GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
1288 // OpenVR API
1289 timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR;
1290 droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR;
1291 viewIn = 2;
1294 if (viewIn) {
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() {
1308 if (mShmem) {
1309 return mDisplayInfo.mPresentingGroups != 0;
1311 return false;
1314 void VRManager::SetGroupMask(uint32_t aGroupMask) {
1315 if (mState != VRManagerState::Active) {
1316 return;
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) {
1326 return;
1328 MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
1329 if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
1330 // Suppress layers hidden by the group mask
1331 return;
1334 // Ensure that we only accept the first SubmitFrame call per RAF cycle.
1335 if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
1336 return;
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;
1347 return;
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,
1359 aRightEyeRect);
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());
1369 #else
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) {
1379 return false;
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 layer.textureType =
1393 VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor;
1394 layer.textureHandle = (void*)surf.handle();
1395 layer.textureSize.width = surf.size().width;
1396 layer.textureSize.height = surf.size().height;
1397 } break;
1398 # elif defined(XP_MACOSX)
1399 case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
1400 // MacIOSurface ptr can't be fetched or used at different threads.
1401 // Both of fetching and using this MacIOSurface are at the VRService
1402 // thread.
1403 const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
1404 layer.textureType = VRLayerTextureType::LayerTextureType_MacIOSurface;
1405 layer.textureHandle = desc.surfaceId();
1406 RefPtr<MacIOSurface> surf =
1407 MacIOSurface::LookupSurface(desc.surfaceId(), desc.scaleFactor(),
1408 !desc.isOpaque(), desc.yUVColorSpace());
1409 if (surf) {
1410 layer.textureSize.width = surf->GetDevicePixelWidth();
1411 layer.textureSize.height = surf->GetDevicePixelHeight();
1413 } break;
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");
1422 return false;
1424 layer.textureType =
1425 VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture;
1426 layer.textureHandle = desc.handle();
1427 layer.textureSize.width = desc.size().width;
1428 layer.textureSize.height = desc.size().height;
1429 } break;
1430 # endif
1431 default: {
1432 MOZ_ASSERT(false);
1433 return false;
1437 layer.frameId = aFrameId;
1438 layer.inputFrameId =
1439 mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames]
1440 .inputFrameID;
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;
1451 PushState(true);
1453 PullState([&]() {
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.
1463 return false;
1466 return mDisplayInfo.mDisplayState.lastSubmittedFrameSuccessful;
1467 #else
1468 MOZ_ASSERT(false); // Not implmented for this platform
1469 return false;
1470 #endif
1473 void VRManager::SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
1474 uint64_t aFrameId,
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);
1482 { // scope lock
1483 MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
1485 if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) {
1486 mCurrentSubmitTask = nullptr;
1487 return;
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
1502 * succeeds again.
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.
1508 StartFrame();
1509 #endif
1512 void VRManager::CancelCurrentSubmitTask() {
1513 MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
1514 if (mCurrentSubmitTask) {
1515 mCurrentSubmitTask->Cancel();
1516 mCurrentSubmitTask = nullptr;
1520 //-----------------------------------------------------------------------------
1521 // VRManager::nsIObserver
1522 //-----------------------------------------------------------------------------
1524 NS_IMETHODIMP
1525 VRManager::Observe(nsISupports* subject, const char* topic,
1526 const char16_t* data) {
1527 if (!strcmp(topic, "application-background")) {
1528 // StopTasks() is called later in the timer thread based on this flag to
1529 // avoid threading issues.
1530 mAppPaused = true;
1531 } else if (!strcmp(topic, "application-foreground") && mAppPaused) {
1532 mAppPaused = false;
1533 // When the apps goes the foreground (e.g. Android) we should restart the
1534 // tasks.
1535 StartTasks();
1537 return NS_OK;
1540 NS_IMPL_ISUPPORTS(VRManager, nsIObserver)
1542 } // namespace mozilla::gfx