Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VRManager.h"
#include "GeckoProfiler.h"
#include "VRManagerParent.h"
#include "VRShMem.h"
#include "VRThread.h"
#include "gfxVR.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/layers/TextureHost.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "nsIObserverService.h"
#include "gfxVR.h"
#include <cstring>
#include "ipc/VRLayerParent.h"
#if !defined(MOZ_WIDGET_ANDROID)
# include "VRServiceHost.h"
#endif
#ifdef XP_WIN
# include "CompositorD3D11.h"
# include "TextureD3D11.h"
# include <d3d11.h>
# include "gfxWindowsPlatform.h"
# include "mozilla/gfx/DeviceManagerDx.h"
#elif defined(XP_MACOSX)
# include "mozilla/gfx/MacIOSurface.h"
# include <errno.h>
#elif defined(MOZ_WIDGET_ANDROID)
# include <string.h>
# include <pthread.h>
# include "GeckoVRManager.h"
# include "mozilla/java/GeckoSurfaceTextureWrappers.h"
# include "mozilla/layers/CompositorThread.h"
#endif // defined(MOZ_WIDGET_ANDROID)
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::gl;
using mozilla::dom::GamepadHandle;
namespace mozilla::gfx {
/**
* When VR content is active, we run the tasks at 1ms
* intervals, enabling multiple events to be processed
* per frame, such as haptic feedback pulses.
*/
const uint32_t kVRActiveTaskInterval = 1; // milliseconds
/**
* When VR content is inactive, we run the tasks at 100ms
* intervals, enabling VR display enumeration and
* presentation startup to be relatively responsive
* while not consuming unnecessary resources.
*/
const uint32_t kVRIdleTaskInterval = 100; // milliseconds
/**
* Max frame duration before the watchdog submits a new one.
* Probably we can get rid of this when we enforce that SubmitFrame can only be
* called in a VRDisplay loop.
*/
const double kVRMaxFrameSubmitDuration = 4000.0f; // milliseconds
static StaticRefPtr<VRManager> sVRManagerSingleton;
static bool ValidVRManagerProcess() {
return XRE_IsParentProcess() || XRE_IsGPUProcess();
}
/* static */
VRManager* VRManager::Get() {
MOZ_ASSERT(sVRManagerSingleton != nullptr);
MOZ_ASSERT(ValidVRManagerProcess());
return sVRManagerSingleton;
}
/* static */
VRManager* VRManager::MaybeGet() {
MOZ_ASSERT(ValidVRManagerProcess());
return sVRManagerSingleton;
}
Atomic<uint32_t> VRManager::sDisplayBase(0);
/* static */
uint32_t VRManager::AllocateDisplayID() { return ++sDisplayBase; }
/*static*/
void VRManager::ManagerInit() {
MOZ_ASSERT(NS_IsMainThread());
if (!ValidVRManagerProcess()) {
return;
}
// Enable gamepad extensions while VR is enabled.
// Preference only can be set at the Parent process.
if (StaticPrefs::dom_vr_enabled() && XRE_IsParentProcess()) {
Preferences::SetBool("dom.gamepad.extensions.enabled", true);
}
if (sVRManagerSingleton == nullptr) {
sVRManagerSingleton = new VRManager();
ClearOnShutdown(&sVRManagerSingleton);
}
}
VRManager::VRManager()
: mState(VRManagerState::Disabled),
mAccumulator100ms(0.0f),
mRuntimeDetectionRequested(false),
mRuntimeDetectionCompleted(false),
mEnumerationRequested(false),
mEnumerationCompleted(false),
mVRDisplaysRequested(false),
mVRDisplaysRequestedNonFocus(false),
mVRControllersRequested(false),
mFrameStarted(false),
mTaskInterval(0),
mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
mCurrentSubmitTask(nullptr),
mLastSubmittedFrameId(0),
mLastStartedFrame(0),
mRuntimeSupportFlags(VRDisplayCapabilityFlags::Cap_None),
mAppPaused(false),
mShmem(nullptr),
mHapticPulseRemaining{},
mDisplayInfo{},
mLastUpdateDisplayInfo{},
mBrowserState{},
mLastSensorState{} {
MOZ_ASSERT(sVRManagerSingleton == nullptr);
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(ValidVRManagerProcess());
#if !defined(MOZ_WIDGET_ANDROID)
// XRE_IsGPUProcess() is helping us to check some platforms like
// Win 7 try which are not using GPU process but VR process is enabled.
mVRProcessEnabled =
StaticPrefs::dom_vr_process_enabled_AtStartup() && XRE_IsGPUProcess();
VRServiceHost::Init(mVRProcessEnabled);
mServiceHost = VRServiceHost::Get();
// We must shutdown before VRServiceHost, which is cleared
// on ShutdownPhase::XPCOMShutdownFinal, potentially before VRManager.
// We hold a reference to VRServiceHost to ensure it stays
// alive until we have shut down.
#else
// For Android, there is no VRProcess available and no VR service is
// created, so default to false.
mVRProcessEnabled = false;
#endif // !defined(MOZ_WIDGET_ANDROID)
nsCOMPtr<nsIObserverService> service = services::GetObserverService();
if (service) {
service->AddObserver(this, "application-background", false);
service->AddObserver(this, "application-foreground", false);
}
}
void VRManager::OpenShmem() {
if (mShmem == nullptr) {
mShmem = new VRShMem(nullptr, true /*aRequiresMutex*/);
#if !defined(MOZ_WIDGET_ANDROID)
mShmem->CreateShMem(mVRProcessEnabled /*aCreateOnSharedMemory*/);
// The VR Service accesses all hardware from a separate process
// and replaces the other VRManager when enabled.
// If the VR process is not enabled, create an in-process VRService.
if (!mVRProcessEnabled) {
// If the VR process is disabled, attempt to create a
// VR service within the current process
mServiceHost->CreateService(mShmem->GetExternalShmem());
return;
}
#else
mShmem->CreateShMemForAndroid();
#endif
} else {
mShmem->ClearShMem();
}
// Reset local information for new connection
mDisplayInfo.Clear();
mLastUpdateDisplayInfo.Clear();
mFrameStarted = false;
mBrowserState.Clear();
mLastSensorState.Clear();
mEnumerationCompleted = false;
mDisplayInfo.mGroupMask = kVRGroupContent;
}
void VRManager::CloseShmem() {
if (mShmem != nullptr) {
mShmem->CloseShMem();
delete mShmem;
mShmem = nullptr;
}
}
VRManager::~VRManager() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == VRManagerState::Disabled);
nsCOMPtr<nsIObserverService> service = services::GetObserverService();
if (service) {
service->RemoveObserver(this, "application-background");
service->RemoveObserver(this, "application-foreground");
}
#if !defined(MOZ_WIDGET_ANDROID)
mServiceHost->Shutdown();
#endif
CloseShmem();
}
void VRManager::AddLayer(VRLayerParent* aLayer) {
mLayers.AppendElement(aLayer);
mDisplayInfo.mPresentingGroups |= aLayer->GetGroup();
if (mLayers.Length() == 1) {
StartPresentation();
}
// Ensure that the content process receives the change immediately
if (mState != VRManagerState::Enumeration &&
mState != VRManagerState::RuntimeDetection) {
DispatchVRDisplayInfoUpdate();
}
}
void VRManager::RemoveLayer(VRLayerParent* aLayer) {
mLayers.RemoveElement(aLayer);
if (mLayers.Length() == 0) {
StopPresentation();
}
mDisplayInfo.mPresentingGroups = 0;
for (auto layer : mLayers) {
mDisplayInfo.mPresentingGroups |= layer->GetGroup();
}
// Ensure that the content process receives the change immediately
if (mState != VRManagerState::Enumeration &&
mState != VRManagerState::RuntimeDetection) {
DispatchVRDisplayInfoUpdate();
}
}
void VRManager::AddVRManagerParent(VRManagerParent* aVRManagerParent) {
mVRManagerParents.Insert(aVRManagerParent);
}
void VRManager::RemoveVRManagerParent(VRManagerParent* aVRManagerParent) {
mVRManagerParents.Remove(aVRManagerParent);
if (mVRManagerParents.IsEmpty()) {
Destroy();
}
}
void VRManager::UpdateRequestedDevices() {
bool bHaveEventListener = false;
bool bHaveEventListenerNonFocus = false;
bool bHaveControllerListener = false;
for (VRManagerParent* vmp : mVRManagerParents) {
bHaveEventListener |= vmp->HaveEventListener() && vmp->GetVRActiveStatus();
bHaveEventListenerNonFocus |=
vmp->HaveEventListener() && !vmp->GetVRActiveStatus();
bHaveControllerListener |= vmp->HaveControllerListener();
}
mVRDisplaysRequested = bHaveEventListener;
mVRDisplaysRequestedNonFocus = bHaveEventListenerNonFocus;
// We only currently allow controllers to be used when
// also activating a VR display
mVRControllersRequested = mVRDisplaysRequested && bHaveControllerListener;
}
/**
* VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz).
* This must be called even when no WebVR site is active.
* If we don't have a 2d display attached to the system, we can call this
* at the VR display's native refresh rate.
**/
void VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) {
if (mState != VRManagerState::Active) {
return;
}
/**
* If the display isn't presenting, refresh the sensors and trigger
* VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
*/
if (mDisplayInfo.mPresentingGroups == 0) {
StartFrame();
}
}
void VRManager::StartTasks() {
if (!mTaskTimer) {
mTaskInterval = GetOptimalTaskInterval();
mTaskTimer = NS_NewTimer();
mTaskTimer->SetTarget(CompositorThread());
mTaskTimer->InitWithNamedFuncCallback(
TaskTimerCallback, this, mTaskInterval,
nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
"VRManager::TaskTimerCallback");
}
}
void VRManager::StopTasks() {
if (mTaskTimer) {
mTaskTimer->Cancel();
mTaskTimer = nullptr;
}
}
/*static*/
void VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure) {
/**
* It is safe to use the pointer passed in aClosure to reference the
* VRManager object as the timer is canceled in VRManager::Destroy.
* VRManager::Destroy set mState to VRManagerState::Disabled, which
* is asserted in the VRManager destructor, guaranteeing that this
* functions runs if and only if the VRManager object is valid.
*/
VRManager* self = static_cast<VRManager*>(aClosure);
self->RunTasks();
if (self->mAppPaused) {
// When the apps goes the background (e.g. Android) we should stop the
// tasks.
self->StopTasks();
self->mState = VRManagerState::Idle;
}
}
void VRManager::RunTasks() {
// Will be called once every 1ms when a VR presentation
// is active or once per vsync when a VR presentation is
// not active.
if (mState == VRManagerState::Disabled) {
// We may have been destroyed but still have messages
// in the queue from mTaskTimer. Bail out to avoid
// running them.
return;
}
TimeStamp now = TimeStamp::Now();
double lastTickMs = mAccumulator100ms;
double deltaTime = 0.0f;
if (!mLastTickTime.IsNull()) {
deltaTime = (now - mLastTickTime).ToMilliseconds();
}
mAccumulator100ms += deltaTime;
mLastTickTime = now;
if (deltaTime > 0.0f && floor(mAccumulator100ms) != floor(lastTickMs)) {
// Even if more than 1 ms has passed, we will only
// execute Run1msTasks() once.
Run1msTasks(deltaTime);
}
if (floor(mAccumulator100ms * 0.1f) != floor(lastTickMs * 0.1f)) {
// Even if more than 10 ms has passed, we will only
// execute Run10msTasks() once.
Run10msTasks();
}
if (mAccumulator100ms >= 100.0f) {
// Even if more than 100 ms has passed, we will only
// execute Run100msTasks() once.
Run100msTasks();
mAccumulator100ms = fmod(mAccumulator100ms, 100.0f);
}
uint32_t optimalTaskInterval = GetOptimalTaskInterval();
if (mTaskTimer && optimalTaskInterval != mTaskInterval) {
mTaskTimer->SetDelay(optimalTaskInterval);
mTaskInterval = optimalTaskInterval;
}
}
uint32_t VRManager::GetOptimalTaskInterval() {
/**
* When either VR content is detected or VR hardware
* has already been activated, we schedule tasks more
* frequently.
*/
bool wantGranularTasks = mVRDisplaysRequested || mVRControllersRequested ||
mDisplayInfo.mDisplayID != 0;
if (wantGranularTasks) {
return kVRActiveTaskInterval;
}
return kVRIdleTaskInterval;
}
/**
* Run1msTasks() is guaranteed not to be
* called more than once within 1ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 1ms.
*/
void VRManager::Run1msTasks(double aDeltaTime) { UpdateHaptics(aDeltaTime); }
/**
* Run10msTasks() is guaranteed not to be
* called more than once within 10ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 10ms.
*/
void VRManager::Run10msTasks() {
UpdateRequestedDevices();
CheckWatchDog();
ExpireNavigationTransition();
PullState();
PushState();
}
/**
* Run100msTasks() is guaranteed not to be
* called more than once within 100ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 100ms.
*/
void VRManager::Run100msTasks() {
// We must continually refresh the VR display enumeration to check
// for events that we must fire such as Window.onvrdisplayconnect
// Note that enumeration itself may activate display hardware, such
// as Oculus, so we only do this when we know we are displaying content
// that is looking for VR displays.
#if !defined(MOZ_WIDGET_ANDROID)
mServiceHost->Refresh();
CheckForPuppetCompletion();
#endif
ProcessManagerState();
}
void VRManager::CheckForInactiveTimeout() {
// Shut down the VR devices when not in use
if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus ||
mVRControllersRequested || mEnumerationRequested ||
mRuntimeDetectionRequested || mState == VRManagerState::Enumeration ||
mState == VRManagerState::RuntimeDetection) {
// We are using a VR device, keep it alive
mLastActiveTime = TimeStamp::Now();
} else if (mLastActiveTime.IsNull()) {
Shutdown();
} else {
TimeDuration duration = TimeStamp::Now() - mLastActiveTime;
if (duration.ToMilliseconds() > StaticPrefs::dom_vr_inactive_timeout()) {
Shutdown();
// We must not throttle the next enumeration request
// after an idle timeout, as it may result in the
// user needing to refresh the browser to detect
// VR hardware when leaving and returning to a VR
// site.
mLastDisplayEnumerationTime = TimeStamp();
}
}
}
void VRManager::CheckForShutdown() {
// Check for remote end shutdown
if (mDisplayInfo.mDisplayState.shutdown) {
Shutdown();
}
}
#if !defined(MOZ_WIDGET_ANDROID)
void VRManager::CheckForPuppetCompletion() {
// Notify content process about completion of puppet test resets
if (mState != VRManagerState::Active) {
for (const auto& key : mManagerParentsWaitingForPuppetReset) {
Unused << key->SendNotifyPuppetResetComplete();
}
mManagerParentsWaitingForPuppetReset.Clear();
}
// Notify content process about completion of puppet test scripts
if (mManagerParentRunningPuppet) {
mServiceHost->CheckForPuppetCompletion();
}
}
void VRManager::NotifyPuppetComplete() {
// Notify content process about completion of puppet test scripts
if (mManagerParentRunningPuppet) {
Unused << mManagerParentRunningPuppet
->SendNotifyPuppetCommandBufferCompleted(true);
mManagerParentRunningPuppet = nullptr;
}
}
#endif // !defined(MOZ_WIDGET_ANDROID)
void VRManager::StartFrame() {
if (mState != VRManagerState::Active) {
return;
}
AUTO_PROFILER_TRACING_MARKER("VR", "GetSensorState", OTHER);
/**
* Do not start more VR frames until the last submitted frame is already
* processed, or the last has stalled for more than
* kVRMaxFrameSubmitDuration milliseconds.
*/
TimeStamp now = TimeStamp::Now();
const TimeStamp lastFrameStart =
mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
double duration =
lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
if (isPresenting && mLastStartedFrame > 0 &&
mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame &&
duration < kVRMaxFrameSubmitDuration) {
return;
}
mDisplayInfo.mFrameId++;
size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
mDisplayInfo.mLastSensorState[bufferIndex] = mLastSensorState;
mLastFrameStart[bufferIndex] = now;
mFrameStarted = true;
mLastStartedFrame = mDisplayInfo.mFrameId;
DispatchVRDisplayInfoUpdate();
}
void VRManager::DetectRuntimes() {
if (mState == VRManagerState::RuntimeDetection) {
// Runtime detection has already been started.
// This additional request will also receive the
// result from the first request.
return;
}
// Detect XR runtimes to determine if they are
// capable of supporting VR or AR sessions, while
// avoiding activating any XR devices or persistent
// background software.
if (mRuntimeDetectionCompleted) {
// We have already detected runtimes, so we can
// immediately respond with the same results.
// This will require the user to restart the browser
// after installing or removing an XR device
// runtime.
DispatchRuntimeCapabilitiesUpdate();
return;
}
mRuntimeDetectionRequested = true;
ProcessManagerState();
}
void VRManager::EnumerateDevices() {
if (mState == VRManagerState::Enumeration ||
(mRuntimeDetectionCompleted &&
(mVRDisplaysRequested || mEnumerationRequested))) {
// Enumeration has already been started.
// This additional request will also receive the
// result from the first request.
return;
}
// Activate XR runtimes and enumerate XR devices.
mEnumerationRequested = true;
ProcessManagerState();
}
void VRManager::ProcessManagerState() {
switch (mState) {
case VRManagerState::Disabled:
ProcessManagerState_Disabled();
break;
case VRManagerState::Idle:
ProcessManagerState_Idle();
break;
case VRManagerState::RuntimeDetection:
ProcessManagerState_DetectRuntimes();
break;
case VRManagerState::Enumeration:
ProcessManagerState_Enumeration();
break;
case VRManagerState::Active:
ProcessManagerState_Active();
break;
case VRManagerState::Stopping:
ProcessManagerState_Stopping();
break;
}
CheckForInactiveTimeout();
CheckForShutdown();
}
void VRManager::ProcessManagerState_Disabled() {
MOZ_ASSERT(mState == VRManagerState::Disabled);
if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) {
return;
}
if (mRuntimeDetectionRequested || mEnumerationRequested ||
mVRDisplaysRequested) {
StartTasks();
mState = VRManagerState::Idle;
}
}
void VRManager::ProcessManagerState_Stopping() {
MOZ_ASSERT(mState == VRManagerState::Stopping);
PullState();
/**
* In the case of Desktop, the VRService shuts itself down.
* Before it's finished stopping, it sets a flag in the ShMem
* to let VRManager know that it's done. VRManager watches for
* this flag and transitions out of the VRManagerState::Stopping
* state to VRManagerState::Idle.
*/
#if defined(MOZ_WIDGET_ANDROID)
// On Android, the VR service never actually shuts
// down or requests VRManager to stop.
Shutdown();
#endif // defined(MOZ_WIDGET_ANDROID)
}
void VRManager::ProcessManagerState_Idle_StartEnumeration() {
MOZ_ASSERT(mState == VRManagerState::Idle);
if (!mEarliestRestartTime.IsNull() &&
mEarliestRestartTime > TimeStamp::Now()) {
// When the VR Service shuts down it informs us of how long we
// must wait until we can re-start it.
// We must wait until mEarliestRestartTime before attempting
// to enumerate again.
return;
}
/**
* Throttle the rate of enumeration to the interval set in
* VRDisplayEnumerateInterval
*/
if (!mLastDisplayEnumerationTime.IsNull()) {
TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime;
if (duration.ToMilliseconds() <
StaticPrefs::dom_vr_display_enumerate_interval()) {
return;
}
}
/**
* If we get this far, don't try again until
* the VRDisplayEnumerateInterval elapses
*/
mLastDisplayEnumerationTime = TimeStamp::Now();
OpenShmem();
mEnumerationRequested = false;
// We must block until enumeration has completed in order
// to signal that the WebVR promise should be resolved at the
// right time.
#if defined(MOZ_WIDGET_ANDROID)
// In Android, we need to make sure calling
// GeckoVRManager::SetExternalContext() from an external VR service
// before doing enumeration.
if (!mShmem->GetExternalShmem()) {
mShmem->CreateShMemForAndroid();
}
if (mShmem->GetExternalShmem()) {
mState = VRManagerState::Enumeration;
} else {
// Not connected to shmem, so no devices to enumerate.
mDisplayInfo.Clear();
DispatchVRDisplayInfoUpdate();
}
#else
PushState();
/**
* We must start the VR Service thread
* and VR Process before enumeration.
* We don't want to start this until we will
* actualy enumerate, to avoid continuously
* re-launching the thread/process when
* no hardware is found or a VR software update
* is in progress
*/
mServiceHost->StartService();
mState = VRManagerState::Enumeration;
#endif // MOZ_WIDGET_ANDROID
}
void VRManager::ProcessManagerState_Idle_StartRuntimeDetection() {
MOZ_ASSERT(mState == VRManagerState::Idle);
OpenShmem();
mBrowserState.detectRuntimesOnly = true;
mRuntimeDetectionRequested = false;
// We must block until enumeration has completed in order
// to signal that the WebVR promise should be resolved at the
// right time.
#if defined(MOZ_WIDGET_ANDROID)
// In Android, we need to make sure calling
// GeckoVRManager::SetExternalContext() from an external VR service
// before doing enumeration.
if (!mShmem->GetExternalShmem()) {
mShmem->CreateShMemForAndroid();
}
if (mShmem->GetExternalShmem()) {
mState = VRManagerState::RuntimeDetection;
} else {
// Not connected to shmem, so no runtimes to detect.
mRuntimeSupportFlags = VRDisplayCapabilityFlags::Cap_None;
mRuntimeDetectionCompleted = true;
DispatchRuntimeCapabilitiesUpdate();
}
#else
PushState();
/**
* We must start the VR Service thread
* and VR Process before enumeration.
* We don't want to start this until we will
* actualy enumerate, to avoid continuously
* re-launching the thread/process when
* no hardware is found or a VR software update
* is in progress
*/
mServiceHost->StartService();
mState = VRManagerState::RuntimeDetection;
#endif // MOZ_WIDGET_ANDROID
}
void VRManager::ProcessManagerState_Idle() {
MOZ_ASSERT(mState == VRManagerState::Idle);
if (!mRuntimeDetectionCompleted) {
// Check if we should start detecting runtimes
// We must alwasy detect runtimes before doing anything
// else with the VR process.
// This will happen only once per browser startup.
if (mRuntimeDetectionRequested || mEnumerationRequested) {
ProcessManagerState_Idle_StartRuntimeDetection();
}
return;
}
// Check if we should start activating enumerating XR hardware
if (mRuntimeDetectionCompleted &&
(mVRDisplaysRequested || mEnumerationRequested)) {
ProcessManagerState_Idle_StartEnumeration();
}
}
void VRManager::ProcessManagerState_DetectRuntimes() {
MOZ_ASSERT(mState == VRManagerState::RuntimeDetection);
MOZ_ASSERT(mShmem != nullptr);
PullState();
if (mEnumerationCompleted) {
/**
* When mBrowserState.detectRuntimesOnly is set, the
* VRService and VR process will shut themselves down
* automatically after detecting runtimes.
* mEnumerationCompleted is also used in this case,
* but to mean "enumeration of runtimes" not
* "enumeration of VR devices".
*
* We set mState to `VRManagerState::Stopping`
* to make sure that we don't try to do anything
* else with the active VRService until it has stopped.
* We must start another one when an XR session will be
* requested.
*
* This logic is optimized for the WebXR design, but still
* works for WebVR so it can continue to function until
* deprecated and removed.
*/
mState = VRManagerState::Stopping;
mRuntimeSupportFlags = mDisplayInfo.mDisplayState.capabilityFlags &
(VRDisplayCapabilityFlags::Cap_ImmersiveVR |
VRDisplayCapabilityFlags::Cap_ImmersiveAR |
VRDisplayCapabilityFlags::Cap_Inline);
mRuntimeDetectionCompleted = true;
DispatchRuntimeCapabilitiesUpdate();
}
}
void VRManager::ProcessManagerState_Enumeration() {
MOZ_ASSERT(mState == VRManagerState::Enumeration);
MOZ_ASSERT(mShmem != nullptr);
PullState();
if (mEnumerationCompleted) {
if (mDisplayInfo.mDisplayState.isConnected) {
mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID();
mState = VRManagerState::Active;
} else {
mDisplayInfo.Clear();
mState = VRManagerState::Stopping;
}
DispatchVRDisplayInfoUpdate();
}
}
void VRManager::ProcessManagerState_Active() {
MOZ_ASSERT(mState == VRManagerState::Active);
if (mDisplayInfo != mLastUpdateDisplayInfo) {
// While the display is active, send continuous updates
DispatchVRDisplayInfoUpdate();
}
}
void VRManager::DispatchVRDisplayInfoUpdate() {
for (VRManagerParent* vmp : mVRManagerParents) {
Unused << vmp->SendUpdateDisplayInfo(mDisplayInfo);
}
mLastUpdateDisplayInfo = mDisplayInfo;
}
void VRManager::DispatchRuntimeCapabilitiesUpdate() {
VRDisplayCapabilityFlags flags = mRuntimeSupportFlags;
if (StaticPrefs::dom_vr_always_support_vr()) {
flags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR;
}
if (StaticPrefs::dom_vr_always_support_ar()) {
flags |= VRDisplayCapabilityFlags::Cap_ImmersiveAR;
}
for (VRManagerParent* vmp : mVRManagerParents) {
Unused << vmp->SendUpdateRuntimeCapabilities(flags);
}
}
void VRManager::StopAllHaptics() {
if (mState != VRManagerState::Active) {
return;
}
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
ClearHapticSlot(i);
}
PushState();
}
void VRManager::VibrateHaptic(GamepadHandle aGamepadHandle,
uint32_t aHapticIndex, double aIntensity,
double aDuration,
const VRManagerPromise& aPromise)
{
if (mState != VRManagerState::Active) {
return;
}
// VRDisplayClient::FireGamepadEvents() assigns a controller ID with
// ranges based on displayID. We must translate this to the indexes
// understood by VRDisplayExternal.
uint32_t controllerBaseIndex =
kVRControllerMaxCount * mDisplayInfo.mDisplayID;
uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex;
TimeStamp now = TimeStamp::Now();
size_t bestSlotIndex = 0;
// Default to an empty slot, or the slot holding the oldest haptic pulse
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// Unused slot, use it
bestSlotIndex = i;
break;
}
if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) {
// If no empty slots are available, fall back to overriding
// the pulse which is ending soonest.
bestSlotIndex = i;
}
}
// Override the last pulse on the same actuator if present.
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// This is an empty slot -- no match
continue;
}
if (state.controllerIndex == controllerIndex &&
state.hapticIndex == aHapticIndex) {
// Found pulse on same actuator -- let's override it.
bestSlotIndex = i;
}
}
ClearHapticSlot(bestSlotIndex);
// Populate the selected slot with new haptic state
size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
bestSlot.inputFrameID =
mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
bestSlot.controllerIndex = controllerIndex;
bestSlot.hapticIndex = aHapticIndex;
bestSlot.pulseStart = (float)(now - mLastFrameStart[bufferIndex]).ToSeconds();
bestSlot.pulseDuration =
(float)aDuration * 0.001f; // Convert from ms to seconds
bestSlot.pulseIntensity = (float)aIntensity;
mHapticPulseRemaining[bestSlotIndex] = aDuration;
MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
if (bestSlotIndex == mHapticPromises.Length()) {
mHapticPromises.AppendElement(
UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
} else {
mHapticPromises[bestSlotIndex] =
UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise));
}
PushState();
}
void VRManager::StopVibrateHaptic(GamepadHandle aGamepadHandle) {
if (mState != VRManagerState::Active) {
return;
}
// VRDisplayClient::FireGamepadEvents() assigns a controller ID with
// ranges based on displayID. We must translate this to the indexes
// understood by VRDisplayExternal.
uint32_t controllerBaseIndex =
kVRControllerMaxCount * mDisplayInfo.mDisplayID;
uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex;
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
VRHapticState& state = mBrowserState.hapticState[i];
if (state.controllerIndex == controllerIndex) {
memset(&state, 0, sizeof(VRHapticState));
}
}
PushState();
}
void VRManager::NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise) {
aPromise.mParent->SendReplyGamepadVibrateHaptic(aPromise.mPromiseID);
}
void VRManager::StartVRNavigation(const uint32_t& aDisplayID) {
if (mState != VRManagerState::Active) {
return;
}
/**
* We only support a single VRSession with a single VR display at a
* time; however, due to the asynchronous nature of the API, it's possible
* that the previously used VR display was a different one than the one now
* allocated. We catch these cases to avoid automatically activating the new
* VR displays. This situation is expected to be very rare and possibly never
* seen. Perhaps further simplification could be made in the content process
* code which passes around displayID's that may no longer be needed.
**/
if (mDisplayInfo.GetDisplayID() != aDisplayID) {
return;
}
mBrowserState.navigationTransitionActive = true;
mVRNavigationTransitionEnd = TimeStamp();
PushState();
}
void VRManager::StopVRNavigation(const uint32_t& aDisplayID,
const TimeDuration& aTimeout) {
if (mState != VRManagerState::Active) {
return;
}
if (mDisplayInfo.GetDisplayID() != aDisplayID) {
return;
}
if (aTimeout.ToMilliseconds() <= 0) {
mBrowserState.navigationTransitionActive = false;
mVRNavigationTransitionEnd = TimeStamp();
PushState();
}
mVRNavigationTransitionEnd = TimeStamp::Now() + aTimeout;
}
#if !defined(MOZ_WIDGET_ANDROID)
bool VRManager::RunPuppet(const nsTArray<uint64_t>& aBuffer,
VRManagerParent* aManagerParent) {
if (!StaticPrefs::dom_vr_puppet_enabled()) {
// Sanity check to ensure that a compromised content process
// can't use this to escalate permissions.
return false;
}
if (mManagerParentRunningPuppet != nullptr) {
// Only one parent may run a puppet at a time
return false;
}
mManagerParentRunningPuppet = aManagerParent;
mServiceHost->PuppetSubmit(aBuffer);
return true;
}
void VRManager::ResetPuppet(VRManagerParent* aManagerParent) {
if (!StaticPrefs::dom_vr_puppet_enabled()) {
return;
}
mManagerParentsWaitingForPuppetReset.Insert(aManagerParent);
if (mManagerParentRunningPuppet != nullptr) {
Unused << mManagerParentRunningPuppet
->SendNotifyPuppetCommandBufferCompleted(false);
mManagerParentRunningPuppet = nullptr;
}
mServiceHost->PuppetReset();
// In the event that we are shut down, the task timer won't be running
// to trigger CheckForPuppetCompletion.
// In this case, CheckForPuppetCompletion() would immediately resolve
// the promises for mManagerParentsWaitingForPuppetReset.
// We can simply call it once here to handle that case.
CheckForPuppetCompletion();
}
#endif // !defined(MOZ_WIDGET_ANDROID)
void VRManager::PullState(
const std::function<bool()>& aWaitCondition /* = nullptr */) {
if (mShmem != nullptr) {
mShmem->PullSystemState(mDisplayInfo.mDisplayState, mLastSensorState,
mDisplayInfo.mControllerState,
mEnumerationCompleted, aWaitCondition);
}
}
void VRManager::PushState(bool aNotifyCond) {
if (mShmem != nullptr) {
mShmem->PushBrowserState(mBrowserState, aNotifyCond);
}
}
void VRManager::Destroy() {
if (mState == VRManagerState::Disabled) {
return;
}
Shutdown();
StopTasks();
mState = VRManagerState::Disabled;
}
void VRManager::Shutdown() {
if (mState == VRManagerState::Disabled || mState == VRManagerState::Idle) {
return;
}
if (mDisplayInfo.mDisplayState.shutdown) {
// Shutdown was requested by VR Service, so we must throttle
// as requested by the VR Service
TimeStamp now = TimeStamp::Now();
mEarliestRestartTime =
now + TimeDuration::FromMilliseconds(
(double)mDisplayInfo.mDisplayState.minRestartInterval);
}
StopAllHaptics();
StopPresentation();
CancelCurrentSubmitTask();
ShutdownSubmitThread();
mDisplayInfo.Clear();
mEnumerationCompleted = false;
if (mState == VRManagerState::RuntimeDetection) {
/**
* We have failed to detect runtimes before shutting down.
* Ensure that promises are resolved
*
* This call to DispatchRuntimeCapabilitiesUpdate will only
* happen when we have failed to detect runtimes. In that case,
* mRuntimeSupportFlags will be 0 and send the correct message
* to the content process.
*
* When we are successful, we store the result in mRuntimeSupportFlags
* and never try again unless the browser is restarted. mRuntimeSupportFlags
* is never reset back to 0 in that case but we will never re-enter the
* VRManagerState::RuntimeDetection state and hit this code path again.
*/
DispatchRuntimeCapabilitiesUpdate();
}
if (mState == VRManagerState::Enumeration) {
// We have failed to enumerate VR devices before shutting down.
// Ensure that promises are resolved
DispatchVRDisplayInfoUpdate();
}
#if !defined(MOZ_WIDGET_ANDROID)
mServiceHost->StopService();
#endif
mState = VRManagerState::Idle;
// We will close Shmem in the DTOR to avoid
// mSubmitThread is still running but its shmem
// has been released.
}
void VRManager::ShutdownVRManagerParents() {
// Close removes the CanvasParent from the set so take a copy first.
const auto parents = ToTArray<nsTArray<VRManagerParent*>>(mVRManagerParents);
for (RefPtr<VRManagerParent> vrManagerParent : parents) {
vrManagerParent->Close();
}
MOZ_DIAGNOSTIC_ASSERT(mVRManagerParents.IsEmpty(),
"Closing should have cleared all entries.");
}
void VRManager::CheckWatchDog() {
/**
* We will trigger a new frame immediately after a successful frame
* texture submission. If content fails to call VRDisplay.submitFrame
* after dom.vr.display.rafMaxDuration milliseconds has elapsed since the
* last VRDisplay.requestAnimationFrame, we act as a "watchdog" and
* kick-off a new VRDisplay.requestAnimationFrame to avoid a render loop
* stall and to give content a chance to recover.
*
* If the lower level VR platform API's are rejecting submitted frames,
* such as when the Oculus "Health and Safety Warning" is displayed,
* we will not kick off the next frame immediately after
* VRDisplay.submitFrame as it would result in an unthrottled render loop
* that would free run at potentially extreme frame rates. To ensure that
* content has a chance to resume its presentation when the frames are
* accepted once again, we rely on this "watchdog" to act as a VR refresh
* driver cycling at a rate defined by dom.vr.display.rafMaxDuration.
*
* This number must be larger than the slowest expected frame time during
* normal VR presentation, but small enough not to break content that
* makes assumptions of reasonably minimal VSync rate.
*
* The slowest expected refresh rate for a VR display currently is an
* Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
* A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a
* 20hz rate, which avoids inadvertent triggering of the watchdog during
* Oculus ASW even if every second frame is dropped.
*/
if (mState != VRManagerState::Active) {
return;
}
bool bShouldStartFrame = false;
// If content fails to call VRDisplay.submitFrame, we must eventually
// time-out and trigger a new frame.
TimeStamp lastFrameStart =
mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
if (lastFrameStart.IsNull()) {
bShouldStartFrame = true;
} else {
TimeDuration duration = TimeStamp::Now() - lastFrameStart;
if (duration.ToMilliseconds() >
StaticPrefs::dom_vr_display_rafMaxDuration()) {
bShouldStartFrame = true;
}
}
if (bShouldStartFrame) {
StartFrame();
}
}
void VRManager::ExpireNavigationTransition() {
if (mState != VRManagerState::Active) {
return;
}
if (!mVRNavigationTransitionEnd.IsNull() &&
TimeStamp::Now() > mVRNavigationTransitionEnd) {
mBrowserState.navigationTransitionActive = false;
}
}
void VRManager::UpdateHaptics(double aDeltaTime) {
if (mState != VRManagerState::Active) {
return;
}
bool bNeedPush = false;
// Check for any haptic pulses that have ended and clear them
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// Nothing in this slot
continue;
}
mHapticPulseRemaining[i] -= aDeltaTime;
if (mHapticPulseRemaining[i] <= 0.0f) {
// The pulse has finished
ClearHapticSlot(i);
bNeedPush = true;
}
}
if (bNeedPush) {
PushState();
}
}
void VRManager::ClearHapticSlot(size_t aSlot) {
MOZ_ASSERT(aSlot < mozilla::ArrayLength(mBrowserState.hapticState));
memset(&mBrowserState.hapticState[aSlot], 0, sizeof(VRHapticState));
mHapticPulseRemaining[aSlot] = 0.0f;
if (aSlot < mHapticPromises.Length() && mHapticPromises[aSlot]) {
NotifyVibrateHapticCompleted(*(mHapticPromises[aSlot]));
mHapticPromises[aSlot] = nullptr;
}
}
void VRManager::ShutdownSubmitThread() {
if (mSubmitThread) {
mSubmitThread->Shutdown();
mSubmitThread = nullptr;
}
}
void VRManager::StartPresentation() {
if (mState != VRManagerState::Active) {
return;
}
if (mBrowserState.presentationActive) {
return;
}
mTelemetry.Clear();
mTelemetry.mPresentationStart = TimeStamp::Now();
// Indicate that we are ready to start immersive mode
mBrowserState.presentationActive = true;
mBrowserState.layerState[0].type = VRLayerType::LayerType_Stereo_Immersive;
PushState();
mDisplayInfo.mDisplayState.lastSubmittedFrameId = 0;
if (mDisplayInfo.mDisplayState.reportsDroppedFrames) {
mTelemetry.mLastDroppedFrameCount =
mDisplayInfo.mDisplayState.droppedFrameCount;
}
mLastSubmittedFrameId = 0;
mLastStartedFrame = 0;
}
void VRManager::StopPresentation() {
if (mState != VRManagerState::Active) {
return;
}
if (!mBrowserState.presentationActive) {
return;
}
// Indicate that we have stopped immersive mode
mBrowserState.presentationActive = false;
memset(mBrowserState.layerState, 0,
sizeof(VRLayerState) * mozilla::ArrayLength(mBrowserState.layerState));
PushState(true);
Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount;
Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount;
int viewIn = 0;
if (mDisplayInfo.mDisplayState.eightCC ==
GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) {
// Oculus Desktop API
timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS;
droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS;
viewIn = 1;
} else if (mDisplayInfo.mDisplayState.eightCC ==
GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
// OpenVR API
timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR;
droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR;
viewIn = 2;
}
if (viewIn) {
const TimeDuration duration =
TimeStamp::Now() - mTelemetry.mPresentationStart;
Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn);
Telemetry::Accumulate(timeSpentID, duration.ToMilliseconds());
const uint32_t droppedFramesPerSec =
(uint32_t)((double)(mDisplayInfo.mDisplayState.droppedFrameCount -
mTelemetry.mLastDroppedFrameCount) /
duration.ToSeconds());
Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec);
}
}
bool VRManager::IsPresenting() {
if (mShmem) {
return mDisplayInfo.mPresentingGroups != 0;
}
return false;
}
void VRManager::SetGroupMask(uint32_t aGroupMask) {
if (mState != VRManagerState::Active) {
return;
}
mDisplayInfo.mGroupMask = aGroupMask;
}
void VRManager::SubmitFrame(VRLayerParent* aLayer,
const layers::SurfaceDescriptor& aTexture,
uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
const gfx::Rect& aRightEyeRect) {
if (mState != VRManagerState::Active) {
return;
}
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
// Suppress layers hidden by the group mask
return;
}
// Ensure that we only accept the first SubmitFrame call per RAF cycle.
if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
return;
}
/**
* Do not queue more submit frames until the last submitted frame is
* already processed and the new WebGL texture is ready.
*/
if (mLastSubmittedFrameId > 0 &&
mLastSubmittedFrameId !=
mDisplayInfo.mDisplayState.lastSubmittedFrameId) {
mLastStartedFrame = 0;
return;
}
mLastSubmittedFrameId = aFrameId;
mFrameStarted = false;
RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<
StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
"gfx::VRManager::SubmitFrameInternal", this,
&VRManager::SubmitFrameInternal, aTexture, aFrameId, aLeftEyeRect,
aRightEyeRect);
if (!mCurrentSubmitTask) {
mCurrentSubmitTask = task;
#if !defined(MOZ_WIDGET_ANDROID)
if (!mSubmitThread) {
mSubmitThread = new VRThread("VR_SubmitFrame"_ns);
}
mSubmitThread->Start();
mSubmitThread->PostTask(task.forget());
#else
CompositorThread()->Dispatch(task.forget());
#endif // defined(MOZ_WIDGET_ANDROID)
}
}
bool VRManager::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
const gfx::Rect& aRightEyeRect) {
if (mState != VRManagerState::Active) {
return false;
}
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
MOZ_ASSERT(mBrowserState.layerState[0].type ==
VRLayerType::LayerType_Stereo_Immersive);
VRLayer_Stereo_Immersive& layer =
mBrowserState.layerState[0].layer_stereo_immersive;
switch (aTexture.type()) {
# if defined(XP_WIN)
case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
const SurfaceDescriptorD3D10& surf =
aTexture.get_SurfaceDescriptorD3D10();
auto handle = surf.handle()->ClonePlatformHandle();
layer.textureType =
VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor;
layer.textureHandle = (void*)handle.release();
layer.textureSize.width = surf.size().width;
layer.textureSize.height = surf.size().height;
} break;
# elif defined(XP_MACOSX)
case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
// MacIOSurface ptr can't be fetched or used at different threads.
// Both of fetching and using this MacIOSurface are at the VRService
// thread.
const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
layer.textureType = VRLayerTextureType::LayerTextureType_MacIOSurface;
layer.textureHandle = desc.surfaceId();
RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(
desc.surfaceId(), !desc.isOpaque(), desc.yUVColorSpace());
if (surf) {
layer.textureSize.width = surf->GetDevicePixelWidth();
layer.textureSize.height = surf->GetDevicePixelHeight();
}
} break;
# elif defined(MOZ_WIDGET_ANDROID)
case SurfaceDescriptor::TSurfaceTextureDescriptor: {
const SurfaceTextureDescriptor& desc =
aTexture.get_SurfaceTextureDescriptor();
java::GeckoSurfaceTexture::LocalRef surfaceTexture =
java::GeckoSurfaceTexture::Lookup(desc.handle());
if (!surfaceTexture) {
NS_WARNING("VRManager::SubmitFrame failed to get a SurfaceTexture");
return false;
}
layer.textureType =
VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture;
layer.textureHandle = desc.handle();
layer.textureSize.width = desc.size().width;
layer.textureSize.height = desc.size().height;
} break;
# endif
default: {
MOZ_ASSERT(false);
return false;
}
}
layer.frameId = aFrameId;
layer.inputFrameId =
mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames]
.inputFrameID;
layer.leftEyeRect.x = aLeftEyeRect.x;
layer.leftEyeRect.y = aLeftEyeRect.y;
layer.leftEyeRect.width = aLeftEyeRect.width;
layer.leftEyeRect.height = aLeftEyeRect.height;
layer.rightEyeRect.x = aRightEyeRect.x;
layer.rightEyeRect.y = aRightEyeRect.y;
layer.rightEyeRect.width = aRightEyeRect.width;
layer.rightEyeRect.height = aRightEyeRect.height;
PushState(true);
PullState([&]() {
return (mDisplayInfo.mDisplayState.lastSubmittedFrameId >= aFrameId) ||
mDisplayInfo.mDisplayState.suppressFrames ||
!mDisplayInfo.mDisplayState.isConnected;
});
if (mDisplayInfo.mDisplayState.suppressFrames ||
!mDisplayInfo.mDisplayState.isConnected) {
// External implementation wants to supress frames, service has shut
// down or hardware has been disconnected.
return false;
}
return mDisplayInfo.mDisplayState.lastSubmittedFrameSuccessful;
#else
MOZ_ASSERT(false); // Not implmented for this platform
return false;
#endif
}
void VRManager::SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
uint64_t aFrameId,
const gfx::Rect& aLeftEyeRect,
const gfx::Rect& aRightEyeRect) {
#if !defined(MOZ_WIDGET_ANDROID)
MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
#endif // !defined(MOZ_WIDGET_ANDROID)
AUTO_PROFILER_TRACING_MARKER("VR", "SubmitFrameAtVRDisplayExternal", OTHER);
{ // scope lock
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) {
mCurrentSubmitTask = nullptr;
return;
}
mCurrentSubmitTask = nullptr;
}
#if defined(XP_WIN) || defined(XP_MACOSX)
/**
* Trigger the next VSync immediately after we are successfully
* submitting frames. As SubmitFrame is responsible for throttling
* the render loop, if we don't successfully call it, we shouldn't trigger
* StartFrame immediately, as it will run unbounded.
* If StartFrame is not called here due to SubmitFrame failing, the
* fallback "watchdog" code in VRManager::NotifyVSync() will cause
* frames to continue at a lower refresh rate until frame submission
* succeeds again.
*/
CompositorThread()->Dispatch(NewRunnableMethod("gfx::VRManager::StartFrame",
this, &VRManager::StartFrame));
#elif defined(MOZ_WIDGET_ANDROID)
// We are already in the CompositorThreadHolder event loop on Android.
StartFrame();
#endif
}
void VRManager::CancelCurrentSubmitTask() {
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
if (mCurrentSubmitTask) {
mCurrentSubmitTask->Cancel();
mCurrentSubmitTask = nullptr;
}
}
//-----------------------------------------------------------------------------
// VRManager::nsIObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
VRManager::Observe(nsISupports* subject, const char* topic,
const char16_t* data) {
if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) {
return NS_OK;
}
if (!strcmp(topic, "application-background")) {
// StopTasks() is called later in the timer thread based on this flag to
// avoid threading issues.
mAppPaused = true;
} else if (!strcmp(topic, "application-foreground") && mAppPaused) {
mAppPaused = false;
// When the apps goes the foreground (e.g. Android) we should restart the
// tasks.
StartTasks();
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(VRManager, nsIObserver)
} // namespace mozilla::gfx