From 5f5804e41bfa1da607f50943d09cb7b88cc52e8f Mon Sep 17 00:00:00 2001 From: thomasmo Date: Wed, 3 Jul 2019 15:50:28 +0000 Subject: [PATCH] Bug 1562777 - Refactor ShMem code from VR classes into separate class r=kip This change brings the related ShMem code from VRManager and VRService into a new class, VRShMem. This is to support future work where this ShMem will be used for other efforts. Having this code in the same class will enable it to be more easily shared in these efforts. Until the new class replaces the code in VRManager and VRService, it can be exercised and validated with two instances of vrtesthost, with the -testmgr and -testsvc parameters. Differential Revision: https://phabricator.services.mozilla.com/D36649 --HG-- extra : moz-landing-system : lando --- gfx/vr/VRShMem.cpp | 552 ++++++++++++++++++++++++++ gfx/vr/VRShMem.h | 66 +++ gfx/vr/external_api/moz_external_vr.h | 3 + gfx/vr/gfxVRMutex.h | 2 + gfx/vr/moz.build | 1 + gfx/vr/vrhost/moz.build | 15 + gfx/vr/vrhost/testhost/testhost.cpp | 12 +- gfx/vr/vrhost/vrhost.cpp | 130 +++++- gfx/vr/vrhost/vrhost.def | 2 + 9 files changed, 780 insertions(+), 3 deletions(-) create mode 100644 gfx/vr/VRShMem.cpp create mode 100644 gfx/vr/VRShMem.h diff --git a/gfx/vr/VRShMem.cpp b/gfx/vr/VRShMem.cpp new file mode 100644 index 000000000000..075bcd52cced --- /dev/null +++ b/gfx/vr/VRShMem.cpp @@ -0,0 +1,552 @@ +/* -*- 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 "VRShMem.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsString.h" +#endif + +#include "gfxVRMutex.h" + +#if defined(XP_MACOSX) +# include +# include /* For mode constants */ +# include /* For O_* constants */ +#elif defined(MOZ_WIDGET_ANDROID) +# include "GeckoVRManager.h" +#endif + +#if !defined(XP_WIN) +# include // for ::sleep +#endif + +using namespace mozilla::gfx; + +// TODO: we might need to use different names for the mutexes +// and mapped files if we have both release and nightlies +// running at the same time? Or...what if we have multiple +// release builds running on same machine? (Bug 1563232) +#ifdef XP_WIN +static const char* kShmemName = "moz.gecko.vr_ext.0.0.1"; +#elif defined(XP_MACOSX) +static const char* kShmemName = "/moz.gecko.vr_ext.0.0.1"; +#endif // XP_WIN + +#if !defined(MOZ_WIDGET_ANDROID) +namespace { +void YieldThread() { +# if defined(XP_WIN) + ::Sleep(0); +# else + ::sleep(0); +# endif +} +} // anonymous namespace +#endif // !defined(MOZ_WIDGET_ANDROID) + +VRShMem::VRShMem(volatile VRExternalShmem* aShmem, bool aSameProcess, + bool aIsParentProcess) + : mExternalShmem(aShmem), + mSameProcess(aSameProcess) +#if defined(XP_WIN) + , + mIsParentProcess(aIsParentProcess) +#endif +#if defined(XP_MACOSX) + , + mShmemFD(0) +#elif defined(XP_WIN) + , + mShmemFile(nullptr), + mMutex(nullptr) +#endif +{ + // To confirm: + // - aShmem is null for VRManager, or for service in multi-proc + // - aShmem is !null for VRService in-proc + // make this into an assert + if (!(aShmem == nullptr || aSameProcess)) { + //::DebugBreak(); + } + + // copied from VRService Ctor + // When we have the VR process, we map the memory + // of mAPIShmem from GPU process and pass it to the CTOR. + // If we don't have the VR process, we will instantiate + // mAPIShmem in VRService. +} + +// Callers/Processes to CreateShMem should followup with CloseShMem +// [copied from VRManager::OpenShmem] +void VRShMem::CreateShMem() { + if (mExternalShmem) { + return; + } +#if defined(XP_WIN) + if (mMutex == nullptr) { + mMutex = CreateMutex(nullptr, // default security descriptor + false, // mutex not owned + TEXT("mozilla::vr::ShmemMutex")); // object name + if (mMutex == nullptr) { +# ifdef MOZILLA_INTERNAL_API + nsAutoCString msg; + msg.AppendPrintf("VRManager CreateMutex error \"%lu\".", GetLastError()); + NS_WARNING(msg.get()); +# endif + MOZ_ASSERT(false); + return; + } + // At xpcshell extension tests, it creates multiple VRManager + // instances in plug-contrainer.exe. It causes GetLastError() return + // `ERROR_ALREADY_EXISTS`. However, even though `ERROR_ALREADY_EXISTS`, it + // still returns the same mutex handle. + // + // https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-createmutexa + MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS); + } +#endif // XP_WIN +#if !defined(MOZ_WIDGET_ANDROID) + // The VR Service accesses all hardware from a separate process + // and replaces the other VRManager when enabled. + // If the VR process is not enabled, create an in-process VRService. + if (mSameProcess) { + // If the VR process is disabled, attempt to create a + // VR service within the current process + mExternalShmem = new VRExternalShmem(); +# ifdef MOZILLA_INTERNAL_API + // TODO: Create external variant to Clear (Bug 1563233) + // VRExternalShmem is asserted to be POD + mExternalShmem->Clear(); +# endif + // TODO: move this call to the caller (Bug 1563233) + // mServiceHost->CreateService(mExternalShmem); + return; + } +#endif + +#if defined(XP_MACOSX) + if (mShmemFD == 0) { + mShmemFD = + shm_open(kShmemName, O_RDWR, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH); + } + if (mShmemFD <= 0) { + mShmemFD = 0; + return; + } + + struct stat sb; + fstat(mShmemFD, &sb); + off_t length = sb.st_size; + if (length < (off_t)sizeof(VRExternalShmem)) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } + + mExternalShmem = (VRExternalShmem*)mmap(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED, mShmemFD, 0); + if (mExternalShmem == MAP_FAILED) { + // TODO - Implement logging (Bug 1558912) + mExternalShmem = NULL; + CloseShMem(); + return; + } + +#elif defined(XP_WIN) + if (mShmemFile == nullptr) { + mShmemFile = + CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, + sizeof(VRExternalShmem), kShmemName); + MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS); + MOZ_ASSERT(mShmemFile); + if (mShmemFile == nullptr) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } + } + LARGE_INTEGER length; + length.QuadPart = sizeof(VRExternalShmem); + mExternalShmem = (VRExternalShmem*)MapViewOfFile( + mShmemFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, 0, length.QuadPart); + + if (mExternalShmem == nullptr) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } +#elif defined(MOZ_WIDGET_ANDROID) && defined(MOZILLA_INTERNAL_API) + mExternalShmem = + (VRExternalShmem*)mozilla::GeckoVRManager::GetExternalContext(); + if (!mExternalShmem) { + return; + } + int32_t version = -1; + int32_t size = 0; + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + version = mExternalShmem->version; + size = mExternalShmem->size; + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } else { + return; + } + if (version != kVRExternalVersion) { + mExternalShmem = nullptr; + return; + } + if (size != sizeof(VRExternalShmem)) { + mExternalShmem = nullptr; + return; + } +#endif +} + +// The cleanup corresponding to CreateShMem +// [copied from VRManager::CloseShmem, dtor] +void VRShMem::CloseShMem() { +#if !defined(MOZ_WIDGET_ANDROID) + if (mSameProcess) { + if (mExternalShmem) { + delete mExternalShmem; + mExternalShmem = nullptr; + } + return; + } +#endif +#if defined(XP_MACOSX) + if (mExternalShmem) { + munmap((void*)mExternalShmem, sizeof(VRExternalShmem)); + mExternalShmem = NULL; + } + if (mShmemFD) { + close(mShmemFD); + mShmemFD = 0; + } +#elif defined(XP_WIN) + if (mExternalShmem) { + UnmapViewOfFile((void*)mExternalShmem); + mExternalShmem = nullptr; + } + if (mShmemFile) { + CloseHandle(mShmemFile); + mShmemFile = nullptr; + } +#elif defined(MOZ_WIDGET_ANDROID) + mExternalShmem = NULL; +#endif +} + +// Called to use an existing shmem instance created by another process +// Callers to JoinShMem should call LeaveShMem for cleanup +// [copied from VRService::InitShmem, VRService::Start] +bool VRShMem::JoinShMem() { + // was if (!mVRProcessEnabled) { + if (mSameProcess) { + return true; + } + +#if defined(XP_WIN) + const char* kShmemName = "moz.gecko.vr_ext.0.0.1"; + base::ProcessHandle targetHandle = 0; + + // Opening a file-mapping object by name + targetHandle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + kShmemName); // name of mapping object + + MOZ_ASSERT(GetLastError() == 0); + + LARGE_INTEGER length; + length.QuadPart = sizeof(VRExternalShmem); + mExternalShmem = (VRExternalShmem*)MapViewOfFile( + reinterpret_cast( + targetHandle), // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, 0, length.QuadPart); + MOZ_ASSERT(GetLastError() == 0); + // TODO - Implement logging (Bug 1558912) + mShmemFile = targetHandle; + if (!mExternalShmem) { + MOZ_ASSERT(mExternalShmem); + return false; + } +#else + // TODO: Implement shmem for other platforms. + // + // TODO: ** Does this mean that ShMem only works in Windows for now? If so, + // let's delete the code from other platforms (Bug 1563234) +#endif + +#if defined(XP_WIN) + // Adding `!XRE_IsParentProcess()` to avoid Win 7 32-bit WebVR tests + // to OpenMutex when there is no GPU process to create + // VRSystemManagerExternal and its mutex. + // was if (!mMutex && !XRE_IsParentProcess()) { + if (!mMutex && !mIsParentProcess) { + mMutex = OpenMutex(MUTEX_ALL_ACCESS, // request full access + false, // handle not inheritable + TEXT("mozilla::vr::ShmemMutex")); // object name + + if (mMutex == nullptr) { +# ifdef MOZILLA_INTERNAL_API + nsAutoCString msg; + msg.AppendPrintf("VRService OpenMutex error \"%lu\".", GetLastError()); + NS_WARNING(msg.get()); +# endif + MOZ_ASSERT(false); + } + MOZ_ASSERT(GetLastError() == 0); + } +#endif + + return true; +} + +// The cleanup corresponding to JoinShMem +// [copied from VRService::Stop] +void VRShMem::LeaveShMem() { +#if defined(XP_WIN) + if (mShmemFile) { + ::CloseHandle(mShmemFile); + mShmemFile = nullptr; + } +#endif + + // was if (mVRProcessEnabled && mAPIShmem) { + if (mExternalShmem != nullptr && !mSameProcess) { +#if defined(XP_WIN) + UnmapViewOfFile((void*)mExternalShmem); +#endif + mExternalShmem = nullptr; + } +#if defined(XP_WIN) + if (mMutex) { + CloseHandle(mMutex); + mMutex = nullptr; + } +#endif +} + +// [copied from from VRManager::PushState] +void VRShMem::PushBrowserState(VRBrowserState& aBrowserState, + bool aNotifyCond) { + if (!mExternalShmem) { + return; + } +#if defined(MOZ_WIDGET_ANDROID) + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) == + 0) { + memcpy((void*)&(mExternalShmem->geckoState), (void*)&aBrowserState, + sizeof(VRBrowserState)); + if (aNotifyCond) { + pthread_cond_signal((pthread_cond_t*)&(mExternalShmem->geckoCond)); + } + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)); + } +#else + bool status = true; +# if defined(XP_WIN) + WaitForMutex lock(mMutex); + status = lock.GetStatus(); +# endif // defined(XP_WIN) + if (status) { + mExternalShmem->geckoGenerationA++; + memcpy((void*)&(mExternalShmem->geckoState), (void*)&aBrowserState, + sizeof(VRBrowserState)); + mExternalShmem->geckoGenerationB++; + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +// [copied from VRService::PullState] +void VRShMem::PullBrowserState(mozilla::gfx::VRBrowserState& aState) { + if (!mExternalShmem) { + return; + } + // Copying the browser state from the shmem is non-blocking + // on x86/x64 architectures. Arm requires a mutex that is + // locked for the duration of the memcpy to and from shmem on + // both sides. + // On x86/x64 It is fallable -- If a dirty copy is detected by + // a mismatch of geckoGenerationA and geckoGenerationB, + // the copy is discarded and will not replace the last known + // browser state. + +#if defined(MOZ_WIDGET_ANDROID) + // TODO: This code is out-of-date and fails to compile, as + // VRService isn't compiled for Android (Bug 1563234) + /* + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) == + 0) { + memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState)); + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)); + } + */ +#else + bool status = true; +# if defined(XP_WIN) + // was if (!XRE_IsParentProcess()) { + if (!mIsParentProcess) { + // TODO: Is this scoped lock okay? Seems like it should allow some + // race condition (Bug 1563234) + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + } +# endif // defined(XP_WIN) + if (status) { + VRExternalShmem tmp; + if (mExternalShmem->geckoGenerationA != mBrowserGeneration) { + // TODO - (void *) cast removes volatile semantics. + // The memcpy is not likely to be optimized out, but is theoretically + // possible. Suggest refactoring to either explicitly enforce memory + // order or to use locks. + memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem)); + if (tmp.geckoGenerationA == tmp.geckoGenerationB && + tmp.geckoGenerationA != 0) { + memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState)); + mBrowserGeneration = tmp.geckoGenerationA; + } + } + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +// [copied from VRService::PushState] +void VRShMem::PushSystemState(const mozilla::gfx::VRSystemState& aState) { + if (!mExternalShmem) { + return; + } + // Copying the VR service state to the shmem is atomic, infallable, + // and non-blocking on x86/x64 architectures. Arm requires a mutex + // that is locked for the duration of the memcpy to and from shmem on + // both sides. + +#if defined(MOZ_WIDGET_ANDROID) + // TODO: This code is out-of-date and fails to compile, as + // VRService isn't compiled for Android (Bug 1563234) + /* + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + // We are casting away the volatile keyword, which is not accepted by + // memcpy. It is possible (although very unlikely) that the compiler + // may optimize out the memcpy here as memcpy isn't explicitly safe for + // volatile memory in the C++ standard. + memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState)); + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } + */ +#else + bool lockState = true; +# if defined(XP_WIN) + // was if (!XRE_IsParentProcess()) { + if (!mIsParentProcess) { + WaitForMutex lock(mMutex); + lockState = lock.GetStatus(); + } +# endif // defined(XP_WIN) + if (lockState) { + mExternalShmem->generationA++; + memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState)); + mExternalShmem->generationB++; + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +// [copied from void VRManager::PullState] +#if defined(MOZ_WIDGET_ANDROID) +void VRShMem::PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function& aWaitCondition /* = nullptr */) { + if (!mExternalShmem) { + return; + } + bool done = false; + while (!done) { + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + while (true) { + memcpy(&aDisplayState, (void*)&(mExternalShmem->state.displayState), + sizeof(VRDisplayState)); + memcpy(&aSensorState, (void*)&(mExternalShmem->state.sensorState), + sizeof(VRHMDSensorState)); + memcpy(aControllerState, + (void*)&(mExternalShmem->state.controllerState), + sizeof(VRControllerState) * kVRControllerMaxCount); + aEnumerationCompleted = mExternalShmem->state.enumerationCompleted; + if (!aWaitCondition || aWaitCondition()) { + done = true; + break; + } + // Block current thead using the condition variable until data + // changes + pthread_cond_wait((pthread_cond_t*)&mExternalShmem->systemCond, + (pthread_mutex_t*)&mExternalShmem->systemMutex); + } // while (true) + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } else if (!aWaitCondition) { + // pthread_mutex_lock failed and we are not waiting for a condition to + // exit from PullState call. + return; + } + } // while (!done) { +} +#else +void VRShMem::PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function& aWaitCondition /* = nullptr */) { + MOZ_ASSERT(mExternalShmem); + if (!mExternalShmem) { + return; + } + while (true) { + { // Scope for WaitForMutex +# if defined(XP_WIN) + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { +# endif // defined(XP_WIN) + VRExternalShmem tmp; + memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem)); + bool isCleanCopy = + tmp.generationA == tmp.generationB && tmp.generationA != 0; + if (isCleanCopy) { + memcpy(&aDisplayState, &tmp.state.displayState, + sizeof(VRDisplayState)); + memcpy(&aSensorState, &tmp.state.sensorState, + sizeof(VRHMDSensorState)); + memcpy(aControllerState, + (void*)&(mExternalShmem->state.controllerState), + sizeof(VRControllerState) * kVRControllerMaxCount); + aEnumerationCompleted = mExternalShmem->state.enumerationCompleted; + // Check for wait condition + if (!aWaitCondition || aWaitCondition()) { + return; + } + } // if (isCleanCopy) + // Yield the thread while polling + YieldThread(); +# if defined(XP_WIN) + } else if (!aWaitCondition) { + // WaitForMutex failed and we are not waiting for a condition to + // exit from PullState call. + return; + } +# endif // defined(XP_WIN) + } // End: Scope for WaitForMutex + // Yield the thread while polling + YieldThread(); + } // while (!true) +} +#endif // defined(MOZ_WIDGET_ANDROID) diff --git a/gfx/vr/VRShMem.h b/gfx/vr/VRShMem.h new file mode 100644 index 000000000000..7a3c3d2fb3d1 --- /dev/null +++ b/gfx/vr/VRShMem.h @@ -0,0 +1,66 @@ +/* -*- 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_VRSHMEM_H +#define GFX_VR_VRSHMEM_H + +// This file declares VRShMem, which is used for cross-platform, cross-process +// communication between VR components. +// A shared memory handle is opened for the struct of type VRExternalShmem, and +// different processes write or read members of it to share data. Note that no +// process should be reading or writing to the same members, except for the +// versioning members used for synchronization. + +#include "moz_external_vr.h" +#include "base/process.h" // for base::ProcessHandle +#include + +namespace mozilla { +namespace gfx { +class VRShMem final { + public: + VRShMem(volatile VRExternalShmem* aShmem, bool aSameProcess, + bool aIsParentProcess); + ~VRShMem() = default; + + void CreateShMem(); + void CloseShMem(); + + bool JoinShMem(); + void LeaveShMem(); + + void PushBrowserState(VRBrowserState& aBrowserState, bool aNotifyCond); + void PullBrowserState(mozilla::gfx::VRBrowserState& aState); + + void PushSystemState(const mozilla::gfx::VRSystemState& aState); + void PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function& aWaitCondition = nullptr); + + private: + volatile VRExternalShmem* mExternalShmem = nullptr; + bool mSameProcess = false; +#if defined(XP_WIN) + bool mIsParentProcess = false; +#endif + +#if defined(XP_MACOSX) + int mShmemFD; +#elif defined(XP_WIN) + base::ProcessHandle mShmemFile; + base::ProcessHandle mMutex; +#endif + +#if !defined(MOZ_WIDGET_ANDROID) + int64_t mBrowserGeneration = 0; +#endif +}; +} // namespace gfx +} // namespace mozilla + +#endif \ No newline at end of file diff --git a/gfx/vr/external_api/moz_external_vr.h b/gfx/vr/external_api/moz_external_vr.h index 06aca374ad0c..2b39273a57eb 100644 --- a/gfx/vr/external_api/moz_external_vr.h +++ b/gfx/vr/external_api/moz_external_vr.h @@ -28,6 +28,9 @@ # include #endif // defined(__ANDROID__) +#include +#include + namespace mozilla { #ifdef MOZILLA_INTERNAL_API namespace dom { diff --git a/gfx/vr/gfxVRMutex.h b/gfx/vr/gfxVRMutex.h index b126d9303a72..a54b8dc8b0d8 100644 --- a/gfx/vr/gfxVRMutex.h +++ b/gfx/vr/gfxVRMutex.h @@ -39,10 +39,12 @@ class WaitForMutex { ~WaitForMutex() { if (mHandle && !ReleaseMutex(mHandle)) { +# ifdef MOZILLA_INTERNAL_API nsAutoCString msg; msg.AppendPrintf("WaitForMutex %d ReleaseMutex error \"%lu\".", mHandle, GetLastError()); NS_WARNING(msg.get()); +# endif MOZ_ASSERT(false, "Failed to release mutex."); } } diff --git a/gfx/vr/moz.build b/gfx/vr/moz.build index 9c5bfd576448..cbb1203935d5 100644 --- a/gfx/vr/moz.build +++ b/gfx/vr/moz.build @@ -53,6 +53,7 @@ UNIFIED_SOURCES += [ SOURCES += [ 'VRManager.cpp', 'VRPuppetCommandBuffer.cpp', + 'VRShMem.cpp' ] if CONFIG['OS_TARGET'] == 'Android': diff --git a/gfx/vr/vrhost/moz.build b/gfx/vr/vrhost/moz.build index f9fc455e7c65..e024355bb824 100644 --- a/gfx/vr/vrhost/moz.build +++ b/gfx/vr/vrhost/moz.build @@ -5,9 +5,17 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. SOURCES += [ + '/gfx/vr/VRShMem.cpp', 'vrhost.cpp' ] +LOCAL_INCLUDES += [ + '/gfx/vr', + '/gfx/vr/external_api', + '/gfx/vr/service', + '/ipc/chromium/src', +] + EXPORTS.vrhost = [ 'vrhostex.h' ] @@ -18,5 +26,12 @@ DIRS += [ 'testhost' ] +# this is Windows-only for now +DEFINES['XP_WIN'] = True +# fixes "lld-link: error: undefined symbol: __imp_moz_xmalloc" +DEFINES['MOZ_NO_MOZALLOC'] = True +# fixes "STL code can only be used with infallible ::operator new()" +DisableStlWrapping() + # Use SharedLibrary to generate the dll SharedLibrary('vrhost') \ No newline at end of file diff --git a/gfx/vr/vrhost/testhost/testhost.cpp b/gfx/vr/vrhost/testhost/testhost.cpp index dc44f03eb5f6..42219526784d 100644 --- a/gfx/vr/vrhost/testhost/testhost.cpp +++ b/gfx/vr/vrhost/testhost/testhost.cpp @@ -7,7 +7,7 @@ #include #include "vrhost/vrhostex.h" -int main() { +int main(int argc, char* argv[], char* envp[]) { HINSTANCE hVR = ::LoadLibrary("vrhost.dll"); if (hVR != nullptr) { PFN_SAMPLE lpfnSample = (PFN_SAMPLE)GetProcAddress(hVR, "SampleExport"); @@ -15,6 +15,16 @@ int main() { lpfnSample(); } + if (strcmp(argv[1], "-testsvc") == 0) { + PFN_SAMPLE lpfnService = + (PFN_SAMPLE)GetProcAddress(hVR, "TestTheService"); + lpfnService(); + } else if (strcmp(argv[1], "-testmgr") == 0) { + PFN_SAMPLE lpfnManager = + (PFN_SAMPLE)GetProcAddress(hVR, "TestTheManager"); + lpfnManager(); + } + ::FreeLibrary(hVR); hVR = nullptr; } diff --git a/gfx/vr/vrhost/vrhost.cpp b/gfx/vr/vrhost/vrhost.cpp index dcfade37a4ee..217a67820d34 100644 --- a/gfx/vr/vrhost/vrhost.cpp +++ b/gfx/vr/vrhost/vrhost.cpp @@ -8,6 +8,132 @@ // Definition of functions that are exported from this dll #include "vrhostex.h" -#include +#include "VRShMem.h" -void SampleExport() { printf("vrhost.cpp hello world"); } \ No newline at end of file +#include +#include "windows.h" + +static const char s_pszSharedEvent[] = "vrhost_test_event_signal"; +static const DWORD s_dwWFSO_WAIT = 20000; + +void SampleExport() { printf("vrhost.cpp hello world"); } + +// For testing ShMem as Manager and Service: +// The two processes should output the steps, synchronously, to validate +// 2-way communication via VRShMem as follows +// 01 mgr: create mgr +// 02 mgr: wait for signal +// 03 svc: create svc +// 04 svc: send signal +// 05 svc: wait for signal +// 06 mgr: push browser +// 07 mgr: send signal +// 08 mgr: wait for signal +// 09 svc: pull browser +// 10 svc: verify data +// 11 svc: push system +// 12 svc: send signal +// 13 mgr: pull system +// 14 mgr: verify data +// 15 return +// These tests can be run with two instances of vrtesthost.exe, one first +// running with -testmgr and the second running with -testsvc. +// TODO: Bug 1563235 - Convert vrtesthost.exe tests into unit tests + +// For testing VRShMem as the Manager (i.e., the one who creates the +// shmem). The sequence of how it tests with the service is listed above. +void TestTheManager() { + HANDLE hEvent = ::CreateEventA(nullptr, // lpEventAttributes + FALSE, // bManualReset + FALSE, // bInitialState + s_pszSharedEvent // lpName + ); + + printf("\n01 mgr: create mgr\n"); + mozilla::gfx::VRShMem shmem(nullptr, false, false); + shmem.CreateShMem(); + + printf("02 mgr: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + // Set some state to verify on the other side + mozilla::gfx::VRBrowserState browserState = {0}; + browserState.presentationActive = true; + browserState.layerState[0].type = + mozilla::gfx::VRLayerType::LayerType_2D_Content; + browserState.hapticState[0].controllerIndex = 987; + + printf("06 mgr: push browser\n"); + shmem.PushBrowserState(browserState, true); + + printf("07 mgr: send signal\n"); + ::SetEvent(hEvent); + + printf("08 mgr: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + printf("13 mgr: pull system\n"); + mozilla::gfx::VRSystemState state; + shmem.PullSystemState(state.displayState, state.sensorState, + state.controllerState, state.enumerationCompleted, + nullptr); + + printf( + "14 mgr: verify data\n" + "\tstate.enumerationCompleted = %d\n" + "\tstate.displayState.displayName = \"%s\"\n" + "\tstate.controllerState[1].hand = %hhu\n" + "\tstate.sensorState.inputFrameID = %llu\n", + state.enumerationCompleted, state.displayState.displayName, + state.controllerState[1].hand, state.sensorState.inputFrameID); + + shmem.CloseShMem(); +} + +// For testing VRShMem as the Service (i.e., the one who consumes the +// shmem). The sequence of how it tests with the service is listed above. +void TestTheService() { + // Handle created by BeTheManager above. + HANDLE hEvent = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess + FALSE, // bInheritHandle + s_pszSharedEvent // lpName + ); + + printf("\n03 svc: create svc\n"); + mozilla::gfx::VRShMem shmem(nullptr, false, false); + shmem.JoinShMem(); + + printf("04 svc: send signal\n"); + ::SetEvent(hEvent); + + printf("05 svc: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + printf("09 svc: pull browser\n"); + mozilla::gfx::VRBrowserState state; + shmem.PullBrowserState(state); + + printf( + "10 svc: verify data\n" + "\tstate.presentationActive = %d\n" + "\tstate.layerState[0].type = %hu\n" + "\tstate.hapticState[0].controllerIndex = %d\n", + state.presentationActive, state.layerState[0].type, + state.hapticState[0].controllerIndex); + + // Set some state to verify on the other side + mozilla::gfx::VRSystemState systemState; + systemState.enumerationCompleted = true; + strncpy(systemState.displayState.displayName, "test from vrservice shmem", + mozilla::gfx::kVRDisplayNameMaxLen); + systemState.controllerState[1].hand = mozilla::gfx::ControllerHand::Left; + systemState.sensorState.inputFrameID = 1234567; + + printf("11 svc: push system\n"); + shmem.PushSystemState(systemState); + + printf("12 svc: send signal\n"); + ::SetEvent(hEvent); + + shmem.LeaveShMem(); +} \ No newline at end of file diff --git a/gfx/vr/vrhost/vrhost.def b/gfx/vr/vrhost/vrhost.def index 55aaf2203e27..d22273c24614 100644 --- a/gfx/vr/vrhost/vrhost.def +++ b/gfx/vr/vrhost/vrhost.def @@ -5,3 +5,5 @@ LIBRARY vrhost.dll EXPORTS SampleExport PRIVATE + TestTheManager PRIVATE + TestTheService PRIVATE