1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #ifdef MOZILLA_INTERNAL_API
10 # include "nsString.h"
11 # include "nsXULAppAPI.h"
14 #include "gfxVRMutex.h"
16 #if defined(XP_MACOSX)
17 # include <sys/mman.h>
18 # include <sys/stat.h> /* For mode constants */
19 # include <fcntl.h> /* For O_* constants */
20 #elif defined(MOZ_WIDGET_ANDROID)
21 # include "GeckoVRManager.h"
25 # include <unistd.h> // for ::sleep
28 using namespace mozilla::gfx
;
31 static const char* kShmemName
= "moz.gecko.vr_ext." SHMEM_VERSION
;
32 static LPCTSTR kMutexName
= TEXT("mozilla::vr::ShmemMutex" SHMEM_VERSION
);
33 #elif defined(XP_MACOSX)
34 static const char* kShmemName
= "/moz.gecko.vr_ext." SHMEM_VERSION
;
37 #if !defined(MOZ_WIDGET_ANDROID)
46 } // anonymous namespace
47 #endif // !defined(MOZ_WIDGET_ANDROID)
49 VRShMem::VRShMem(volatile VRExternalShmem
* aShmem
, bool aRequiresMutex
)
50 : mExternalShmem(aShmem
),
51 mIsSharedExternalShmem(aShmem
!= nullptr)
54 mRequiresMutex(aRequiresMutex
)
56 #if defined(XP_MACOSX)
65 // Regarding input parameters,
66 // - aShmem is null for VRManager or for VRService in multi-proc
67 // - aShmem is !null for VRService in-proc (i.e., no VR proc)
70 // Note: This function should only be called for in-proc scenarios, where the
71 // shared memory is only shared within the same proc (rather than across
72 // processes). Also, this local heap memory's lifetime is tied to the class.
73 // Callers to this must ensure that its reference doesn't outlive the owning
75 volatile VRExternalShmem
* VRShMem::GetExternalShmem() const {
76 #if defined(XP_MACOSX)
77 MOZ_ASSERT(mShmemFD
== 0);
79 MOZ_ASSERT(mShmemFile
== nullptr);
81 return mExternalShmem
;
84 bool VRShMem::IsDisplayStateShutdown() const {
85 // adapted from VRService::Refresh
86 // Does this need the mutex for getting .shutdown?
87 return mExternalShmem
!= nullptr &&
88 mExternalShmem
->state
.displayState
.shutdown
;
91 // This method returns true when there is a Shmem struct allocated and
92 // when there is a shmem handle from the OS. This implies that the struct
93 // is mapped to shared memory rather than being allocated on the heap by
95 bool VRShMem::IsCreatedOnSharedMemory() const {
96 #if defined(XP_MACOSX)
97 return HasExternalShmem() && (mShmemFD
!= 0);
99 return HasExternalShmem() && (mShmemFile
!= nullptr);
101 // VRShMem does not support system shared memory on remaining platformss
106 // CreateShMem allocates system shared memory for mExternalShmem and
107 // synchronization primitives to protect it.
108 // Callers/Processes to CreateShMem should followup with CloseShMem
109 void VRShMem::CreateShMem(bool aCreateOnSharedMemory
) {
110 if (HasExternalShmem()) {
111 MOZ_ASSERT(mIsSharedExternalShmem
&& !IsCreatedOnSharedMemory());
115 if (mMutex
== nullptr) {
116 mMutex
= CreateMutex(nullptr, // default security descriptor
117 false, // mutex not owned
118 kMutexName
); // object name
119 if (mMutex
== nullptr) {
120 # ifdef MOZILLA_INTERNAL_API
122 msg
.AppendPrintf("VRManager CreateMutex error \"%lu\".", GetLastError());
123 NS_WARNING(msg
.get());
128 // At xpcshell extension tests, it creates multiple VRManager
129 // instances in plug-contrainer.exe. It causes GetLastError() return
130 // `ERROR_ALREADY_EXISTS`. However, even though `ERROR_ALREADY_EXISTS`, it
131 // still returns the same mutex handle.
133 // https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-createmutexa
134 MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS
);
137 #if !defined(MOZ_WIDGET_ANDROID)
138 // The VR Service accesses all hardware from a separate process
139 // and replaces the other VRManager when enabled.
140 // If the VR process is not enabled, create an in-process VRService.
141 if (!aCreateOnSharedMemory
) {
142 MOZ_ASSERT(mExternalShmem
== nullptr);
143 // If the VR process is disabled, attempt to create a
144 // VR service within the current process on the heap
145 mExternalShmem
= new VRExternalShmem();
151 MOZ_ASSERT(aCreateOnSharedMemory
);
153 #if defined(XP_MACOSX)
156 shm_open(kShmemName
, O_RDWR
, S_IRUSR
| S_IWUSR
| S_IROTH
| S_IWOTH
);
164 fstat(mShmemFD
, &sb
);
165 off_t length
= sb
.st_size
;
166 if (length
< (off_t
)sizeof(VRExternalShmem
)) {
167 // TODO - Implement logging (Bug 1558912)
172 mExternalShmem
= (VRExternalShmem
*)mmap(NULL
, length
, PROT_READ
| PROT_WRITE
,
173 MAP_SHARED
, mShmemFD
, 0);
174 if (mExternalShmem
== MAP_FAILED
) {
175 // TODO - Implement logging (Bug 1558912)
176 mExternalShmem
= NULL
;
181 #elif defined(XP_WIN)
182 if (mShmemFile
== nullptr) {
184 CreateFileMappingA(INVALID_HANDLE_VALUE
, nullptr, PAGE_READWRITE
, 0,
185 sizeof(VRExternalShmem
), kShmemName
);
186 MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS
);
187 MOZ_ASSERT(mShmemFile
);
188 if (mShmemFile
== nullptr) {
189 // TODO - Implement logging (Bug 1558912)
195 LARGE_INTEGER length
;
196 length
.QuadPart
= sizeof(VRExternalShmem
);
197 mExternalShmem
= (VRExternalShmem
*)MapViewOfFile(
198 mShmemFile
, // handle to map object
199 FILE_MAP_ALL_ACCESS
, // read/write permission
200 0, 0, length
.QuadPart
);
202 if (mExternalShmem
== nullptr) {
203 // TODO - Implement logging (Bug 1558912)
207 #elif defined(MOZ_WIDGET_ANDROID)
209 "CreateShMem should not be called for Android. Use "
210 "CreateShMemForAndroid instead");
214 // This function sets mExternalShmem in the Android/GeckoView
215 // scenarios where the host creates it in-memory and VRShMem
216 // accesses it via GeckVRManager.
217 void VRShMem::CreateShMemForAndroid() {
218 #if defined(MOZ_WIDGET_ANDROID) && defined(MOZILLA_INTERNAL_API)
220 (VRExternalShmem
*)mozilla::GeckoVRManager::GetExternalContext();
221 if (!mExternalShmem
) {
224 mIsSharedExternalShmem
= true;
227 int32_t version
= -1;
229 if (pthread_mutex_lock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
)) ==
231 version
= mExternalShmem
->version
;
232 size
= mExternalShmem
->size
;
233 pthread_mutex_unlock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
));
237 if (version
!= kVRExternalVersion
) {
238 mExternalShmem
= nullptr;
241 if (size
!= sizeof(VRExternalShmem
)) {
242 mExternalShmem
= nullptr;
248 void VRShMem::ClearShMem() {
249 if (mExternalShmem
!= nullptr) {
250 #ifdef MOZILLA_INTERNAL_API
251 // VRExternalShmem is asserted to be POD
252 mExternalShmem
->Clear();
254 memset((void*)mExternalShmem
, 0, sizeof(VRExternalShmem
));
259 // The cleanup corresponding to CreateShMem
260 void VRShMem::CloseShMem() {
261 #if !defined(MOZ_WIDGET_ANDROID)
262 if (!IsCreatedOnSharedMemory()) {
263 MOZ_ASSERT(!mIsSharedExternalShmem
);
264 if (mExternalShmem
) {
265 delete mExternalShmem
;
266 mExternalShmem
= nullptr;
271 #if defined(XP_MACOSX)
272 if (mExternalShmem
) {
273 munmap((void*)mExternalShmem
, sizeof(VRExternalShmem
));
274 mExternalShmem
= NULL
;
280 #elif defined(XP_WIN)
281 if (mExternalShmem
) {
282 UnmapViewOfFile((void*)mExternalShmem
);
283 mExternalShmem
= nullptr;
286 CloseHandle(mShmemFile
);
287 mShmemFile
= nullptr;
289 #elif defined(MOZ_WIDGET_ANDROID)
290 mExternalShmem
= NULL
;
295 MOZ_ASSERT(mRequiresMutex
);
302 // Called to use an existing shmem instance created by another process
303 // Callers to JoinShMem should call LeaveShMem for cleanup
304 bool VRShMem::JoinShMem() {
306 if (!mMutex
&& mRequiresMutex
) {
307 // Check that there are no errors before making system calls
308 MOZ_ASSERT(GetLastError() == 0);
310 mMutex
= OpenMutex(MUTEX_ALL_ACCESS
, // request full access
311 false, // handle not inheritable
312 kMutexName
); // object name
314 if (mMutex
== nullptr) {
315 # ifdef MOZILLA_INTERNAL_API
317 msg
.AppendPrintf("VRService OpenMutex error \"%lu\".", GetLastError());
318 NS_WARNING(msg
.get());
322 MOZ_ASSERT(GetLastError() == 0);
326 if (HasExternalShmem()) {
327 // An ExternalShmem is already set. No need to override and rejoin
332 // Opening a file-mapping object by name
333 base::ProcessHandle targetHandle
=
334 OpenFileMappingA(FILE_MAP_ALL_ACCESS
, // read/write access
335 FALSE
, // do not inherit the name
336 kShmemName
); // name of mapping object
338 MOZ_ASSERT(GetLastError() == 0);
340 LARGE_INTEGER length
;
341 length
.QuadPart
= sizeof(VRExternalShmem
);
342 mExternalShmem
= (VRExternalShmem
*)MapViewOfFile(
343 reinterpret_cast<base::ProcessHandle
>(
344 targetHandle
), // handle to map object
345 FILE_MAP_ALL_ACCESS
, // read/write permission
346 0, 0, length
.QuadPart
);
347 MOZ_ASSERT(GetLastError() == 0);
349 // TODO - Implement logging (Bug 1558912)
350 mShmemFile
= targetHandle
;
351 if (!mExternalShmem
) {
352 MOZ_ASSERT(mExternalShmem
);
356 // TODO: Implement shmem for other platforms.
358 // TODO: ** Does this mean that ShMem only works in Windows for now? If so,
359 // let's delete the code from other platforms (Bug 1563234)
360 MOZ_ASSERT(false, "JoinShMem not implemented");
365 // The cleanup corresponding to JoinShMem
366 void VRShMem::LeaveShMem() {
368 // Check that there are no errors before making system calls
369 MOZ_ASSERT(GetLastError() == 0);
372 ::CloseHandle(mShmemFile
);
373 mShmemFile
= nullptr;
377 if (mExternalShmem
!= nullptr) {
379 if (IsCreatedOnSharedMemory()) {
380 UnmapViewOfFile((void*)mExternalShmem
);
381 MOZ_ASSERT(GetLastError() == 0);
383 // Otherwise, if not created on shared memory, simply null the shared
384 // reference to the heap object. The call to CloseShMem will appropriately
385 // free the allocation.
387 mExternalShmem
= nullptr;
391 MOZ_ASSERT(mRequiresMutex
);
398 void VRShMem::PushBrowserState(VRBrowserState
& aBrowserState
,
400 if (!mExternalShmem
) {
403 #if defined(MOZ_WIDGET_ANDROID)
404 if (pthread_mutex_lock((pthread_mutex_t
*)&(mExternalShmem
->geckoMutex
)) ==
406 memcpy((void*)&(mExternalShmem
->geckoState
), (void*)&aBrowserState
,
407 sizeof(VRBrowserState
));
409 pthread_cond_signal((pthread_cond_t
*)&(mExternalShmem
->geckoCond
));
411 pthread_mutex_unlock((pthread_mutex_t
*)&(mExternalShmem
->geckoMutex
));
416 WaitForMutex
lock(mMutex
);
417 status
= lock
.GetStatus();
418 # endif // defined(XP_WIN)
420 mExternalShmem
->geckoGenerationA
++;
421 memcpy((void*)&(mExternalShmem
->geckoState
), (void*)&aBrowserState
,
422 sizeof(VRBrowserState
));
423 mExternalShmem
->geckoGenerationB
++;
425 #endif // defined(MOZ_WIDGET_ANDROID)
428 void VRShMem::PullBrowserState(mozilla::gfx::VRBrowserState
& aState
) {
429 if (!mExternalShmem
) {
432 // Copying the browser state from the shmem is non-blocking
433 // on x86/x64 architectures. Arm requires a mutex that is
434 // locked for the duration of the memcpy to and from shmem on
436 // On x86/x64 It is fallable -- If a dirty copy is detected by
437 // a mismatch of geckoGenerationA and geckoGenerationB,
438 // the copy is discarded and will not replace the last known
441 #if defined(MOZ_WIDGET_ANDROID)
442 // TODO: This code is out-of-date and fails to compile, as
443 // VRService isn't compiled for Android (Bug 1563234)
445 if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) ==
447 memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState));
448 pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex));
451 MOZ_ASSERT(false, "PullBrowserState not implemented");
455 if (mRequiresMutex
) {
456 // TODO: Is this scoped lock okay? Seems like it should allow some
457 // race condition (Bug 1563234)
458 WaitForMutex
lock(mMutex
);
459 status
= lock
.GetStatus();
461 # endif // defined(XP_WIN)
464 if (mExternalShmem
->geckoGenerationA
!= mBrowserGeneration
) {
465 // TODO - (void *) cast removes volatile semantics.
466 // The memcpy is not likely to be optimized out, but is theoretically
467 // possible. Suggest refactoring to either explicitly enforce memory
468 // order or to use locks.
469 memcpy(&tmp
, (void*)mExternalShmem
, sizeof(VRExternalShmem
));
470 if (tmp
.geckoGenerationA
== tmp
.geckoGenerationB
&&
471 tmp
.geckoGenerationA
!= 0) {
472 memcpy(&aState
, &tmp
.geckoState
, sizeof(VRBrowserState
));
473 mBrowserGeneration
= tmp
.geckoGenerationA
;
477 #endif // defined(MOZ_WIDGET_ANDROID)
480 void VRShMem::PushSystemState(const mozilla::gfx::VRSystemState
& aState
) {
481 if (!mExternalShmem
) {
484 // Copying the VR service state to the shmem is atomic, infallable,
485 // and non-blocking on x86/x64 architectures. Arm requires a mutex
486 // that is locked for the duration of the memcpy to and from shmem on
489 #if defined(MOZ_WIDGET_ANDROID)
490 // TODO: This code is out-of-date and fails to compile, as
491 // VRService isn't compiled for Android (Bug 1563234)
492 MOZ_ASSERT(false, "JoinShMem not implemented");
494 if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
496 // We are casting away the volatile keyword, which is not accepted by
497 // memcpy. It is possible (although very unlikely) that the compiler
498 // may optimize out the memcpy here as memcpy isn't explicitly safe for
499 // volatile memory in the C++ standard.
500 memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState));
501 pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
505 bool lockState
= true;
507 if (mRequiresMutex
) {
508 // TODO: Is this scoped lock okay? Seems like it should allow some
509 // race condition (Bug 1563234)
510 WaitForMutex
lock(mMutex
);
511 lockState
= lock
.GetStatus();
513 # endif // defined(XP_WIN)
515 mExternalShmem
->generationA
++;
516 memcpy((void*)&mExternalShmem
->state
, &aState
, sizeof(VRSystemState
));
517 mExternalShmem
->generationB
++;
519 #endif // defined(MOZ_WIDGET_ANDROID)
522 #if defined(MOZ_WIDGET_ANDROID)
523 void VRShMem::PullSystemState(
524 VRDisplayState
& aDisplayState
, VRHMDSensorState
& aSensorState
,
525 VRControllerState (&aControllerState
)[kVRControllerMaxCount
],
526 bool& aEnumerationCompleted
,
527 const std::function
<bool()>& aWaitCondition
/* = nullptr */) {
528 if (!mExternalShmem
) {
533 if (pthread_mutex_lock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
)) ==
536 memcpy(&aDisplayState
, (void*)&(mExternalShmem
->state
.displayState
),
537 sizeof(VRDisplayState
));
538 memcpy(&aSensorState
, (void*)&(mExternalShmem
->state
.sensorState
),
539 sizeof(VRHMDSensorState
));
540 memcpy(aControllerState
,
541 (void*)&(mExternalShmem
->state
.controllerState
),
542 sizeof(VRControllerState
) * kVRControllerMaxCount
);
543 aEnumerationCompleted
= mExternalShmem
->state
.enumerationCompleted
;
544 if (!aWaitCondition
|| aWaitCondition()) {
548 // Block current thead using the condition variable until data
550 pthread_cond_wait((pthread_cond_t
*)&mExternalShmem
->systemCond
,
551 (pthread_mutex_t
*)&mExternalShmem
->systemMutex
);
553 pthread_mutex_unlock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
));
554 } else if (!aWaitCondition
) {
555 // pthread_mutex_lock failed and we are not waiting for a condition to
556 // exit from PullState call.
562 void VRShMem::PullSystemState(
563 VRDisplayState
& aDisplayState
, VRHMDSensorState
& aSensorState
,
564 VRControllerState (&aControllerState
)[kVRControllerMaxCount
],
565 bool& aEnumerationCompleted
,
566 const std::function
<bool()>& aWaitCondition
/* = nullptr */) {
567 MOZ_ASSERT(mExternalShmem
);
568 if (!mExternalShmem
) {
572 { // Scope for WaitForMutex
575 WaitForMutex
lock(mMutex
);
576 status
= lock
.GetStatus();
578 # endif // defined(XP_WIN)
580 memcpy(&tmp
, (void*)mExternalShmem
, sizeof(VRExternalShmem
));
582 tmp
.generationA
== tmp
.generationB
&& tmp
.generationA
!= 0;
584 memcpy(&aDisplayState
, &tmp
.state
.displayState
,
585 sizeof(VRDisplayState
));
586 memcpy(&aSensorState
, &tmp
.state
.sensorState
,
587 sizeof(VRHMDSensorState
));
588 memcpy(aControllerState
,
589 (void*)&(mExternalShmem
->state
.controllerState
),
590 sizeof(VRControllerState
) * kVRControllerMaxCount
);
591 aEnumerationCompleted
= mExternalShmem
->state
.enumerationCompleted
;
592 // Check for wait condition
593 if (!aWaitCondition
|| aWaitCondition()) {
596 } else if (!aWaitCondition
) {
597 // We did not get a clean copy, and we are not waiting for a condition
598 // to exit from PullState call.
601 // Yield the thread while polling
604 } else if (!aWaitCondition
) {
605 // WaitForMutex failed and we are not waiting for a condition to
606 // exit from PullState call.
609 # endif // defined(XP_WIN)
610 } // End: Scope for WaitForMutex
611 // Yield the thread while polling
615 #endif // defined(MOZ_WIDGET_ANDROID)
617 void VRShMem::PushWindowState(VRWindowState
& aState
) {
619 if (!mExternalShmem
) {
624 WaitForMutex
lock(mMutex
);
625 status
= lock
.GetStatus();
627 memcpy((void*)&(mExternalShmem
->windowState
), (void*)&aState
,
628 sizeof(VRWindowState
));
630 #endif // defined(XP_WIN)
633 void VRShMem::PullWindowState(VRWindowState
& aState
) {
635 if (!mExternalShmem
) {
640 WaitForMutex
lock(mMutex
);
641 status
= lock
.GetStatus();
643 memcpy((void*)&aState
, (void*)&(mExternalShmem
->windowState
),
644 sizeof(VRWindowState
));
646 #endif // defined(XP_WIN)
649 void VRShMem::PushTelemetryState(VRTelemetryState
& aState
) {
651 if (!mExternalShmem
) {
656 WaitForMutex
lock(mMutex
);
657 status
= lock
.GetStatus();
659 memcpy((void*)&(mExternalShmem
->telemetryState
), (void*)&aState
,
660 sizeof(VRTelemetryState
));
662 #endif // defined(XP_WIN)
664 void VRShMem::PullTelemetryState(VRTelemetryState
& aState
) {
666 if (!mExternalShmem
) {
671 WaitForMutex
lock(mMutex
);
672 status
= lock
.GetStatus();
674 memcpy((void*)&aState
, (void*)&(mExternalShmem
->telemetryState
),
675 sizeof(VRTelemetryState
));
677 #endif // defined(XP_WIN)
680 void VRShMem::SendEvent(uint64_t aWindowID
,
681 mozilla::gfx::VRFxEventType aEventType
,
682 mozilla::gfx::VRFxEventState aEventState
) {
683 MOZ_ASSERT(!HasExternalShmem());
685 mozilla::gfx::VRWindowState windowState
= {0};
686 PullWindowState(windowState
);
687 windowState
.windowID
= aWindowID
;
688 windowState
.eventType
= aEventType
;
689 windowState
.eventState
= aEventState
;
690 PushWindowState(windowState
);
694 // Notify the waiting host process that the data is now available
695 HANDLE hSignal
= ::OpenEventA(EVENT_ALL_ACCESS
, // dwDesiredAccess
696 FALSE
, // bInheritHandle
697 windowState
.signalName
// lpName
700 ::CloseHandle(hSignal
);
701 #endif // defined(XP_WIN)
705 void VRShMem::SendIMEState(uint64_t aWindowID
,
706 mozilla::gfx::VRFxEventState aEventState
) {
707 SendEvent(aWindowID
, mozilla::gfx::VRFxEventType::IME
, aEventState
);
710 void VRShMem::SendFullscreenState(uint64_t aWindowID
, bool aFullscreen
) {
711 SendEvent(aWindowID
, mozilla::gfx::VRFxEventType::FULLSCREEN
,
712 aFullscreen
? mozilla::gfx::VRFxEventState::FULLSCREEN_ENTER
713 : mozilla::gfx::VRFxEventState::FULLSCREEN_EXIT
);
716 // Note: this should be called from the VRShMem instance that created
717 // the external shmem rather than joined it.
718 void VRShMem::SendShutdowmState(uint64_t aWindowID
) {
719 MOZ_ASSERT(HasExternalShmem());
721 mozilla::gfx::VRWindowState windowState
= {0};
722 PullWindowState(windowState
);
723 windowState
.windowID
= aWindowID
;
724 windowState
.eventType
= mozilla::gfx::VRFxEventType::SHUTDOWN
;
725 PushWindowState(windowState
);
728 // Notify the waiting host process that the data is now available
729 HANDLE hSignal
= ::OpenEventA(EVENT_ALL_ACCESS
, // dwDesiredAccess
730 FALSE
, // bInheritHandle
731 windowState
.signalName
// lpName
734 ::CloseHandle(hSignal
);
735 #endif // defined(XP_WIN)