diff options
Diffstat (limited to 'gfx/vr/ipc')
27 files changed, 3492 insertions, 0 deletions
diff --git a/gfx/vr/ipc/PVR.ipdl b/gfx/vr/ipc/PVR.ipdl new file mode 100644 index 0000000000..34f01fb51c --- /dev/null +++ b/gfx/vr/ipc/PVR.ipdl @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +using mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using mozilla::gfx::VRControllerType from "moz_external_vr.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +include "VRMessageUtils.h"; +include "VRParent.h"; +include "VRChild.h"; + +include GraphicsMessages; +include MemoryReportTypes; +include PrefsTypes; +include protocol PVRGPU; + +namespace mozilla { +namespace gfx { + +[ManualDealloc, NeedsOtherPid, ChildImpl="VRChild", ParentImpl="VRParent"] +async protocol PVR +{ +parent: + async NewGPUVRManager(Endpoint<PVRGPUParent> endpoint); + async Init(GfxVarUpdate[] vars, DevicePrefs devicePrefs); + + async UpdateVar(GfxVarUpdate var); + async PreferenceUpdate(Pref pref); + async OpenVRControllerActionPathToVR(nsCString aPath); + async OpenVRControllerManifestPathToVR(VRControllerType aType, nsCString aPath); + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + +child: + // Sent when the GPU process has initialized devices. This occurs once, after + // Init(). + async InitComplete(); + async OpenVRControllerActionPathToParent(nsCString aPath); + async OpenVRControllerManifestPathToParent(VRControllerType aType, nsCString aPath); + async InitCrashReporter(NativeThreadId threadId); + async AddMemoryReport(MemoryReport aReport); +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/PVRGPU.ipdl b/gfx/vr/ipc/PVRGPU.ipdl new file mode 100644 index 0000000000..51ebee8c89 --- /dev/null +++ b/gfx/vr/ipc/PVRGPU.ipdl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "VRGPUParent.h"; +include "VRGPUChild.h"; + +namespace mozilla { +namespace gfx { + +// The parent process is the VR process. +// The child process is the GPU process. +[ManualDealloc, NeedsOtherPid, ChildImpl="VRGPUChild", ParentImpl="VRGPUParent"] +async protocol PVRGPU +{ +parent: + async StartVRService(); + async StopVRService(); + async PuppetSubmit(uint64_t[] aBuffer); + async PuppetReset(); + async PuppetCheckForCompletion(); +child: + async NotifyPuppetComplete(); +}; + +} // gfx +} // mozilla
\ No newline at end of file diff --git a/gfx/vr/ipc/PVRLayer.ipdl b/gfx/vr/ipc/PVRLayer.ipdl new file mode 100644 index 0000000000..c6fa16fd2e --- /dev/null +++ b/gfx/vr/ipc/PVRLayer.ipdl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 LayersSurfaces; +include protocol PVRManager; + +include "mozilla/GfxMessageUtils.h"; +include "VRLayerChild.h"; + +using mozilla::gfx::Rect from "mozilla/gfx/Rect.h"; + +namespace mozilla { +namespace gfx { + +[ManualDealloc, ChildImpl="VRLayerChild", ParentImpl=virtual] +async protocol PVRLayer +{ + manager PVRManager; + +parent: + async SubmitFrame(SurfaceDescriptor aTexture, uint64_t aFrameId, + Rect aLeftEyeRect, Rect aRightEyeRect); + + async Destroy(); + +child: + async __delete__(); +}; + +} // gfx +} // mozilla diff --git a/gfx/vr/ipc/PVRManager.ipdl b/gfx/vr/ipc/PVRManager.ipdl new file mode 100644 index 0000000000..73c2a1bb4e --- /dev/null +++ b/gfx/vr/ipc/PVRManager.ipdl @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 LayersSurfaces; +include protocol PVRLayer; +include LayersMessages; +include GamepadEventTypes; + +include "VRMessageUtils.h"; +include "VRManagerParent.h"; +include "VRManagerChild.h"; + +using struct mozilla::gfx::VRFieldOfView from "gfxVR.h"; +using struct mozilla::gfx::VRDisplayInfo from "gfxVR.h"; +using struct mozilla::gfx::VRSensorUpdate from "gfxVR.h"; +using struct mozilla::gfx::VRHMDSensorState from "gfxVR.h"; +using struct mozilla::gfx::VRControllerInfo from "gfxVR.h"; +using struct mozilla::gfx::VRSubmitFrameResultInfo from "gfxVR.h"; +using mozilla::gfx::VRDisplayCapabilityFlags from "moz_external_vr.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; +using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h"; + + +namespace mozilla { +namespace gfx { + +/** + * The PVRManager protocol is used to enable communication of VR display + * enumeration and sensor state between the compositor thread and + * content threads/processes. + */ +[ManualDealloc, NeedsOtherPid, ChildImpl="VRManagerChild", ParentImpl="VRManagerParent"] +sync protocol PVRManager +{ + manages PVRLayer; + +parent: + async PVRLayer(uint32_t aDisplayID, uint32_t aGroup); + + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + async DetectRuntimes(); + + // (Re)Enumerate VR Displays. An updated list of VR displays will be returned + // asynchronously to children via UpdateDisplayInfo. + async RefreshDisplays(); + + async SetGroupMask(uint32_t aDisplayID, uint32_t aGroupMask); + async SetHaveEventListener(bool aHaveEventListener); + + async ControllerListenerAdded(); + async ControllerListenerRemoved(); + async VibrateHaptic(GamepadHandle aGamepadHandle, uint32_t aHapticIndex, + double aIntensity, double aDuration, uint32_t aPromiseID); + async StopVibrateHaptic(GamepadHandle aGamepadHandle); + async StartVRNavigation(uint32_t aDeviceID); + async StopVRNavigation(uint32_t aDeviceID, TimeDuration aDuration); + async StartActivity(); + async StopActivity(); + + async RunPuppet(uint64_t[] buffer); + async ResetPuppet(); + +child: + // Notify children of updated VR display enumeration and details. This will + // be sent to all children when the parent receives RefreshDisplays, even + // if no changes have been detected. This ensures that Promises exposed + // through DOM calls are always resolved. + async UpdateDisplayInfo(VRDisplayInfo aDisplayInfo); + + async UpdateRuntimeCapabilities(VRDisplayCapabilityFlags aCapabilities); + + async ReplyGamepadVibrateHaptic(uint32_t aPromiseID); + async NotifyPuppetCommandBufferCompleted(bool aSuccess); + async NotifyPuppetResetComplete(); + + async __delete__(); + +}; + +} // gfx +} // mozilla diff --git a/gfx/vr/ipc/VRChild.cpp b/gfx/vr/ipc/VRChild.cpp new file mode 100644 index 0000000000..858568455b --- /dev/null +++ b/gfx/vr/ipc/VRChild.cpp @@ -0,0 +1,212 @@ +/* -*- 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 "VRChild.h" +#include "VRProcessManager.h" +#include "VRProcessParent.h" +#include "gfxConfig.h" + +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Telemetry.h" +#include "mozilla/VsyncDispatcher.h" +#include "mozilla/dom/MemoryReportRequest.h" + +namespace mozilla { +namespace gfx { + +class OpenVRControllerManifestManager { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OpenVRControllerManifestManager) + public: + explicit OpenVRControllerManifestManager() = default; + + void SetOpenVRControllerActionPath(const nsCString& aPath) { + mAction = aPath; + } + + void SetOpenVRControllerManifestPath(VRControllerType aType, + const nsCString& aPath) { + mManifest.InsertOrUpdate(static_cast<uint32_t>(aType), aPath); + } + + bool GetActionPath(nsCString* aPath) { + if (!mAction.IsEmpty()) { + *aPath = mAction; + return true; + } + return false; + } + + bool GetManifestPath(VRControllerType aType, nsCString* aPath) { + return mManifest.Get(static_cast<uint32_t>(aType), aPath); + } + + private: + ~OpenVRControllerManifestManager() { + if (!mAction.IsEmpty() && remove(mAction.BeginReading()) != 0) { + MOZ_ASSERT(false, "Delete controller action file failed."); + } + mAction = ""; + + for (const auto& path : mManifest.Values()) { + if (!path.IsEmpty() && remove(path.BeginReading()) != 0) { + MOZ_ASSERT(false, "Delete controller manifest file failed."); + } + } + mManifest.Clear(); + } + + nsCString mAction; + nsTHashMap<nsUint32HashKey, nsCString> mManifest; + OpenVRControllerManifestManager(const OpenVRControllerManifestManager&) = + delete; + + const OpenVRControllerManifestManager& operator=( + const OpenVRControllerManifestManager&) = delete; +}; + +StaticRefPtr<OpenVRControllerManifestManager> sOpenVRControllerManifestManager; + +VRChild::VRChild(VRProcessParent* aHost) : mHost(aHost), mVRReady(false) { + MOZ_ASSERT(XRE_IsParentProcess()); +} + +mozilla::ipc::IPCResult VRChild::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +void VRChild::ActorDestroy(ActorDestroyReason aWhy) { + if (aWhy == AbnormalShutdown) { + GenerateCrashReport(OtherPid()); + + Telemetry::Accumulate( + Telemetry::SUBPROCESS_ABNORMAL_ABORT, + nsDependentCString(XRE_GeckoProcessTypeToString(GeckoProcessType_VR)), + 1); + } + gfxVars::RemoveReceiver(this); + mHost->OnChannelClosed(); +} + +void VRChild::Init() { + nsTArray<GfxVarUpdate> updates = gfxVars::FetchNonDefaultVars(); + + DevicePrefs devicePrefs; + devicePrefs.hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING); + devicePrefs.d3d11Compositing() = + gfxConfig::GetValue(Feature::D3D11_COMPOSITING); + devicePrefs.oglCompositing() = + gfxConfig::GetValue(Feature::OPENGL_COMPOSITING); + devicePrefs.useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D); + + SendInit(updates, devicePrefs); + + if (!sOpenVRControllerManifestManager) { + sOpenVRControllerManifestManager = new OpenVRControllerManifestManager(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ClearOnShutdown OpenVRControllerManifestManager", + []() { ClearOnShutdown(&sOpenVRControllerManifestManager); })); + } + + nsCString output; + if (sOpenVRControllerManifestManager->GetActionPath(&output)) { + SendOpenVRControllerActionPathToVR(output); + } + if (sOpenVRControllerManifestManager->GetManifestPath( + VRControllerType::HTCVive, &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::HTCVive, output); + } + if (sOpenVRControllerManifestManager->GetManifestPath(VRControllerType::MSMR, + &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::MSMR, output); + } + if (sOpenVRControllerManifestManager->GetManifestPath( + VRControllerType::ValveIndex, &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::ValveIndex, output); + } + gfxVars::AddReceiver(this); +} + +bool VRChild::EnsureVRReady() { + if (!mVRReady) { + return false; + } + + return true; +} + +mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerActionPathToParent( + const nsCString& aPath) { + sOpenVRControllerManifestManager->SetOpenVRControllerActionPath(aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerManifestPathToParent( + const VRControllerType& aType, const nsCString& aPath) { + sOpenVRControllerManifestManager->SetOpenVRControllerManifestPath(aType, + aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRChild::RecvInitComplete() { + // We synchronously requested VR parameters before this arrived. + mVRReady = true; + return IPC_OK(); +} + +bool VRChild::SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<FileDescriptor>& aDMDFile) { + mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration); + + PVRChild::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [&](const uint32_t& aGeneration2) { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + if (child->mMemoryReportRequest) { + child->mMemoryReportRequest->Finish(aGeneration2); + child->mMemoryReportRequest = nullptr; + } + } + } + }, + [&](mozilla::ipc::ResponseRejectReason) { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + child->mMemoryReportRequest = nullptr; + } + } + }); + + return true; +} + +void VRChild::OnVarChanged(const GfxVarUpdate& aVar) { SendUpdateVar(aVar); } + +class DeferredDeleteVRChild : public Runnable { + public: + explicit DeferredDeleteVRChild(UniquePtr<VRChild>&& aChild) + : Runnable("gfx::DeferredDeleteVRChild"), mChild(std::move(aChild)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + UniquePtr<VRChild> mChild; +}; + +/* static */ +void VRChild::Destroy(UniquePtr<VRChild>&& aChild) { + NS_DispatchToMainThread(new DeferredDeleteVRChild(std::move(aChild))); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRChild.h b/gfx/vr/ipc/VRChild.h new file mode 100644 index 0000000000..04d9501fe8 --- /dev/null +++ b/gfx/vr/ipc/VRChild.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_CHILD_H +#define GFX_VR_CHILD_H + +#include "mozilla/gfx/PVRChild.h" +#include "mozilla/gfx/gfxVarReceiver.h" +#include "mozilla/ipc/CrashReporterHelper.h" +#include "mozilla/VsyncDispatcher.h" +#include "moz_external_vr.h" + +namespace mozilla { +namespace dom { +class MemoryReportRequestHost; +} // namespace dom +namespace gfx { + +class VRProcessParent; +class VRChild; + +class VRChild final : public PVRChild, + public ipc::CrashReporterHelper<GeckoProcessType_VR>, + public gfxVarReceiver { + typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost; + friend class PVRChild; + + public: + explicit VRChild(VRProcessParent* aHost); + ~VRChild() = default; + + static void Destroy(UniquePtr<VRChild>&& aChild); + void Init(); + bool EnsureVRReady(); + virtual void OnVarChanged(const GfxVarUpdate& aVar) override; + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + protected: + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToParent( + const nsCString& aPath); + mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToParent( + const VRControllerType& aType, const nsCString& aPath); + mozilla::ipc::IPCResult RecvInitComplete(); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + + private: + VRProcessParent* mHost; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest; + bool mVRReady; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_CHILD_H diff --git a/gfx/vr/ipc/VRGPUChild.cpp b/gfx/vr/ipc/VRGPUChild.cpp new file mode 100644 index 0000000000..793778c032 --- /dev/null +++ b/gfx/vr/ipc/VRGPUChild.cpp @@ -0,0 +1,86 @@ +/* -*- 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 "VRGPUChild.h" +#include "VRServiceHost.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/StaticPrefs_dom.h" +#include "VRManager.h" + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRGPUChild> sVRGPUChildSingleton; + +/* static */ +bool VRGPUChild::InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRGPUChildSingleton); + + if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) { + return false; + } + + RefPtr<VRGPUChild> child(new VRGPUChild()); + if (!aEndpoint.Bind(child)) { + return false; + } + sVRGPUChildSingleton = child; + +#if !defined(MOZ_WIDGET_ANDROID) + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::NotifyVRProcessStarted", []() -> void { + VRServiceHost* host = VRServiceHost::Get(); + host->NotifyVRProcessStarted(); + }); + + NS_DispatchToMainThread(task.forget()); +#endif + + return true; +} + +/* static */ +bool VRGPUChild::IsCreated() { return !!sVRGPUChildSingleton; } + +/* static */ +VRGPUChild* VRGPUChild::Get() { + MOZ_ASSERT(IsCreated(), "VRGPUChild haven't initialized yet."); + return sVRGPUChildSingleton; +} + +/*static*/ +void VRGPUChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + if (sVRGPUChildSingleton && !sVRGPUChildSingleton->IsClosed()) { + sVRGPUChildSingleton->Close(); + } + sVRGPUChildSingleton = nullptr; +} + +void VRGPUChild::ActorDestroy(ActorDestroyReason aWhy) { + VRManager* vm = VRManager::Get(); + mozilla::layers::CompositorThread()->Dispatch( + NewRunnableMethod("VRGPUChild::ActorDestroy", vm, &VRManager::Shutdown)); + + mClosed = true; +} + +mozilla::ipc::IPCResult VRGPUChild::RecvNotifyPuppetComplete() { +#if !defined(MOZ_WIDGET_ANDROID) + VRManager* vm = VRManager::Get(); + mozilla::layers::CompositorThread()->Dispatch(NewRunnableMethod( + "VRManager::NotifyPuppetComplete", vm, &VRManager::NotifyPuppetComplete)); +#endif + return IPC_OK(); +} + +bool VRGPUChild::IsClosed() { return mClosed; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRGPUChild.h b/gfx/vr/ipc/VRGPUChild.h new file mode 100644 index 0000000000..3c13ecfc76 --- /dev/null +++ b/gfx/vr/ipc/VRGPUChild.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_GPU_CHILD_H +#define GFX_VR_GPU_CHILD_H + +#include "mozilla/gfx/PVRGPUChild.h" + +namespace mozilla { +namespace gfx { + +class VRGPUChild final : public PVRGPUChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUChild); + + static VRGPUChild* Get(); + static bool InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint); + static bool IsCreated(); + static void Shutdown(); + + mozilla::ipc::IPCResult RecvNotifyPuppetComplete(); + mozilla::ipc::IPCResult RecvNotifyServiceStarted(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool IsClosed(); + + protected: + explicit VRGPUChild() : mClosed(false) {} + ~VRGPUChild() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(VRGPUChild); + + bool mClosed; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_GPU_CHILD_H diff --git a/gfx/vr/ipc/VRGPUParent.cpp b/gfx/vr/ipc/VRGPUParent.cpp new file mode 100644 index 0000000000..72d943086d --- /dev/null +++ b/gfx/vr/ipc/VRGPUParent.cpp @@ -0,0 +1,117 @@ +/* -*- 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 "VRGPUParent.h" +#include "VRPuppetCommandBuffer.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/StaticPrefs_dom.h" + +namespace mozilla { +namespace gfx { + +using namespace ipc; + +VRGPUParent::VRGPUParent(ProcessId aChildProcessId) : mClosed(false) { + MOZ_COUNT_CTOR(VRGPUParent); + MOZ_ASSERT(NS_IsMainThread()); + + SetOtherProcessId(aChildProcessId); +} + +VRGPUParent::~VRGPUParent() { MOZ_COUNT_DTOR(VRGPUParent); } + +void VRGPUParent::ActorDestroy(ActorDestroyReason aWhy) { +#if !defined(MOZ_WIDGET_ANDROID) + if (mVRService) { + mVRService->Stop(); + mVRService = nullptr; + } +#endif + + mClosed = true; + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod("gfx::VRGPUParent::DeferredDestroy", this, + &VRGPUParent::DeferredDestroy)); +} + +void VRGPUParent::DeferredDestroy() { mSelfRef = nullptr; } + +/* static */ +RefPtr<VRGPUParent> VRGPUParent::CreateForGPU( + Endpoint<PVRGPUParent>&& aEndpoint) { + if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) { + return nullptr; + } + + RefPtr<VRGPUParent> vcp = new VRGPUParent(aEndpoint.OtherPid()); + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod<Endpoint<PVRGPUParent>&&>("gfx::VRGPUParent::Bind", vcp, + &VRGPUParent::Bind, + std::move(aEndpoint))); + + return vcp; +} + +void VRGPUParent::Bind(Endpoint<PVRGPUParent>&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + + mSelfRef = this; +} + +mozilla::ipc::IPCResult VRGPUParent::RecvStartVRService() { +#if !defined(MOZ_WIDGET_ANDROID) + mVRService = VRService::Create(); + MOZ_ASSERT(mVRService); + + mVRService->Start(); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvStopVRService() { +#if !defined(MOZ_WIDGET_ANDROID) + if (mVRService) { + mVRService->Stop(); + mVRService = nullptr; + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetReset() { +#if !defined(MOZ_WIDGET_ANDROID) + VRPuppetCommandBuffer::Get().Reset(); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetSubmit( + const nsTArray<uint64_t>& aBuffer) { +#if !defined(MOZ_WIDGET_ANDROID) + VRPuppetCommandBuffer::Get().Submit(aBuffer); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetCheckForCompletion() { +#if !defined(MOZ_WIDGET_ANDROID) + if (VRPuppetCommandBuffer::Get().HasEnded()) { + Unused << SendNotifyPuppetComplete(); + } +#endif + return IPC_OK(); +} + +bool VRGPUParent::IsClosed() { return mClosed; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRGPUParent.h b/gfx/vr/ipc/VRGPUParent.h new file mode 100644 index 0000000000..0a5b5ca9b0 --- /dev/null +++ b/gfx/vr/ipc/VRGPUParent.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_GPU_PARENT_H +#define GFX_VR_GPU_PARENT_H + +#include "mozilla/gfx/PVRGPUParent.h" +#include "VRService.h" + +namespace mozilla { +namespace gfx { + +class VRGPUParent final : public PVRGPUParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUParent) + + friend class PVRGPUParent; + + public: + static RefPtr<VRGPUParent> CreateForGPU(Endpoint<PVRGPUParent>&& aEndpoint); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool IsClosed(); + + protected: + void Bind(Endpoint<PVRGPUParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvStartVRService(); + mozilla::ipc::IPCResult RecvStopVRService(); + mozilla::ipc::IPCResult RecvPuppetReset(); + mozilla::ipc::IPCResult RecvPuppetSubmit(const nsTArray<uint64_t>& aBuffer); + mozilla::ipc::IPCResult RecvPuppetCheckForCompletion(); + + private: + explicit VRGPUParent(ProcessId aChildProcessId); + ~VRGPUParent(); + + void DeferredDestroy(); + + RefPtr<VRGPUParent> mSelfRef; +#if !defined(MOZ_WIDGET_ANDROID) + RefPtr<VRService> mVRService; +#endif + bool mClosed; + + DISALLOW_COPY_AND_ASSIGN(VRGPUParent); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_CONTENT_PARENT_H diff --git a/gfx/vr/ipc/VRLayerChild.cpp b/gfx/vr/ipc/VRLayerChild.cpp new file mode 100644 index 0000000000..57f69f7d2b --- /dev/null +++ b/gfx/vr/ipc/VRLayerChild.cpp @@ -0,0 +1,151 @@ +/* -*- 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 "VRLayerChild.h" + +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/LayersMessages.h" // for TimedTexture +#include "mozilla/layers/SyncObject.h" // for SyncObjectClient +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_webgl.h" + +#include "ClientWebGLContext.h" +#include "gfxPlatform.h" +#include "GLContext.h" +#include "GLScreenBuffer.h" +#include "SharedSurface.h" // for SharedSurface +#include "SharedSurfaceGL.h" // for SharedSurface + +namespace mozilla::gfx { + +VRLayerChild::VRLayerChild() { MOZ_COUNT_CTOR(VRLayerChild); } + +VRLayerChild::~VRLayerChild() { + ClearSurfaces(); + + MOZ_COUNT_DTOR(VRLayerChild); +} + +void VRLayerChild::Initialize(dom::HTMLCanvasElement* aCanvasElement, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect) { + MOZ_ASSERT(aCanvasElement); + mLeftEyeRect = aLeftEyeRect; + mRightEyeRect = aRightEyeRect; + mCanvasElement = aCanvasElement; +} + +void VRLayerChild::SetXRFramebuffer(WebGLFramebufferJS* fb) { + mFramebuffer = fb; +} + +static constexpr bool kIsAndroid = +#if defined(MOZ_WIDGET_ANDROID) + true; +#else + false; +#endif + +void VRLayerChild::SubmitFrame(const VRDisplayInfo& aDisplayInfo) { + uint64_t frameId = aDisplayInfo.GetFrameId(); + + // aFrameId will not increment unless the previuosly submitted + // frame was received by the VR thread and submitted to the VR + // compositor. We early-exit here in the event that SubmitFrame + // was called twice for the same aFrameId. + if (!mCanvasElement || frameId == mLastSubmittedFrameId) { + return; + } + + const auto& webgl = mCanvasElement->GetWebGLContext(); + if (!webgl) return; + + // Keep the SharedSurfaceTextureClient alive long enough for + // 1 extra frame, accomodating overlapped asynchronous rendering. + mLastFrameTextureDesc = mThisFrameTextureDesc; + + bool getNewFrame = true; + if (kIsAndroid) { + /** + * Do not blit WebGL to a SurfaceTexture until the last submitted frame is + * already processed and the new frame poses are ready. SurfaceTextures need + * to be released in the VR render thread in order to allow to be used again + * in the WebGLContext GLScreenBuffer producer. Not doing so causes some + * freezes, crashes or other undefined behaviour. + */ + getNewFrame = (!mThisFrameTextureDesc || + aDisplayInfo.mDisplayState.lastSubmittedFrameId == + mLastSubmittedFrameId); + } + if (getNewFrame) { + const RefPtr<layers::ImageBridgeChild> imageBridge = + layers::ImageBridgeChild::GetSingleton(); + + auto texType = layers::TextureType::Unknown; + if (imageBridge) { + texType = layers::PreferredCanvasTextureType(imageBridge); + } + if (kIsAndroid && StaticPrefs::webgl_enable_surface_texture()) { + texType = layers::TextureType::AndroidNativeWindow; + } + + webgl->Present(mFramebuffer, texType, true); + mThisFrameTextureDesc = webgl->GetFrontBuffer(mFramebuffer, true); + } + + mLastSubmittedFrameId = frameId; + + if (!mThisFrameTextureDesc) { + gfxCriticalError() << "ToSurfaceDescriptor failed in " + "VRLayerChild::SubmitFrame"; + return; + } + + SendSubmitFrame(*mThisFrameTextureDesc, frameId, mLeftEyeRect, mRightEyeRect); +} + +bool VRLayerChild::IsIPCOpen() { return mIPCOpen; } + +void VRLayerChild::ClearSurfaces() { + mThisFrameTextureDesc = Nothing(); + mLastFrameTextureDesc = Nothing(); + const auto& webgl = mCanvasElement->GetWebGLContext(); + if (!mFramebuffer && webgl) { + webgl->ClearVRSwapChain(); + } +} + +void VRLayerChild::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; } + +// static +PVRLayerChild* VRLayerChild::CreateIPDLActor() { + if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) { + return nullptr; + } + + VRLayerChild* c = new VRLayerChild(); + c->AddIPDLReference(); + return c; +} + +// static +bool VRLayerChild::DestroyIPDLActor(PVRLayerChild* actor) { + static_cast<VRLayerChild*>(actor)->ReleaseIPDLReference(); + return true; +} + +void VRLayerChild::AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); +} +void VRLayerChild::ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + Release(); +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/ipc/VRLayerChild.h b/gfx/vr/ipc/VRLayerChild.h new file mode 100644 index 0000000000..de91a46b87 --- /dev/null +++ b/gfx/vr/ipc/VRLayerChild.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_LAYERCHILD_H +#define GFX_VR_LAYERCHILD_H + +#include "VRManagerChild.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/PVRLayerChild.h" +#include "gfxVR.h" + +class nsICanvasRenderingContextInternal; + +namespace mozilla { +class WebGLContext; +class WebGLFramebufferJS; +namespace dom { +class HTMLCanvasElement; +} +namespace layers { +class SharedSurfaceTextureClient; +} +namespace gl { +class SurfaceFactory; +} +namespace gfx { + +class VRLayerChild : public PVRLayerChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRLayerChild) + + public: + static PVRLayerChild* CreateIPDLActor(); + static bool DestroyIPDLActor(PVRLayerChild* actor); + + void Initialize(dom::HTMLCanvasElement* aCanvasElement, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect); + void SetXRFramebuffer(WebGLFramebufferJS*); + void SubmitFrame(const VRDisplayInfo& aDisplayInfo); + bool IsIPCOpen(); + + private: + VRLayerChild(); + virtual ~VRLayerChild(); + void ClearSurfaces(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<dom::HTMLCanvasElement> mCanvasElement; + bool mIPCOpen = false; + + // AddIPDLReference and ReleaseIPDLReference are only to be called by + // CreateIPDLActor and DestroyIPDLActor, respectively. We intentionally make + // them private to prevent misuse. The purpose of these methods is to be aware + // of when the IPC system around this actor goes down: mIPCOpen is then set to + // false. + void AddIPDLReference(); + void ReleaseIPDLReference(); + + gfx::Rect mLeftEyeRect; + gfx::Rect mRightEyeRect; + RefPtr<WebGLFramebufferJS> mFramebuffer; + + Maybe<layers::SurfaceDescriptor> mThisFrameTextureDesc; + Maybe<layers::SurfaceDescriptor> mLastFrameTextureDesc; + + uint64_t mLastSubmittedFrameId = 0; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/vr/ipc/VRLayerParent.cpp b/gfx/vr/ipc/VRLayerParent.cpp new file mode 100644 index 0000000000..b75a151027 --- /dev/null +++ b/gfx/vr/ipc/VRLayerParent.cpp @@ -0,0 +1,55 @@ +/* -*- 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 "VRLayerParent.h" +#include "VRManager.h" +#include "mozilla/Unused.h" +#include "mozilla/layers/CompositorThread.h" + +namespace mozilla { +using namespace layers; +namespace gfx { + +VRLayerParent::VRLayerParent(uint32_t aVRDisplayID, const uint32_t aGroup) + : mIPCOpen(true), mDestroyed(false), mGroup(aGroup) {} + +VRLayerParent::~VRLayerParent() { + Destroy(); + MOZ_COUNT_DTOR(VRLayerParent); +} + +mozilla::ipc::IPCResult VRLayerParent::RecvDestroy() { + Destroy(); + return IPC_OK(); +} + +void VRLayerParent::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; } + +void VRLayerParent::Destroy() { + if (!mDestroyed) { + VRManager* vm = VRManager::Get(); + vm->RemoveLayer(this); + mDestroyed = true; + } + + if (mIPCOpen) { + Unused << PVRLayerParent::Send__delete__(this); + } +} + +mozilla::ipc::IPCResult VRLayerParent::RecvSubmitFrame( + const layers::SurfaceDescriptor& aTexture, const uint64_t& aFrameId, + const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { + if (!mDestroyed) { + VRManager* vm = VRManager::Get(); + vm->SubmitFrame(this, aTexture, aFrameId, aLeftEyeRect, aRightEyeRect); + } + + return IPC_OK(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRLayerParent.h b/gfx/vr/ipc/VRLayerParent.h new file mode 100644 index 0000000000..559ee1c7de --- /dev/null +++ b/gfx/vr/ipc/VRLayerParent.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_LAYERPARENT_H +#define GFX_VR_LAYERPARENT_H + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/PVRLayerParent.h" +#include "gfxVR.h" + +namespace mozilla { +namespace gfx { + +class VRLayerParent : public PVRLayerParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRLayerParent) + + public: + VRLayerParent(uint32_t aVRDisplayID, const uint32_t aGroup); + virtual mozilla::ipc::IPCResult RecvSubmitFrame( + const layers::SurfaceDescriptor& aTexture, const uint64_t& aFrameId, + const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) override; + virtual mozilla::ipc::IPCResult RecvDestroy() override; + uint32_t GetGroup() const { return mGroup; } + + protected: + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual ~VRLayerParent(); + void Destroy(); + + bool mIPCOpen; + + bool mDestroyed; + gfx::Rect mLeftEyeRect; + gfx::Rect mRightEyeRect; + uint32_t mGroup; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/vr/ipc/VRManagerChild.cpp b/gfx/vr/ipc/VRManagerChild.cpp new file mode 100644 index 0000000000..84e293568d --- /dev/null +++ b/gfx/vr/ipc/VRManagerChild.cpp @@ -0,0 +1,631 @@ +/* -*- 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 "nsGlobalWindow.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<RefPtr<mozilla::gfx::VRManagerEventObserver>>::index_type + kNoIndex = nsTArray<RefPtr<mozilla::gfx::VRManagerEventObserver>>::NoIndex; +} // namespace + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton; +static StaticRefPtr<VRManagerParent> 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(); + AddRef(); +} + +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<RefPtr<VRDisplayClient>> 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<PVRManagerChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<VRManagerChild> 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<PVRManagerChild>&& 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::ActorDealloc() { Release(); } + +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<uint32_t> disconnectedDisplays; + nsTArray<uint32_t> connectedDisplays; + + const nsTArray<RefPtr<VRDisplayClient>> 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<VRManagerChild> 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<RefPtr<VRDisplayClient>> 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<RefPtr<VRManagerEventObserver>> 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<dom::Promise> promise = mRunPuppetPromise; + mRunPuppetPromise = nullptr; + if (aSuccess) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } else { + promise->MaybeRejectWithUndefined(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetResetComplete() { + nsTArray<RefPtr<dom::Promise>> promises; + promises.AppendElements(mResetPuppetPromises); + mResetPuppetPromises.Clear(); + for (const auto& promise : promises) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } + return IPC_OK(); +} + +void VRManagerChild::RunPuppet(const nsTArray<uint64_t>& 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<RefPtr<VRDisplayClient>>& 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<mozilla::dom::FrameRequestCallback> callback = mCallback; + callback->Call(aTimeStamp); + } else { + RefPtr<mozilla::dom::XRFrameRequestCallback> callback = mXRCallback; + RefPtr<mozilla::dom::XRFrame> 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<XRFrameRequest> 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<uint32_t>( + "gfx::VRManagerChild::NotifyPresentationGenerationChangedInternal", this, + &VRManagerChild::NotifyPresentationGenerationChangedInternal, + aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayMountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayMountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayMountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayUnmountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayConnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayConnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayDisconnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "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<VRManagerEventObserver>>( + "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) const { + 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<dom::Promise> 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 diff --git a/gfx/vr/ipc/VRManagerChild.h b/gfx/vr/ipc/VRManagerChild.h new file mode 100644 index 0000000000..e980c69b88 --- /dev/null +++ b/gfx/vr/ipc/VRManagerChild.h @@ -0,0 +1,199 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_VR_VRMANAGERCHILD_H +#define MOZILLA_GFX_VR_VRMANAGERCHILD_H + +#include "nsISupportsImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/AnimationFrameProviderBinding.h" +#include "mozilla/dom/WebXRBinding.h" +#include "mozilla/dom/XRFrame.h" +#include "mozilla/gfx/PVRManagerChild.h" +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/LayersTypes.h" // for LayersBackend + +namespace mozilla { +namespace dom { +class Promise; +class GamepadManager; +class Navigator; +class VRDisplay; +class FrameRequestCallback; +} // namespace dom +namespace gfx { +class VRLayerChild; +class VRDisplayClient; + +class VRManagerEventObserver { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + virtual void NotifyVRDisplayMounted(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayUnmounted(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayConnect(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayDisconnect(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayPresentChange(uint32_t aDisplayID) = 0; + virtual void NotifyPresentationGenerationChanged(uint32_t aDisplayID) = 0; + virtual bool GetStopActivityStatus() const = 0; + virtual void NotifyEnumerationCompleted() = 0; + virtual void NotifyDetectRuntimesCompleted() = 0; + + protected: + virtual ~VRManagerEventObserver() = default; +}; + +class VRManagerChild : public PVRManagerChild { + friend class PVRManagerChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRManagerChild); + + static VRManagerChild* Get(); + + // Indicate that an observer wants to receive VR events. + void AddListener(VRManagerEventObserver* aObserver); + // Indicate that an observer should no longer receive VR events. + void RemoveListener(VRManagerEventObserver* aObserver); + void StartActivity(); + void StopActivity(); + bool RuntimeSupportsVR() const; + bool RuntimeSupportsAR() const; + bool RuntimeSupportsInline() const; + + void GetVRDisplays(nsTArray<RefPtr<VRDisplayClient>>& aDisplays); + bool RefreshVRDisplaysWithCallback(uint64_t aWindowId); + bool EnumerateVRDisplays(); + void DetectRuntimes(); + void AddPromise(const uint32_t& aID, dom::Promise* aPromise); + gfx::VRAPIMode GetVRAPIMode(uint32_t aDisplayID) const; + + static void InitSameProcess(); + static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint); + static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint); + static void ShutDown(); + + static bool IsCreated(); + static bool IsPresenting(); + static TimeStamp GetIdleDeadlineHint(TimeStamp aDefault); + + PVRLayerChild* CreateVRLayer(uint32_t aDisplayID, uint32_t aGroup); + + static void IdentifyTextureHost( + const layers::TextureFactoryIdentifier& aIdentifier); + layers::LayersBackend GetBackendType() const; + + nsresult ScheduleFrameRequestCallback(dom::FrameRequestCallback& aCallback, + int32_t* aHandle); + void CancelFrameRequestCallback(int32_t aHandle); + MOZ_CAN_RUN_SCRIPT + void RunFrameRequestCallbacks(); + void NotifyPresentationGenerationChanged(uint32_t aDisplayID); + + MOZ_CAN_RUN_SCRIPT + void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo); + void FireDOMVRDisplayMountedEvent(uint32_t aDisplayID); + void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID); + void FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID); + void FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventsForLoad(VRManagerEventObserver* aObserver); + + void HandleFatalError(const char* aMsg) const override; + void ActorDestroy(ActorDestroyReason aReason) override; + + void RunPuppet(const nsTArray<uint64_t>& aBuffer, dom::Promise* aPromise, + ErrorResult& aRv); + void ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv); + + protected: + explicit VRManagerChild(); + ~VRManagerChild(); + + PVRLayerChild* AllocPVRLayerChild(const uint32_t& aDisplayID, + const uint32_t& aGroup); + bool DeallocPVRLayerChild(PVRLayerChild* actor); + + void ActorDealloc() override; + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can mark ipdl-generated things as + // MOZ_CAN_RUN_SCRIPT. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvUpdateDisplayInfo( + const VRDisplayInfo& aDisplayInfo); + mozilla::ipc::IPCResult RecvUpdateRuntimeCapabilities( + const VRDisplayCapabilityFlags& aCapabilities); + mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic( + const uint32_t& aPromiseID); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvNotifyPuppetCommandBufferCompleted(bool aSuccess); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvNotifyPuppetResetComplete(); + + bool IsSameProcess() const { return OtherPid() == base::GetCurrentProcId(); } + + private: + void FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventsForLoadInternal( + uint32_t aDisplayID, VRManagerEventObserver* aObserver); + void NotifyPresentationGenerationChangedInternal(uint32_t aDisplayID); + void NotifyEnumerationCompletedInternal(); + void NotifyRuntimeCapabilitiesUpdatedInternal(); + + nsTArray<RefPtr<VRDisplayClient>> mDisplays; + VRDisplayCapabilityFlags mRuntimeCapabilities; + bool mDisplaysInitialized; + nsTArray<uint64_t> mNavigatorCallbacks; + + struct XRFrameRequest { + XRFrameRequest(mozilla::dom::FrameRequestCallback& aCallback, + int32_t aHandle) + : mCallback(&aCallback), mHandle(aHandle) {} + + XRFrameRequest(mozilla::dom::XRFrameRequestCallback& aCallback, + mozilla::dom::XRFrame& aFrame, int32_t aHandle) + : mXRCallback(&aCallback), mXRFrame(&aFrame), mHandle(aHandle) {} + MOZ_CAN_RUN_SCRIPT + void Call(const DOMHighResTimeStamp& aTimeStamp); + + // Comparator operators to allow RemoveElementSorted with an + // integer argument on arrays of XRFrameRequest + bool operator==(int32_t aHandle) const { return mHandle == aHandle; } + bool operator<(int32_t aHandle) const { return mHandle < aHandle; } + + RefPtr<mozilla::dom::FrameRequestCallback> mCallback; + RefPtr<mozilla::dom::XRFrameRequestCallback> mXRCallback; + RefPtr<mozilla::dom::XRFrame> mXRFrame; + int32_t mHandle; + }; + + nsTArray<XRFrameRequest> mFrameRequestCallbacks; + /** + * The current frame request callback handle + */ + int32_t mFrameRequestCallbackCounter; + mozilla::TimeStamp mStartTimeStamp; + + nsTArray<RefPtr<VRManagerEventObserver>> mListeners; + bool mWaitingForEnumeration; + + layers::LayersBackend mBackend; + nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mGamepadPromiseList; + RefPtr<dom::Promise> mRunPuppetPromise; + nsTArray<RefPtr<dom::Promise>> mResetPuppetPromises; + + DISALLOW_COPY_AND_ASSIGN(VRManagerChild); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_VR_VRMANAGERCHILD_H diff --git a/gfx/vr/ipc/VRManagerParent.cpp b/gfx/vr/ipc/VRManagerParent.cpp new file mode 100644 index 0000000000..01a2f4e6a5 --- /dev/null +++ b/gfx/vr/ipc/VRManagerParent.cpp @@ -0,0 +1,308 @@ +/* -*- 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 "VRManagerParent.h" + +#include "ipc/VRLayerParent.h" +#include "mozilla/gfx/PVRManagerParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/Unused.h" +#include "VRManager.h" +#include "VRThread.h" + +using mozilla::dom::GamepadHandle; + +namespace mozilla { +using namespace layers; +namespace gfx { + +// See VRManagerChild.cpp +void ReleaseVRManagerParentSingleton(); + +VRManagerParent::VRManagerParent(ProcessId aChildProcessId, + bool aIsContentChild) + : mHaveEventListener(false), + mHaveControllerListener(false), + mIsContentChild(aIsContentChild), + mVRActiveStatus(false) { + MOZ_COUNT_CTOR(VRManagerParent); + MOZ_ASSERT(NS_IsMainThread()); + + SetOtherProcessId(aChildProcessId); +} + +VRManagerParent::~VRManagerParent() { + MOZ_ASSERT(!mVRManagerHolder); + + MOZ_COUNT_DTOR(VRManagerParent); +} + +PVRLayerParent* VRManagerParent::AllocPVRLayerParent(const uint32_t& aDisplayID, + const uint32_t& aGroup) { + if (!StaticPrefs::dom_vr_enabled() && !StaticPrefs::dom_vr_webxr_enabled()) { + return nullptr; + } + + RefPtr<VRLayerParent> layer; + layer = new VRLayerParent(aDisplayID, aGroup); + VRManager* vm = VRManager::Get(); + vm->AddLayer(layer); + return layer.forget().take(); +} + +bool VRManagerParent::DeallocPVRLayerParent(PVRLayerParent* actor) { + delete actor; + return true; +} + +bool VRManagerParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void VRManagerParent::RegisterWithManager() { + VRManager* vm = VRManager::Get(); + vm->AddVRManagerParent(this); + mVRManagerHolder = vm; +} + +void VRManagerParent::UnregisterFromManager() { + VRManager* vm = VRManager::Get(); + vm->RemoveVRManagerParent(this); + mVRManagerHolder = nullptr; +} + +/* static */ +bool VRManagerParent::CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint) { + if (!CompositorThread()) { + return false; + } + + RefPtr<VRManagerParent> vmp = new VRManagerParent(aEndpoint.OtherPid(), true); + CompositorThread()->Dispatch(NewRunnableMethod<Endpoint<PVRManagerParent>&&>( + "gfx::VRManagerParent::Bind", vmp, &VRManagerParent::Bind, + std::move(aEndpoint))); + + return true; +} + +void VRManagerParent::Bind(Endpoint<PVRManagerParent>&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + mSelfRef = this; + + RegisterWithManager(); +} + +/*static*/ +void VRManagerParent::RegisterVRManagerInCompositorThread( + VRManagerParent* aVRManager) { + aVRManager->RegisterWithManager(); +} + +/*static*/ +VRManagerParent* VRManagerParent::CreateSameProcess() { + RefPtr<VRManagerParent> vmp = + new VRManagerParent(base::GetCurrentProcId(), false); + vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); + vmp->mSelfRef = vmp; + CompositorThread()->Dispatch( + NewRunnableFunction("RegisterVRManagerIncompositorThreadRunnable", + RegisterVRManagerInCompositorThread, vmp.get())); + return vmp.get(); +} + +bool VRManagerParent::CreateForGPUProcess( + Endpoint<PVRManagerParent>&& aEndpoint) { + RefPtr<VRManagerParent> vmp = + new VRManagerParent(aEndpoint.OtherPid(), false); + vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); + vmp->mSelfRef = vmp; + CompositorThread()->Dispatch(NewRunnableMethod<Endpoint<PVRManagerParent>&&>( + "gfx::VRManagerParent::Bind", vmp, &VRManagerParent::Bind, + std::move(aEndpoint))); + return true; +} + +/*static*/ +void VRManagerParent::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT( + CompositorThread(), + "Shutdown() must gets called before the compositor thread is shutdown"); + ReleaseVRManagerParentSingleton(); + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "VRManagerParent::Shutdown", + [vm = RefPtr<VRManager>(VRManager::MaybeGet())]() -> void { + if (!vm) { + return; + } + vm->ShutdownVRManagerParents(); + })); +} + +void VRManagerParent::ActorDestroy(ActorDestroyReason why) {} + +void VRManagerParent::ActorAlloc() { + // FIXME: This actor should probably use proper refcounting instead of manual + // reference management, and probably shouldn't manage + // `mCompositorThreadHolder` in the alloc/dealloc methods. + PVRManagerParent::ActorAlloc(); + mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); +} + +void VRManagerParent::ActorDealloc() { + UnregisterFromManager(); + mCompositorThreadHolder = nullptr; + mSelfRef = nullptr; +} + +mozilla::ipc::IPCResult VRManagerParent::RecvDetectRuntimes() { + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // detection. + VRManager* vm = VRManager::Get(); + vm->DetectRuntimes(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvRefreshDisplays() { + // This is called to activate the VR runtimes, detecting the + // presence and capabilities of XR hardware. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // enumerated hardware. + VRManager* vm = VRManager::Get(); + vm->EnumerateDevices(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvSetGroupMask( + const uint32_t& aDisplayID, const uint32_t& aGroupMask) { + VRManager* vm = VRManager::Get(); + vm->SetGroupMask(aGroupMask); + return IPC_OK(); +} + +bool VRManagerParent::HaveEventListener() { return mHaveEventListener; } + +bool VRManagerParent::HaveControllerListener() { + return mHaveControllerListener; +} + +bool VRManagerParent::GetVRActiveStatus() { return mVRActiveStatus; } + +mozilla::ipc::IPCResult VRManagerParent::RecvSetHaveEventListener( + const bool& aHaveEventListener) { + mHaveEventListener = aHaveEventListener; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerAdded() { + // Force update the available controllers for GamepadManager, + VRManager* vm = VRManager::Get(); + vm->StopAllHaptics(); + mHaveControllerListener = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerRemoved() { + mHaveControllerListener = false; + VRManager* vm = VRManager::Get(); + vm->StopAllHaptics(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvRunPuppet( + const nsTArray<uint64_t>& aBuffer) { +#if defined(MOZ_WIDGET_ANDROID) + // Not yet implemented for Android / GeckoView + // See Bug 1555192 + Unused << SendNotifyPuppetCommandBufferCompleted(false); +#else + VRManager* vm = VRManager::Get(); + if (!vm->RunPuppet(aBuffer, this)) { + // We have immediately failed, need to resolve the + // promise right away + Unused << SendNotifyPuppetCommandBufferCompleted(false); + } +#endif // defined(MOZ_WIDGET_ANDROID) + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvResetPuppet() { +#if defined(MOZ_WIDGET_ANDROID) + // Not yet implemented for Android / GeckoView + // See Bug 1555192 +#else + VRManager* vm = VRManager::Get(); + vm->ResetPuppet(this); +#endif // defined(MOZ_WIDGET_ANDROID) + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle, + const uint32_t& aHapticIndex, const double& aIntensity, + const double& aDuration, const uint32_t& aPromiseID) { + VRManager* vm = VRManager::Get(); + VRManagerPromise promise(this, aPromiseID); + + vm->VibrateHaptic(aGamepadHandle, aHapticIndex, aIntensity, aDuration, + promise); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle) { + VRManager* vm = VRManager::Get(); + vm->StopVibrateHaptic(aGamepadHandle); + return IPC_OK(); +} + +bool VRManagerParent::SendReplyGamepadVibrateHaptic( + const uint32_t& aPromiseID) { + // GamepadManager only exists at the content process + // or the same process in non-e10s mode. + if (mHaveControllerListener && (mIsContentChild || IsSameProcess())) { + return PVRManagerParent::SendReplyGamepadVibrateHaptic(aPromiseID); + } + + return true; +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStartVRNavigation( + const uint32_t& aDeviceID) { + VRManager* vm = VRManager::Get(); + vm->StartVRNavigation(aDeviceID); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopVRNavigation( + const uint32_t& aDeviceID, const TimeDuration& aTimeout) { + VRManager* vm = VRManager::Get(); + vm->StopVRNavigation(aDeviceID, aTimeout); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStartActivity() { + mVRActiveStatus = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopActivity() { + mVRActiveStatus = false; + return IPC_OK(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRManagerParent.h b/gfx/vr/ipc/VRManagerParent.h new file mode 100644 index 0000000000..bc23d541a6 --- /dev/null +++ b/gfx/vr/ipc/VRManagerParent.h @@ -0,0 +1,122 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_VR_VRMANAGERPARENT_H +#define MOZILLA_GFX_VR_VRMANAGERPARENT_H + +#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder +#include "mozilla/layers/CompositableTransactionParent.h" // need? +#include "mozilla/gfx/PVRManagerParent.h" // for PVRManagerParent +#include "mozilla/gfx/PVRLayerParent.h" // for PVRLayerParent +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "gfxVR.h" // for VRFieldOfView + +namespace mozilla { +using namespace layers; +namespace gfx { + +class VRManager; + +class VRManagerParent final : public PVRManagerParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRManagerParent); + + friend class PVRManagerParent; + + public: + explicit VRManagerParent(ProcessId aChildProcessId, bool aIsContentChild); + + static VRManagerParent* CreateSameProcess(); + static bool CreateForGPUProcess(Endpoint<PVRManagerParent>&& aEndpoint); + static bool CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint); + static void Shutdown(); + + bool IsSameProcess() const; + bool HaveEventListener(); + bool HaveControllerListener(); + bool GetVRActiveStatus(); + bool SendReplyGamepadVibrateHaptic(const uint32_t& aPromiseID); + + protected: + ~VRManagerParent(); + + PVRLayerParent* AllocPVRLayerParent(const uint32_t& aDisplayID, + const uint32_t& aGroup); + bool DeallocPVRLayerParent(PVRLayerParent* actor); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + mozilla::ipc::IPCResult RecvDetectRuntimes(); + mozilla::ipc::IPCResult RecvRefreshDisplays(); + mozilla::ipc::IPCResult RecvSetGroupMask(const uint32_t& aDisplayID, + const uint32_t& aGroupMask); + mozilla::ipc::IPCResult RecvSetHaveEventListener( + const bool& aHaveEventListener); + mozilla::ipc::IPCResult RecvControllerListenerAdded(); + mozilla::ipc::IPCResult RecvControllerListenerRemoved(); + mozilla::ipc::IPCResult RecvVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle, + const uint32_t& aHapticIndex, const double& aIntensity, + const double& aDuration, const uint32_t& aPromiseID); + mozilla::ipc::IPCResult RecvStopVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle); + mozilla::ipc::IPCResult RecvStartVRNavigation(const uint32_t& aDeviceID); + mozilla::ipc::IPCResult RecvStopVRNavigation(const uint32_t& aDeviceID, + const TimeDuration& aTimeout); + mozilla::ipc::IPCResult RecvStartActivity(); + mozilla::ipc::IPCResult RecvStopActivity(); + + mozilla::ipc::IPCResult RecvRunPuppet(const nsTArray<uint64_t>& aBuffer); + mozilla::ipc::IPCResult RecvResetPuppet(); + + private: + void ActorAlloc() override; + void ActorDealloc() override; + void RegisterWithManager(); + void UnregisterFromManager(); + + void Bind(Endpoint<PVRManagerParent>&& aEndpoint); + + static void RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager); + + // This keeps us alive until ActorDestroy(), at which point we do a + // deferred destruction of ourselves. + RefPtr<VRManagerParent> mSelfRef; + // Keep the compositor thread alive, until we have destroyed ourselves. + RefPtr<CompositorThreadHolder> mCompositorThreadHolder; + + // Keep the VRManager alive, until we have destroyed ourselves. + RefPtr<VRManager> mVRManagerHolder; + bool mHaveEventListener; + bool mHaveControllerListener; + bool mIsContentChild; + + // When VR tabs are switched the background, we won't need to + // initialize its session in VRService thread. + bool mVRActiveStatus; +}; + +class VRManagerPromise final { + friend class VRManager; + + public: + explicit VRManagerPromise(RefPtr<VRManagerParent> aParent, + uint32_t aPromiseID) + : mParent(aParent), mPromiseID(aPromiseID) {} + ~VRManagerPromise() { mParent = nullptr; } + bool operator==(const VRManagerPromise& aOther) const { + return mParent == aOther.mParent && mPromiseID == aOther.mPromiseID; + } + + private: + RefPtr<VRManagerParent> mParent; + uint32_t mPromiseID; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_VR_VRMANAGERPARENT_H diff --git a/gfx/vr/ipc/VRMessageUtils.h b/gfx/vr/ipc/VRMessageUtils.h new file mode 100644 index 0000000000..0d19c9e4ec --- /dev/null +++ b/gfx/vr/ipc/VRMessageUtils.h @@ -0,0 +1,73 @@ +/* -*- 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/. */ + +#ifndef mozilla_gfx_vr_VRMessageUtils_h +#define mozilla_gfx_vr_VRMessageUtils_h + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/dom/GamepadMessageUtils.h" + +#include "gfxVR.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::gfx::VRControllerType> + : public ContiguousEnumSerializer<mozilla::gfx::VRControllerType, + mozilla::gfx::VRControllerType::_empty, + mozilla::gfx::VRControllerType::_end> {}; + +// VRHMDSensorState is POD, we can use PlainOldDataSerializer +static_assert(std::is_pod<mozilla::gfx::VRHMDSensorState>::value, + "mozilla::gfx::VRHMDSensorState must be a POD type."); +template <> +struct ParamTraits<mozilla::gfx::VRHMDSensorState> + : public PlainOldDataSerializer<mozilla::gfx::VRHMDSensorState> {}; + +// VRDisplayInfo is POD, we can use PlainOldDataSerializer +static_assert(std::is_pod<mozilla::gfx::VRDisplayInfo>::value, + "mozilla::gfx::VRDisplayInfo must be a POD type."); +template <> +struct ParamTraits<mozilla::gfx::VRDisplayInfo> + : public PlainOldDataSerializer<mozilla::gfx::VRDisplayInfo> {}; + +template <> +struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo> { + typedef mozilla::gfx::VRSubmitFrameResultInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mBase64Image); + WriteParam(aWriter, aParam.mFormat); + WriteParam(aWriter, aParam.mWidth); + WriteParam(aWriter, aParam.mHeight); + WriteParam(aWriter, aParam.mFrameNum); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->mBase64Image)) || + !ReadParam(aReader, &(aResult->mFormat)) || + !ReadParam(aReader, &(aResult->mWidth)) || + !ReadParam(aReader, &(aResult->mHeight)) || + !ReadParam(aReader, &(aResult->mFrameNum))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags> + : public BitFlagsEnumSerializer< + mozilla::gfx::VRDisplayCapabilityFlags, + mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {}; + +} // namespace IPC + +#endif // mozilla_gfx_vr_VRMessageUtils_h diff --git a/gfx/vr/ipc/VRParent.cpp b/gfx/vr/ipc/VRParent.cpp new file mode 100644 index 0000000000..234352ba08 --- /dev/null +++ b/gfx/vr/ipc/VRParent.cpp @@ -0,0 +1,191 @@ +/* -*- 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 "VRParent.h" +#include "VRGPUParent.h" +#include "gfxConfig.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" +#include "nsPrintfCString.h" + +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/Preferences.h" + +#if defined(XP_WIN) +# include <process.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#else +# include <unistd.h> +#endif + +namespace mozilla { +namespace gfx { + +using mozilla::ipc::IPCResult; + +VRParent::VRParent() : mVRGPUParent(nullptr) {} + +IPCResult VRParent::RecvNewGPUVRManager(Endpoint<PVRGPUParent>&& aEndpoint) { + RefPtr<VRGPUParent> vrGPUParent = + VRGPUParent::CreateForGPU(std::move(aEndpoint)); + if (!vrGPUParent) { + return IPC_FAIL_NO_REASON(this); + } + + mVRGPUParent = std::move(vrGPUParent); + return IPC_OK(); +} + +IPCResult VRParent::RecvInit(nsTArray<GfxVarUpdate>&& vars, + const DevicePrefs& devicePrefs) { + Unused << SendInitComplete(); + + for (const auto& var : vars) { + gfxVars::ApplyUpdate(var); + } + + // Inherit device preferences. + gfxConfig::Inherit(Feature::HW_COMPOSITING, devicePrefs.hwCompositing()); + gfxConfig::Inherit(Feature::D3D11_COMPOSITING, + devicePrefs.d3d11Compositing()); + gfxConfig::Inherit(Feature::OPENGL_COMPOSITING, devicePrefs.oglCompositing()); + gfxConfig::Inherit(Feature::DIRECT2D, devicePrefs.useD2D1()); + +#if defined(XP_WIN) + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + DeviceManagerDx::Get()->CreateCompositorDevices(); + } +#endif + return IPC_OK(); +} + +IPCResult VRParent::RecvUpdateVar(const GfxVarUpdate& aUpdate) { + gfxVars::ApplyUpdate(aUpdate); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerActionPathToVR( + const nsCString& aPath) { + mOpenVRControllerAction = aPath; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerManifestPathToVR( + const VRControllerType& aType, const nsCString& aPath) { + mOpenVRControllerManifest.InsertOrUpdate(static_cast<uint32_t>(aType), aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + MOZ_ASSERT(XRE_IsVRProcess()); + nsPrintfCString processName("VR (pid %u)", (unsigned)getpid()); + + mozilla::dom::MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName, + [&](const MemoryReport& aReport) { + Unused << SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +void VRParent::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down VR process early due to a crash!"); + ipc::ProcessChild::QuickExit(); + } + if (mVRGPUParent && !mVRGPUParent->IsClosed()) { + mVRGPUParent->Close(); + } + mVRGPUParent = nullptr; + +#ifndef NS_FREE_PERMANENT_DATA + // No point in going through XPCOM shutdown because we don't keep persistent + // state. + ipc::ProcessChild::QuickExit(); +#endif + +#if defined(XP_WIN) + DeviceManagerDx::Shutdown(); +#endif + gfxVars::Shutdown(); + gfxConfig::Shutdown(); + ipc::CrashReporterClient::DestroySingleton(); + // Only calling XRE_ShutdownChildProcess() at the child process + // instead of the main process. Otherwise, it will close all child processes + // that are spawned from the main process. + XRE_ShutdownChildProcess(); +} + +bool VRParent::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID) { + // Initialize the thread manager before starting IPC. Otherwise, messages + // may be posted to the main thread and we won't be able to process them. + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + + // Now it's safe to start IPC. + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return false; + } + + nsDebugImpl::SetMultiprocessMode("VR"); + + // This must be checked before any IPDL message, which may hit sentinel + // errors due to parent and content processes having different + // versions. + MessageChannel* channel = GetIPCChannel(); + if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) { + // We need to quit this process if the buildID doesn't match the parent's. + // This can occur when an update occurred in the background. + ipc::ProcessChild::QuickExit(); + } + + // Init crash reporter support. + ipc::CrashReporterClient::InitSingleton(this); + + gfxConfig::Init(); + gfxVars::Initialize(); +#if defined(XP_WIN) + DeviceManagerDx::Init(); +#endif + if (NS_FAILED(NS_InitMinimalXPCOM())) { + return false; + } + + mozilla::ipc::SetThisProcessName("VR Process"); + return true; +} + +bool VRParent::GetOpenVRControllerActionPath(nsCString* aPath) { + if (!mOpenVRControllerAction.IsEmpty()) { + *aPath = mOpenVRControllerAction; + return true; + } + + return false; +} + +bool VRParent::GetOpenVRControllerManifestPath(VRControllerType aType, + nsCString* aPath) { + return mOpenVRControllerManifest.Get(static_cast<uint32_t>(aType), aPath); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRParent.h b/gfx/vr/ipc/VRParent.h new file mode 100644 index 0000000000..222ef7ace2 --- /dev/null +++ b/gfx/vr/ipc/VRParent.h @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PARENT_H +#define GFX_VR_PARENT_H + +#include "mozilla/gfx/PVRParent.h" +#include "VRGPUParent.h" + +namespace mozilla { +namespace gfx { + +class VRService; +class VRSystemManagerExternal; + +class VRParent final : public PVRParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRParent); + + friend class PVRParent; + + public: + explicit VRParent(); + + bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool GetOpenVRControllerActionPath(nsCString* aPath); + bool GetOpenVRControllerManifestPath(VRControllerType aType, + nsCString* aPath); + + protected: + ~VRParent() = default; + + mozilla::ipc::IPCResult RecvNewGPUVRManager( + Endpoint<PVRGPUParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvInit(nsTArray<GfxVarUpdate>&& vars, + const DevicePrefs& devicePrefs); + mozilla::ipc::IPCResult RecvNotifyVsync(const TimeStamp& vsyncTimestamp); + mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref); + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); + mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToVR( + const nsCString& aPath); + mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToVR( + const VRControllerType& aType, const nsCString& aPath); + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + + private: + nsCString mOpenVRControllerAction; + nsTHashMap<nsUint32HashKey, nsCString> mOpenVRControllerManifest; + RefPtr<VRGPUParent> mVRGPUParent; + DISALLOW_COPY_AND_ASSIGN(VRParent); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_PARENT_H diff --git a/gfx/vr/ipc/VRProcessChild.cpp b/gfx/vr/ipc/VRProcessChild.cpp new file mode 100644 index 0000000000..be7dc89bd3 --- /dev/null +++ b/gfx/vr/ipc/VRProcessChild.cpp @@ -0,0 +1,49 @@ +/* -*- 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 "VRProcessChild.h" + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/StaticPrefs_dom.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::ipc::IOThreadChild; + +StaticRefPtr<VRParent> sVRParent; + +VRProcessChild::~VRProcessChild() { sVRParent = nullptr; } + +/*static*/ +VRParent* VRProcessChild::GetVRParent() { + MOZ_ASSERT(sVRParent); + return sVRParent; +} + +bool VRProcessChild::Init(int aArgc, char* aArgv[]) { + Maybe<const char*> parentBuildID = + geckoargs::sParentBuildID.Get(aArgc, aArgv); + if (parentBuildID.isNothing()) { + return false; + } + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + return false; + } + + sVRParent = new VRParent(); + sVRParent->Init(TakeInitialEndpoint(), *parentBuildID); + + return true; +} + +void VRProcessChild::CleanUp() { + sVRParent = nullptr; + NS_ShutdownXPCOM(nullptr); +} diff --git a/gfx/vr/ipc/VRProcessChild.h b/gfx/vr/ipc/VRProcessChild.h new file mode 100644 index 0000000000..e3f275c9af --- /dev/null +++ b/gfx/vr/ipc/VRProcessChild.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PROCESS_CHILD_H +#define GFX_VR_PROCESS_CHILD_H + +#include "mozilla/ipc/ProcessChild.h" +#include "VRParent.h" + +namespace mozilla { +namespace gfx { + +/** + * Contains the VRChild object that facilitates IPC communication to/from + * the instance of the VR library that is run in this process. + */ +class VRProcessChild final : public mozilla::ipc::ProcessChild { + protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + using ProcessChild::ProcessChild; + ~VRProcessChild(); + + // IPC channel for VR process talk to the parent process. + static VRParent* GetVRParent(); + + // ProcessChild functions. + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_PROCESS_CHILD_H */ diff --git a/gfx/vr/ipc/VRProcessManager.cpp b/gfx/vr/ipc/VRProcessManager.cpp new file mode 100644 index 0000000000..f3bc13bac0 --- /dev/null +++ b/gfx/vr/ipc/VRProcessManager.cpp @@ -0,0 +1,287 @@ +/* -*- 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 "VRProcessManager.h" + +#include "VRProcessParent.h" +#include "VRChild.h" +#include "VRGPUChild.h" +#include "VRGPUParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/Preferences.h" + +namespace mozilla { +namespace gfx { + +static StaticAutoPtr<VRProcessManager> sSingleton; + +/* static */ +VRProcessManager* VRProcessManager::Get() { return sSingleton; } + +/* static */ +void VRProcessManager::Initialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + if (sSingleton == nullptr) { + sSingleton = new VRProcessManager(); + } +} + +/* static */ +void VRProcessManager::Shutdown() { sSingleton = nullptr; } + +VRProcessManager::VRProcessManager() : mProcess(nullptr), mVRChild(nullptr) { + MOZ_COUNT_CTOR(VRProcessManager); + + mObserver = new Observer(this); + nsContentUtils::RegisterShutdownObserver(mObserver); + Preferences::AddStrongObserver(mObserver, ""); +} + +VRProcessManager::~VRProcessManager() { + MOZ_COUNT_DTOR(VRProcessManager); + + if (mObserver) { + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); + mObserver = nullptr; + } + + DestroyProcess(); + // The VR process should have already been shut down. + MOZ_ASSERT(!mProcess); +} + +void VRProcessManager::LaunchVRProcess() { + if (mProcess) { + return; + } + + // The subprocess is launched asynchronously, so we wait for a callback to + // acquire the IPDL actor. + mProcess = new VRProcessParent(this); + if (!mProcess->Launch()) { + DisableVRProcess("Failed to launch VR process"); + } +} + +void VRProcessManager::DisableVRProcess(const char* aMessage) { + if (!StaticPrefs::dom_vr_process_enabled_AtStartup()) { + return; + } + + DestroyProcess(); +} + +void VRProcessManager::DestroyProcess() { + if (!mProcess) { + return; + } + + mProcess->Shutdown(); + mProcess = nullptr; + mVRChild = nullptr; + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::VRProcessStatus, + "Destroyed"_ns); +} + +bool VRProcessManager::EnsureVRReady() { + if (mProcess && !mProcess->IsConnected()) { + if (!mProcess->WaitForLaunch()) { + // If this fails, we should have fired OnProcessLaunchComplete and + // removed the process. + MOZ_ASSERT(!mProcess && !mVRChild); + return false; + } + } + + if (mVRChild) { + if (mVRChild->EnsureVRReady()) { + return true; + } + + // If the initialization above fails, we likely have a GPU process teardown + // waiting in our message queue (or will soon). We need to ensure we don't + // restart it later because if we fail here, our callers assume they should + // fall back to a combined UI/GPU process. This also ensures our internal + // state is consistent (e.g. process token is reset). + DisableVRProcess("Failed to initialize VR process"); + } + + return false; +} + +void VRProcessManager::OnProcessLaunchComplete(VRProcessParent* aParent) { + MOZ_ASSERT(mProcess && mProcess == aParent); + + mVRChild = mProcess->GetActor(); + + if (!mProcess->IsConnected()) { + DestroyProcess(); + return; + } + + // Flush any pref updates that happened during launch and weren't + // included in the blobs set up in LaunchGPUProcess. + for (const mozilla::dom::Pref& pref : mQueuedPrefs) { + Unused << NS_WARN_IF(!mVRChild->SendPreferenceUpdate(pref)); + } + mQueuedPrefs.Clear(); + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::VRProcessStatus, + "Running"_ns); +} + +void VRProcessManager::OnProcessUnexpectedShutdown(VRProcessParent* aParent) { + MOZ_ASSERT(mProcess && mProcess == aParent); + + DestroyProcess(); +} + +bool VRProcessManager::CreateGPUBridges( + base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutVRBridge) { + if (!CreateGPUVRManager(aOtherProcess, aOutVRBridge)) { + return false; + } + return true; +} + +bool VRProcessManager::CreateGPUVRManager( + base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutEndpoint) { + if (mProcess && !mProcess->IsConnected()) { + NS_WARNING("VR process haven't connected with the parent process yet"); + return false; + } + + base::ProcessId vrparentPid = mProcess + ? mProcess->OtherPid() // VR process id. + : base::GetCurrentProcId(); + + ipc::Endpoint<PVRGPUParent> vrparentPipe; + ipc::Endpoint<PVRGPUChild> vrchildPipe; + nsresult rv = PVRGPU::CreateEndpoints(vrparentPid, // vr process id + aOtherProcess, // gpu process id + &vrparentPipe, &vrchildPipe); + + if (NS_FAILED(rv)) { + gfxCriticalNote << "Could not create gpu-vr bridge: " << hexa(int(rv)); + return false; + } + + // Bind vr-gpu pipe to VRParent and make a PVRGPU connection. + VRChild* vrChild = mProcess->GetActor(); + vrChild->SendNewGPUVRManager(std::move(vrparentPipe)); + + *aOutEndpoint = std::move(vrchildPipe); + return true; +} + +NS_IMPL_ISUPPORTS(VRProcessManager::Observer, nsIObserver); + +VRProcessManager::Observer::Observer(VRProcessManager* aManager) + : mManager(aManager) {} + +NS_IMETHODIMP +VRProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + mManager->OnXPCOMShutdown(); + } else if (!strcmp(aTopic, "nsPref:changed")) { + mManager->OnPreferenceChange(aData); + } + return NS_OK; +} + +void VRProcessManager::CleanShutdown() { DestroyProcess(); } + +void VRProcessManager::OnXPCOMShutdown() { + if (mObserver) { + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); + mObserver = nullptr; + } + + CleanShutdown(); +} + +void VRProcessManager::OnPreferenceChange(const char16_t* aData) { + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + mozilla::dom::Pref pref(strData, /* isLocked */ false, + /* isSanitized */ false, Nothing(), Nothing()); + + Preferences::GetPreference(&pref, GeckoProcessType_VR, + /* remoteType */ ""_ns); + if (!!mVRChild) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + mVRChild->SendPreferenceUpdate(pref); + } else { + mQueuedPrefs.AppendElement(pref); + } +} + +VRChild* VRProcessManager::GetVRChild() { return mProcess->GetActor(); } + +class VRMemoryReporter : public MemoryReportingProcess { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRMemoryReporter, override) + + bool IsAlive() const override { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + return !!vpm->GetVRChild(); + } + return false; + } + + bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) override { + VRChild* child = GetChild(); + if (!child) { + return false; + } + + return child->SendRequestMemoryReport(aGeneration, aAnonymize, + aMinimizeMemoryUsage, aDMDFile); + } + + int32_t Pid() const override { + if (VRChild* child = GetChild()) { + return (int32_t)child->OtherPid(); + } + return 0; + } + + private: + VRChild* GetChild() const { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + return child; + } + } + return nullptr; + } + + protected: + ~VRMemoryReporter() = default; +}; + +RefPtr<MemoryReportingProcess> VRProcessManager::GetProcessMemoryReporter() { + if (!EnsureVRReady()) { + return nullptr; + } + return new VRMemoryReporter(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRProcessManager.h b/gfx/vr/ipc/VRProcessManager.h new file mode 100644 index 0000000000..030045d300 --- /dev/null +++ b/gfx/vr/ipc/VRProcessManager.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ +#ifndef GFX_VR_PROCESS_MANAGER_H +#define GFX_VR_PROCESS_MANAGER_H + +#include "VRProcessParent.h" + +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsIObserver.h" + +namespace mozilla { +class MemoryReportingProcess; +namespace ipc { +template <typename T> +class Endpoint; +} +namespace gfx { + +class VRManagerChild; +class PVRGPUChild; +class VRChild; + +// The VRProcessManager is a singleton responsible for creating VR-bound +// objects that may live in another process. +class VRProcessManager final : public VRProcessParent::Listener { + public: + static VRProcessManager* Get(); + static void Initialize(); + static void Shutdown(); + + ~VRProcessManager(); + + // If not using a VR process, launch a new VR process asynchronously. + void LaunchVRProcess(); + + // Ensure that VR-bound methods can be used. If no VR process is being + // used, or one is launched and ready, this function returns immediately. + // Otherwise it blocks until the VR process has finished launching. + bool EnsureVRReady(); + + bool CreateGPUBridges(base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutVRBridge); + + VRChild* GetVRChild(); + // If a VR process is present, create a MemoryReportingProcess object. + // Otherwise, return null. + RefPtr<MemoryReportingProcess> GetProcessMemoryReporter(); + + virtual void OnProcessLaunchComplete(VRProcessParent* aParent) override; + virtual void OnProcessUnexpectedShutdown(VRProcessParent* aParent) override; + + private: + VRProcessManager(); + + DISALLOW_COPY_AND_ASSIGN(VRProcessManager); + + bool CreateGPUVRManager(base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutEndpoint); + void OnXPCOMShutdown(); + void OnPreferenceChange(const char16_t* aData); + void CleanShutdown(); + void DestroyProcess(); + + // Permanently disable the VR process and record a message why. + void DisableVRProcess(const char* aMessage); + + class Observer final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + explicit Observer(VRProcessManager* aManager); + + protected: + ~Observer() = default; + + VRProcessManager* mManager; + }; + friend class Observer; + + RefPtr<Observer> mObserver; + VRProcessParent* mProcess; + VRChild* mVRChild; + // Collects any pref changes that occur during process launch (after + // the initial map is passed in command-line arguments) to be sent + // when the process can receive IPC messages. + nsTArray<mozilla::dom::Pref> mQueuedPrefs; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_PROCESS_MANAGER_H diff --git a/gfx/vr/ipc/VRProcessParent.cpp b/gfx/vr/ipc/VRProcessParent.cpp new file mode 100644 index 0000000000..cdc50c7dd0 --- /dev/null +++ b/gfx/vr/ipc/VRProcessParent.cpp @@ -0,0 +1,248 @@ +/* -*- 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 "VRProcessParent.h" +#include "VRGPUChild.h" +#include "VRProcessManager.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GPUChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/Unused.h" +#include "VRChild.h" +#include "VRThread.h" + +#include "nsAppRunner.h" // for IToplevelProtocol + +using std::string; +using std::vector; + +using namespace mozilla::ipc; + +namespace mozilla { +namespace gfx { + +VRProcessParent::VRProcessParent(Listener* aListener) + : GeckoChildProcessHost(GeckoProcessType_VR), + mTaskFactory(this), + mListener(aListener), + mLaunchPhase(LaunchPhase::Unlaunched), + mChannelClosed(false), + mShutdownRequested(false) { + MOZ_COUNT_CTOR(VRProcessParent); +} + +VRProcessParent::~VRProcessParent() { MOZ_COUNT_DTOR(VRProcessParent); } + +bool VRProcessParent::Launch() { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); + MOZ_ASSERT(!mVRChild); + mLaunchThread = NS_GetCurrentThread(); + + mLaunchPhase = LaunchPhase::Waiting; + + std::vector<std::string> extraArgs; + ProcessChild::AddPlatformBuildID(extraArgs); + + mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); + if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_VR, + /* remoteType */ ""_ns)) { + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*this, extraArgs); + + if (!GeckoChildProcessHost::AsyncLaunch(extraArgs)) { + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + return false; + } + return true; +} + +bool VRProcessParent::WaitForLaunch() { + if (mLaunchPhase == LaunchPhase::Complete) { + return !!mVRChild; + } + + int32_t timeoutMs = + StaticPrefs::dom_vr_process_startup_timeout_ms_AtStartup(); + + // If one of the following environment variables are set we can effectively + // ignore the timeout - as we can guarantee the compositor process will be + // terminated + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || + PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + timeoutMs = 0; + } + + // Our caller expects the connection to be finished after we return, so we + // immediately set up the IPDL actor and fire callbacks. The IO thread will + // still dispatch a notification to the main thread - we'll just ignore it. + bool result = GeckoChildProcessHost::WaitUntilConnected(timeoutMs); + result &= InitAfterConnect(result); + return result; +} + +void VRProcessParent::Shutdown() { + MOZ_ASSERT(!mShutdownRequested); + mListener = nullptr; + + if (mVRChild) { + // The channel might already be closed if we got here unexpectedly. + if (!mChannelClosed) { + mVRChild->Close(); + } + // OnChannelClosed uses this to check if the shutdown was expected or + // unexpected. + mShutdownRequested = true; + +#ifndef NS_FREE_PERMANENT_DATA + // No need to communicate shutdown, the VR process doesn't need to + // communicate anything back. + KillHard("NormalShutdown"); +#endif + + // If we're shutting down unexpectedly, we're in the middle of handling an + // ActorDestroy for PVRChild, which is still on the stack. We'll return + // back to OnChannelClosed. + // + // Otherwise, we'll wait for OnChannelClose to be called whenever PVRChild + // acknowledges shutdown. + return; + } + + DestroyProcess(); +} + +void VRProcessParent::DestroyProcess() { + if (mLaunchThread) { + // Cancel all tasks. We don't want anything triggering after our caller + // expects this to go away. + { + MonitorAutoLock lock(mMonitor); + mTaskFactory.RevokeAll(); + } + + mLaunchThread->Dispatch(NS_NewRunnableFunction("DestroyProcessRunnable", + [this] { Destroy(); })); + } +} + +bool VRProcessParent::InitAfterConnect(bool aSucceeded) { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); + MOZ_ASSERT(!mVRChild); + + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + + if (aSucceeded) { + GPUChild* gpuChild = GPUProcessManager::Get()->GetGPUChild(); + if (!gpuChild) { + NS_WARNING( + "GPU process haven't connected with the parent process yet" + "when creating VR process."); + return false; + } + + if (!StaticPrefs::dom_vr_enabled() && + !StaticPrefs::dom_vr_webxr_enabled()) { + NS_WARNING("VR is not enabled when trying to create a VRChild"); + return false; + } + + mVRChild = MakeUnique<VRChild>(this); + + DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mVRChild.get()); + MOZ_ASSERT(rv); + + mVRChild->Init(); + + if (mListener) { + mListener->OnProcessLaunchComplete(this); + } + + // Make vr-gpu process connection + Endpoint<PVRGPUChild> vrGPUBridge; + VRProcessManager* vpm = VRProcessManager::Get(); + DebugOnly<bool> opened = + vpm->CreateGPUBridges(gpuChild->OtherPid(), &vrGPUBridge); + MOZ_ASSERT(opened); + + Unused << gpuChild->SendInitVR(std::move(vrGPUBridge)); + } + + return true; +} + +void VRProcessParent::KillHard(const char* aReason) { + ProcessHandle handle = GetChildProcessHandle(); + if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) { + NS_WARNING("failed to kill subprocess!"); + } + + SetAlreadyDead(); +} + +void VRProcessParent::OnChannelError() { + MOZ_ASSERT(false, "VR process channel error."); +} + +void VRProcessParent::OnChannelConnected(base::ProcessId peer_pid) { + MOZ_ASSERT(!NS_IsMainThread()); + + GeckoChildProcessHost::OnChannelConnected(peer_pid); + + // Post a task to the main thread. Take the lock because mTaskFactory is not + // thread-safe. + RefPtr<Runnable> runnable; + { + MonitorAutoLock lock(mMonitor); + runnable = mTaskFactory.NewRunnableMethod( + &VRProcessParent::OnChannelConnectedTask); + } + NS_DispatchToMainThread(runnable); +} + +void VRProcessParent::OnChannelConnectedTask() { + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } +} + +void VRProcessParent::OnChannelErrorTask() { + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(false); + } +} + +void VRProcessParent::OnChannelClosed() { + mChannelClosed = true; + if (!mShutdownRequested && mListener) { + // This is an unclean shutdown. Notify we're going away. + mListener->OnProcessUnexpectedShutdown(this); + } else { + DestroyProcess(); + } + + // Release the actor. + VRChild::Destroy(std::move(mVRChild)); + MOZ_ASSERT(!mVRChild); +} + +base::ProcessId VRProcessParent::OtherPid() { return mVRChild->OtherPid(); } + +bool VRProcessParent::IsConnected() const { return !!mVRChild; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRProcessParent.h b/gfx/vr/ipc/VRProcessParent.h new file mode 100644 index 0000000000..75c65e594e --- /dev/null +++ b/gfx/vr/ipc/VRProcessParent.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PROCESS_PARENT_H +#define GFX_VR_PROCESS_PARENT_H + +#include "mozilla/UniquePtr.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/TaskFactory.h" + +namespace mozilla { +namespace ipc { +class SharedPreferenceSerializer; +} +namespace gfx { + +class VRChild; + +class VRProcessParent final : public mozilla::ipc::GeckoChildProcessHost { + public: + class Listener { + public: + virtual void OnProcessLaunchComplete(VRProcessParent* aParent) {} + + // Follow GPU and RDD process manager, adding this to avoid + // unexpectedly shutdown or had its connection severed. + // This is not called if an error occurs after calling Shutdown(). + virtual void OnProcessUnexpectedShutdown(VRProcessParent* aParent) {} + }; + + explicit VRProcessParent(Listener* aListener); + + // Launch the subprocess asynchronously. On failure, false is returned. + // Otherwise, true is returned, and the OnProcessLaunchComplete listener + // callback will be invoked either when a connection has been established, or + // if a connection could not be established due to an asynchronous error. + bool Launch(); + // If the process is being launched, block until it has launched and + // connected. If a launch task is pending, it will fire immediately. + // + // Returns true if the process is successfully connected; false otherwise. + bool WaitForLaunch(); + void Shutdown(); + void DestroyProcess(); + bool CanShutdown() override { return true; } + + void OnChannelError() override; + void OnChannelConnected(base::ProcessId peer_pid) override; + void OnChannelConnectedTask(); + void OnChannelErrorTask(); + void OnChannelClosed(); + bool IsConnected() const; + + base::ProcessId OtherPid(); + VRChild* GetActor() const { return mVRChild.get(); } + // Return a unique id for this process, guaranteed not to be shared with any + // past or future instance of VRProcessParent. + uint64_t GetProcessToken() const; + + private: + ~VRProcessParent(); + + DISALLOW_COPY_AND_ASSIGN(VRProcessParent); + + bool InitAfterConnect(bool aSucceeded); + void KillHard(const char* aReason); + + UniquePtr<VRChild> mVRChild; + mozilla::ipc::TaskFactory<VRProcessParent> mTaskFactory; + nsCOMPtr<nsIThread> mLaunchThread; + Listener* mListener; + + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase; + bool mChannelClosed; + bool mShutdownRequested; + UniquePtr<mozilla::ipc::SharedPreferenceSerializer> mPrefSerializer; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // ifndef GFX_VR_PROCESS_PARENT_H |