/* -*- 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 "VRManagerChild.h" #include "VRLayerChild.h" #include "VRManagerParent.h" #include "VRThread.h" #include "VRDisplayClient.h" #include "nsGlobalWindowInner.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/StaticPtr.h" #include "mozilla/layers/CompositorThread.h" // for CompositorThread #include "mozilla/dom/Navigator.h" #include "mozilla/dom/VREventObserver.h" #include "mozilla/dom/WebXRBinding.h" #include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback #include "mozilla/dom/XRSystem.h" #include "mozilla/dom/XRFrame.h" #include "mozilla/dom/ContentChild.h" #include "nsContentUtils.h" #include "mozilla/dom/GamepadManager.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/layers/SyncObject.h" #include "mozilla/layers/TextureForwarder.h" using namespace mozilla::dom; namespace { const nsTArray>::index_type kNoIndex = nsTArray>::NoIndex; } // namespace namespace mozilla { namespace gfx { static StaticRefPtr sVRManagerChildSingleton; static StaticRefPtr sVRManagerParentSingleton; static TimeStamp sMostRecentFrameEnd; static TimeDuration sAverageFrameInterval; void ReleaseVRManagerParentSingleton() { sVRManagerParentSingleton = nullptr; } VRManagerChild::VRManagerChild() : mRuntimeCapabilities(VRDisplayCapabilityFlags::Cap_None), mFrameRequestCallbackCounter(0), mWaitingForEnumeration(false), mBackend(layers::LayersBackend::LAYERS_NONE) { MOZ_ASSERT(NS_IsMainThread()); mStartTimeStamp = TimeStamp::Now(); } VRManagerChild::~VRManagerChild() { MOZ_ASSERT(NS_IsMainThread()); } /*static*/ void VRManagerChild::IdentifyTextureHost( const TextureFactoryIdentifier& aIdentifier) { if (sVRManagerChildSingleton) { sVRManagerChildSingleton->mBackend = aIdentifier.mParentBackend; } } layers::LayersBackend VRManagerChild::GetBackendType() const { return mBackend; } /*static*/ VRManagerChild* VRManagerChild::Get() { MOZ_ASSERT(sVRManagerChildSingleton); return sVRManagerChildSingleton; } /* static */ bool VRManagerChild::IsCreated() { return !!sVRManagerChildSingleton; } /* static */ bool VRManagerChild::IsPresenting() { if (!VRManagerChild::IsCreated()) { return false; } nsTArray> displays; sVRManagerChildSingleton->GetVRDisplays(displays); bool result = false; for (auto& display : displays) { result |= display->IsPresenting(); } return result; } TimeStamp VRManagerChild::GetIdleDeadlineHint(TimeStamp aDefault) { MOZ_ASSERT(NS_IsMainThread()); if (!VRManagerChild::IsCreated() || sMostRecentFrameEnd.IsNull()) { return aDefault; } TimeStamp idleEnd = sMostRecentFrameEnd + sAverageFrameInterval; return idleEnd < aDefault ? idleEnd : aDefault; } /* static */ bool VRManagerChild::InitForContent(Endpoint&& aEndpoint) { MOZ_ASSERT(NS_IsMainThread()); RefPtr child(new VRManagerChild()); if (!aEndpoint.Bind(child)) { return false; } sVRManagerChildSingleton = child; return true; } /*static*/ void VRManagerChild::InitSameProcess() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!sVRManagerChildSingleton); sVRManagerChildSingleton = new VRManagerChild(); sVRManagerParentSingleton = VRManagerParent::CreateSameProcess(); sVRManagerChildSingleton->Open(sVRManagerParentSingleton, CompositorThread(), mozilla::ipc::ChildSide); } /* static */ void VRManagerChild::InitWithGPUProcess(Endpoint&& aEndpoint) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!sVRManagerChildSingleton); sVRManagerChildSingleton = new VRManagerChild(); if (!aEndpoint.Bind(sVRManagerChildSingleton)) { MOZ_CRASH("Couldn't Open() Compositor channel."); } } /*static*/ void VRManagerChild::ShutDown() { MOZ_ASSERT(NS_IsMainThread()); if (!sVRManagerChildSingleton) { return; } sVRManagerChildSingleton->Close(); sVRManagerChildSingleton = nullptr; } void VRManagerChild::ActorDestroy(ActorDestroyReason aReason) { if (sVRManagerChildSingleton == this) { sVRManagerChildSingleton = nullptr; } } PVRLayerChild* VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID, const uint32_t& aGroup) { return VRLayerChild::CreateIPDLActor(); } bool VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor) { return VRLayerChild::DestroyIPDLActor(actor); } void VRManagerChild::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) { nsTArray disconnectedDisplays; nsTArray connectedDisplays; const nsTArray> prevDisplays(mDisplays.Clone()); // Check if any displays have been disconnected for (auto& display : prevDisplays) { bool found = false; if (aDisplayInfo.GetDisplayID() != 0) { if (display->GetDisplayInfo().GetDisplayID() == aDisplayInfo.GetDisplayID()) { found = true; break; } } if (!found) { // In order to make the current VRDisplay can continue to apply for the // newest VRDisplayInfo, we need to exit presentionation before // disconnecting. if (display->IsPresentationGenerationCurrent()) { NotifyPresentationGenerationChangedInternal( display->GetDisplayInfo().GetDisplayID()); RefPtr vm = VRManagerChild::Get(); vm->FireDOMVRDisplayPresentChangeEvent( display->GetDisplayInfo().GetDisplayID()); } display->NotifyDisconnected(); disconnectedDisplays.AppendElement( display->GetDisplayInfo().GetDisplayID()); } } // mDisplays could be a hashed container for more scalability, but not worth // it now as we expect < 10 entries. nsTArray> displays; if (aDisplayInfo.GetDisplayID() != 0) { bool isNewDisplay = true; for (auto& display : prevDisplays) { const VRDisplayInfo& prevInfo = display->GetDisplayInfo(); if (prevInfo.GetDisplayID() == aDisplayInfo.GetDisplayID()) { if (aDisplayInfo.GetIsConnected() && !prevInfo.GetIsConnected()) { connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } if (!aDisplayInfo.GetIsConnected() && prevInfo.GetIsConnected()) { disconnectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } // MOZ_KnownLive because 'prevDisplays' is guaranteed to keep it alive. // // This can go away once // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. MOZ_KnownLive(display)->UpdateDisplayInfo(aDisplayInfo); displays.AppendElement(display); isNewDisplay = false; break; } } if (isNewDisplay) { displays.AppendElement(new VRDisplayClient(aDisplayInfo)); connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); } } mDisplays = std::move(displays); // We wish to fire the events only after mDisplays is updated for (uint32_t displayID : disconnectedDisplays) { FireDOMVRDisplayDisconnectEvent(displayID); } for (uint32_t displayID : connectedDisplays) { FireDOMVRDisplayConnectEvent(displayID); } } bool VRManagerChild::RuntimeSupportsVR() const { return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveVR); } bool VRManagerChild::RuntimeSupportsAR() const { return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveAR); } bool VRManagerChild::RuntimeSupportsInline() const { return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_Inline); } mozilla::ipc::IPCResult VRManagerChild::RecvUpdateRuntimeCapabilities( const VRDisplayCapabilityFlags& aCapabilities) { mRuntimeCapabilities = aCapabilities; nsContentUtils::AddScriptRunner(NewRunnableMethod<>( "gfx::VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal", this, &VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal)); return IPC_OK(); } void VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal() { const nsTArray> listeners = mListeners.Clone(); for (auto& listener : listeners) { listener->NotifyDetectRuntimesCompleted(); } } mozilla::ipc::IPCResult VRManagerChild::RecvUpdateDisplayInfo( const VRDisplayInfo& aDisplayInfo) { UpdateDisplayInfo(aDisplayInfo); for (auto& windowId : mNavigatorCallbacks) { /** We must call NotifyVRDisplaysUpdated for every * window's Navigator in mNavigatorCallbacks to ensure that * the promise returned by Navigator.GetVRDevices * can resolve. This must happen even if no changes * to VRDisplays have been detected here. */ nsGlobalWindowInner* window = nsGlobalWindowInner::GetInnerWindowWithId(windowId); if (!window) { continue; } dom::Navigator* nav = window->Navigator(); if (!nav) { continue; } nav->NotifyVRDisplaysUpdated(); } mNavigatorCallbacks.Clear(); if (mWaitingForEnumeration) { nsContentUtils::AddScriptRunner(NewRunnableMethod<>( "gfx::VRManagerChild::NotifyEnumerationCompletedInternal", this, &VRManagerChild::NotifyEnumerationCompletedInternal)); mWaitingForEnumeration = false; } return IPC_OK(); } mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetCommandBufferCompleted( bool aSuccess) { RefPtr promise = mRunPuppetPromise; mRunPuppetPromise = nullptr; if (aSuccess) { promise->MaybeResolve(JS::UndefinedHandleValue); } else { promise->MaybeRejectWithUndefined(); } return IPC_OK(); } mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetResetComplete() { nsTArray> promises; promises.AppendElements(mResetPuppetPromises); mResetPuppetPromises.Clear(); for (const auto& promise : promises) { promise->MaybeResolve(JS::UndefinedHandleValue); } return IPC_OK(); } void VRManagerChild::RunPuppet(const nsTArray& aBuffer, dom::Promise* aPromise, ErrorResult& aRv) { if (mRunPuppetPromise) { // We only allow one puppet script to run simultaneously. // The prior promise must be resolved before running a new // script. aRv.Throw(NS_ERROR_INVALID_ARG); return; } if (!SendRunPuppet(aBuffer)) { aRv.Throw(NS_ERROR_FAILURE); return; } mRunPuppetPromise = aPromise; } void VRManagerChild::ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv) { if (!SendResetPuppet()) { aRv.Throw(NS_ERROR_FAILURE); return; } mResetPuppetPromises.AppendElement(aPromise); } void VRManagerChild::GetVRDisplays( nsTArray>& aDisplays) { aDisplays = mDisplays.Clone(); } bool VRManagerChild::RefreshVRDisplaysWithCallback(uint64_t aWindowId) { bool success = SendRefreshDisplays(); if (success) { mNavigatorCallbacks.AppendElement(aWindowId); } return success; } bool VRManagerChild::EnumerateVRDisplays() { bool success = SendRefreshDisplays(); if (success) { mWaitingForEnumeration = true; } return success; } void VRManagerChild::DetectRuntimes() { Unused << SendDetectRuntimes(); } PVRLayerChild* VRManagerChild::CreateVRLayer(uint32_t aDisplayID, uint32_t aGroup) { PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aGroup); return SendPVRLayerConstructor(vrLayerChild, aDisplayID, aGroup); } void VRManagerChild::XRFrameRequest::Call( const DOMHighResTimeStamp& aTimeStamp) { if (mCallback) { RefPtr callback = mCallback; callback->Call(aTimeStamp); } else { RefPtr callback = mXRCallback; RefPtr frame = mXRFrame; callback->Call(aTimeStamp, *frame); } } nsresult VRManagerChild::ScheduleFrameRequestCallback( mozilla::dom::FrameRequestCallback& aCallback, int32_t* aHandle) { if (mFrameRequestCallbackCounter == INT32_MAX) { // Can't increment without overflowing; bail out return NS_ERROR_NOT_AVAILABLE; } int32_t newHandle = ++mFrameRequestCallbackCounter; mFrameRequestCallbacks.AppendElement(XRFrameRequest(aCallback, newHandle)); *aHandle = newHandle; return NS_OK; } void VRManagerChild::CancelFrameRequestCallback(int32_t aHandle) { // mFrameRequestCallbacks is stored sorted by handle mFrameRequestCallbacks.RemoveElementSorted(aHandle); } void VRManagerChild::RunFrameRequestCallbacks() { AUTO_PROFILER_TRACING_MARKER("VR", "RunFrameRequestCallbacks", GRAPHICS); TimeStamp nowTime = TimeStamp::Now(); mozilla::TimeDuration duration = nowTime - mStartTimeStamp; DOMHighResTimeStamp timeStamp = duration.ToMilliseconds(); if (!sMostRecentFrameEnd.IsNull()) { TimeDuration frameInterval = nowTime - sMostRecentFrameEnd; if (sAverageFrameInterval.IsZero()) { sAverageFrameInterval = frameInterval; } else { // Calculate the average interval between frame end and next frame start. // Apply some smoothing to make it more stable. const double smooth = 0.9; sAverageFrameInterval = sAverageFrameInterval.MultDouble(smooth) + frameInterval.MultDouble(1.0 - smooth); } } nsTArray callbacks; callbacks.AppendElements(mFrameRequestCallbacks); mFrameRequestCallbacks.Clear(); for (auto& callback : callbacks) { // The FrameRequest copied into the on-stack array holds a strong ref to its // mCallback and there's nothing that can drop that ref until we return. MOZ_KnownLive(callback.mCallback)->Call(timeStamp); } if (IsPresenting()) { sMostRecentFrameEnd = TimeStamp::Now(); } } void VRManagerChild::NotifyPresentationGenerationChanged(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::NotifyPresentationGenerationChangedInternal", this, &VRManagerChild::NotifyPresentationGenerationChangedInternal, aDisplayID)); } void VRManagerChild::FireDOMVRDisplayMountedEvent(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::FireDOMVRDisplayMountedEventInternal", this, &VRManagerChild::FireDOMVRDisplayMountedEventInternal, aDisplayID)); } void VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::FireDOMVRDisplayUnmountedEventInternal", this, &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal, aDisplayID)); } void VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::FireDOMVRDisplayConnectEventInternal", this, &VRManagerChild::FireDOMVRDisplayConnectEventInternal, aDisplayID)); } void VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::FireDOMVRDisplayDisconnectEventInternal", this, &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal, aDisplayID)); } void VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "gfx::VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal", this, &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal, aDisplayID)); if (!IsPresenting()) { sMostRecentFrameEnd = TimeStamp(); sAverageFrameInterval = 0; } } void VRManagerChild::FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID) { // Iterate over a copy of mListeners, as dispatched events may modify it. for (auto& listener : mListeners.Clone()) { listener->NotifyVRDisplayMounted(aDisplayID); } } void VRManagerChild::FireDOMVRDisplayUnmountedEventInternal( uint32_t aDisplayID) { // Iterate over a copy of mListeners, as dispatched events may modify it. for (auto& listener : mListeners.Clone()) { listener->NotifyVRDisplayUnmounted(aDisplayID); } } void VRManagerChild::FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID) { // Iterate over a copy of mListeners, as dispatched events may modify it. for (auto& listener : mListeners.Clone()) { listener->NotifyVRDisplayConnect(aDisplayID); } } void VRManagerChild::FireDOMVRDisplayDisconnectEventInternal( uint32_t aDisplayID) { // Iterate over a copy of mListeners, as dispatched events may modify it. for (auto& listener : mListeners.Clone()) { listener->NotifyVRDisplayDisconnect(aDisplayID); } } void VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal( uint32_t aDisplayID) { // Iterate over a copy of mListeners, as dispatched events may modify it. for (auto& listener : mListeners.Clone()) { // MOZ_KnownLive because 'listeners' is guaranteed to keep it alive. // // This can go away once // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. MOZ_KnownLive(listener)->NotifyVRDisplayPresentChange(aDisplayID); } } void VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal( uint32_t aDisplayID, VRManagerEventObserver* aObserver) { aObserver->NotifyVRDisplayConnect(aDisplayID); } void VRManagerChild::NotifyPresentationGenerationChangedInternal( uint32_t aDisplayID) { for (auto& listener : mListeners.Clone()) { listener->NotifyPresentationGenerationChanged(aDisplayID); } } void VRManagerChild::NotifyEnumerationCompletedInternal() { for (auto& listener : mListeners.Clone()) { listener->NotifyEnumerationCompleted(); } } void VRManagerChild::FireDOMVRDisplayConnectEventsForLoad( VRManagerEventObserver* aObserver) { // We need to fire the VRDisplayConnect event when a page is loaded // for each VR Display that has already been enumerated for (const auto& display : mDisplays.Clone()) { const VRDisplayInfo& info = display->GetDisplayInfo(); if (info.GetIsConnected()) { nsContentUtils::AddScriptRunner(NewRunnableMethod< uint32_t, RefPtr>( "gfx::VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal", this, &VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal, info.GetDisplayID(), aObserver)); } } } void VRManagerChild::AddListener(VRManagerEventObserver* aObserver) { MOZ_ASSERT(aObserver); if (mListeners.IndexOf(aObserver) != kNoIndex) { return; // already exists } mListeners.AppendElement(aObserver); if (mListeners.Length() == 1) { Unused << SendSetHaveEventListener(true); } } void VRManagerChild::RemoveListener(VRManagerEventObserver* aObserver) { MOZ_ASSERT(aObserver); mListeners.RemoveElement(aObserver); if (mListeners.IsEmpty()) { Unused << SendSetHaveEventListener(false); } } void VRManagerChild::StartActivity() { Unused << SendStartActivity(); } void VRManagerChild::StopActivity() { for (auto& listener : mListeners) { if (!listener->GetStopActivityStatus()) { // We are still showing VR in the active window. return; } } Unused << SendStopActivity(); } void VRManagerChild::HandleFatalError(const char* aMsg) { dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); } void VRManagerChild::AddPromise(const uint32_t& aID, dom::Promise* aPromise) { MOZ_ASSERT(!mGamepadPromiseList.Contains(aID)); mGamepadPromiseList.InsertOrUpdate(aID, RefPtr{aPromise}); } gfx::VRAPIMode VRManagerChild::GetVRAPIMode(uint32_t aDisplayID) const { for (auto& display : mDisplays) { if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) { return display->GetXRAPIMode(); } } return VRAPIMode::WebXR; } mozilla::ipc::IPCResult VRManagerChild::RecvReplyGamepadVibrateHaptic( const uint32_t& aPromiseID) { // VRManagerChild could be at other processes, but GamepadManager // only exists at the content process or the same process // in non-e10s mode. MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess()); RefPtr p; if (!mGamepadPromiseList.Get(aPromiseID, getter_AddRefs(p))) { MOZ_CRASH("We should always have a promise."); } p->MaybeResolve(true); mGamepadPromiseList.Remove(aPromiseID); return IPC_OK(); } } // namespace gfx } // namespace mozilla