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
));
417 WaitForMutex
lock(mMutex
);
418 status
= lock
.GetStatus();
419 # endif // defined(XP_WIN)
422 mExternalShmem
->geckoGenerationA
= mExternalShmem
->geckoGenerationA
+ 1;
423 memcpy((void*)&(mExternalShmem
->geckoState
), (void*)&aBrowserState
,
424 sizeof(VRBrowserState
));
425 mExternalShmem
->geckoGenerationB
= mExternalShmem
->geckoGenerationB
+ 1;
427 #endif // defined(MOZ_WIDGET_ANDROID)
430 void VRShMem::PullBrowserState(mozilla::gfx::VRBrowserState
& aState
) {
431 if (!mExternalShmem
) {
434 // Copying the browser state from the shmem is non-blocking
435 // on x86/x64 architectures. Arm requires a mutex that is
436 // locked for the duration of the memcpy to and from shmem on
438 // On x86/x64 It is fallable -- If a dirty copy is detected by
439 // a mismatch of geckoGenerationA and geckoGenerationB,
440 // the copy is discarded and will not replace the last known
443 #if defined(MOZ_WIDGET_ANDROID)
444 // TODO: This code is out-of-date and fails to compile, as
445 // VRService isn't compiled for Android (Bug 1563234)
447 if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) ==
449 memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState));
450 pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex));
453 MOZ_ASSERT(false, "PullBrowserState not implemented");
457 if (mRequiresMutex
) {
458 // TODO: Is this scoped lock okay? Seems like it should allow some
459 // race condition (Bug 1563234)
460 WaitForMutex
lock(mMutex
);
461 status
= lock
.GetStatus();
463 # endif // defined(XP_WIN)
466 if (mExternalShmem
->geckoGenerationA
!= mBrowserGeneration
) {
467 // TODO - (void *) cast removes volatile semantics.
468 // The memcpy is not likely to be optimized out, but is theoretically
469 // possible. Suggest refactoring to either explicitly enforce memory
470 // order or to use locks.
471 memcpy(&tmp
, (void*)mExternalShmem
, sizeof(VRExternalShmem
));
472 if (tmp
.geckoGenerationA
== tmp
.geckoGenerationB
&&
473 tmp
.geckoGenerationA
!= 0) {
474 memcpy(&aState
, &tmp
.geckoState
, sizeof(VRBrowserState
));
475 mBrowserGeneration
= tmp
.geckoGenerationA
;
479 #endif // defined(MOZ_WIDGET_ANDROID)
482 void VRShMem::PushSystemState(const mozilla::gfx::VRSystemState
& aState
) {
483 if (!mExternalShmem
) {
486 // Copying the VR service state to the shmem is atomic, infallable,
487 // and non-blocking on x86/x64 architectures. Arm requires a mutex
488 // that is locked for the duration of the memcpy to and from shmem on
491 #if defined(MOZ_WIDGET_ANDROID)
492 // TODO: This code is out-of-date and fails to compile, as
493 // VRService isn't compiled for Android (Bug 1563234)
494 MOZ_ASSERT(false, "JoinShMem not implemented");
496 if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
498 // We are casting away the volatile keyword, which is not accepted by
499 // memcpy. It is possible (although very unlikely) that the compiler
500 // may optimize out the memcpy here as memcpy isn't explicitly safe for
501 // volatile memory in the C++ standard.
502 memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState));
503 pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
507 bool lockState
= true;
510 if (mRequiresMutex
) {
511 // TODO: Is this scoped lock okay? Seems like it should allow some
512 // race condition (Bug 1563234)
513 WaitForMutex
lock(mMutex
);
514 lockState
= lock
.GetStatus();
516 # endif // defined(XP_WIN)
519 mExternalShmem
->generationA
= mExternalShmem
->generationA
+ 1;
520 memcpy((void*)&mExternalShmem
->state
, &aState
, sizeof(VRSystemState
));
521 mExternalShmem
->generationB
= mExternalShmem
->generationB
+ 1;
523 #endif // defined(MOZ_WIDGET_ANDROID)
526 #if defined(MOZ_WIDGET_ANDROID)
527 void VRShMem::PullSystemState(
528 VRDisplayState
& aDisplayState
, VRHMDSensorState
& aSensorState
,
529 std::array
<VRControllerState
, kVRControllerMaxCount
>* const
531 bool& aEnumerationCompleted
,
532 const std::function
<bool()>& aWaitCondition
/* = nullptr */) {
533 if (!mExternalShmem
) {
538 if (pthread_mutex_lock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
)) ==
541 memcpy(&aDisplayState
, (void*)&(mExternalShmem
->state
.displayState
),
542 sizeof(VRDisplayState
));
543 memcpy(&aSensorState
, (void*)&(mExternalShmem
->state
.sensorState
),
544 sizeof(VRHMDSensorState
));
545 memcpy(aControllerState
->data(),
546 (void*)&(mExternalShmem
->state
.controllerState
),
547 sizeof(aControllerState
->at(0)) * aControllerState
->size());
548 aEnumerationCompleted
= mExternalShmem
->state
.enumerationCompleted
;
549 if (!aWaitCondition
|| aWaitCondition()) {
553 // Block current thead using the condition variable until data
555 pthread_cond_wait((pthread_cond_t
*)&mExternalShmem
->systemCond
,
556 (pthread_mutex_t
*)&mExternalShmem
->systemMutex
);
558 pthread_mutex_unlock((pthread_mutex_t
*)&(mExternalShmem
->systemMutex
));
559 } else if (!aWaitCondition
) {
560 // pthread_mutex_lock failed and we are not waiting for a condition to
561 // exit from PullState call.
567 void VRShMem::PullSystemState(
568 VRDisplayState
& aDisplayState
, VRHMDSensorState
& aSensorState
,
569 std::array
<VRControllerState
, kVRControllerMaxCount
>* const
571 bool& aEnumerationCompleted
,
572 const std::function
<bool()>& aWaitCondition
/* = nullptr */) {
573 MOZ_ASSERT(mExternalShmem
);
574 if (!mExternalShmem
) {
578 { // Scope for WaitForMutex
581 WaitForMutex
lock(mMutex
);
582 status
= lock
.GetStatus();
584 # endif // defined(XP_WIN)
586 memcpy(&tmp
, (void*)mExternalShmem
, sizeof(VRExternalShmem
));
588 tmp
.generationA
== tmp
.generationB
&& tmp
.generationA
!= 0;
590 memcpy(&aDisplayState
, &tmp
.state
.displayState
,
591 sizeof(VRDisplayState
));
592 memcpy(&aSensorState
, &tmp
.state
.sensorState
,
593 sizeof(VRHMDSensorState
));
594 *aControllerState
= tmp
.state
.controllerState
;
595 aEnumerationCompleted
= tmp
.state
.enumerationCompleted
;
596 // Check for wait condition
597 if (!aWaitCondition
|| aWaitCondition()) {
600 } else if (!aWaitCondition
) {
601 // We did not get a clean copy, and we are not waiting for a condition
602 // to exit from PullState call.
605 // Yield the thread while polling
608 } else if (!aWaitCondition
) {
609 // WaitForMutex failed and we are not waiting for a condition to
610 // exit from PullState call.
613 # endif // defined(XP_WIN)
614 } // End: Scope for WaitForMutex
615 // Yield the thread while polling
619 #endif // defined(MOZ_WIDGET_ANDROID)
621 void VRShMem::PushWindowState(VRWindowState
& aState
) {
623 if (!mExternalShmem
) {
628 WaitForMutex
lock(mMutex
);
629 status
= lock
.GetStatus();
631 memcpy((void*)&(mExternalShmem
->windowState
), (void*)&aState
,
632 sizeof(VRWindowState
));
634 #endif // defined(XP_WIN)
637 void VRShMem::PullWindowState(VRWindowState
& aState
) {
639 if (!mExternalShmem
) {
644 WaitForMutex
lock(mMutex
);
645 status
= lock
.GetStatus();
647 memcpy((void*)&aState
, (void*)&(mExternalShmem
->windowState
),
648 sizeof(VRWindowState
));
650 #endif // defined(XP_WIN)
653 void VRShMem::PushTelemetryState(VRTelemetryState
& aState
) {
655 if (!mExternalShmem
) {
660 WaitForMutex
lock(mMutex
);
661 status
= lock
.GetStatus();
663 memcpy((void*)&(mExternalShmem
->telemetryState
), (void*)&aState
,
664 sizeof(VRTelemetryState
));
666 #endif // defined(XP_WIN)
668 void VRShMem::PullTelemetryState(VRTelemetryState
& aState
) {
670 if (!mExternalShmem
) {
675 WaitForMutex
lock(mMutex
);
676 status
= lock
.GetStatus();
678 memcpy((void*)&aState
, (void*)&(mExternalShmem
->telemetryState
),
679 sizeof(VRTelemetryState
));
681 #endif // defined(XP_WIN)
684 void VRShMem::SendEvent(uint64_t aWindowID
,
685 mozilla::gfx::VRFxEventType aEventType
,
686 mozilla::gfx::VRFxEventState aEventState
) {
687 MOZ_ASSERT(!HasExternalShmem());
689 mozilla::gfx::VRWindowState windowState
= {0};
690 PullWindowState(windowState
);
691 windowState
.windowID
= aWindowID
;
692 windowState
.eventType
= aEventType
;
693 windowState
.eventState
= aEventState
;
694 PushWindowState(windowState
);
698 // Notify the waiting host process that the data is now available
699 HANDLE hSignal
= ::OpenEventA(EVENT_ALL_ACCESS
, // dwDesiredAccess
700 FALSE
, // bInheritHandle
701 windowState
.signalName
// lpName
704 ::CloseHandle(hSignal
);
705 #endif // defined(XP_WIN)
709 void VRShMem::SendIMEState(uint64_t aWindowID
,
710 mozilla::gfx::VRFxEventState aEventState
) {
711 SendEvent(aWindowID
, mozilla::gfx::VRFxEventType::IME
, aEventState
);
714 void VRShMem::SendFullscreenState(uint64_t aWindowID
, bool aFullscreen
) {
715 SendEvent(aWindowID
, mozilla::gfx::VRFxEventType::FULLSCREEN
,
716 aFullscreen
? mozilla::gfx::VRFxEventState::FULLSCREEN_ENTER
717 : mozilla::gfx::VRFxEventState::FULLSCREEN_EXIT
);
720 // Note: this should be called from the VRShMem instance that created
721 // the external shmem rather than joined it.
722 void VRShMem::SendShutdowmState(uint64_t aWindowID
) {
723 MOZ_ASSERT(HasExternalShmem());
725 mozilla::gfx::VRWindowState windowState
= {0};
726 PullWindowState(windowState
);
727 windowState
.windowID
= aWindowID
;
728 windowState
.eventType
= mozilla::gfx::VRFxEventType::SHUTDOWN
;
729 PushWindowState(windowState
);
732 // Notify the waiting host process that the data is now available
733 HANDLE hSignal
= ::OpenEventA(EVENT_ALL_ACCESS
, // dwDesiredAccess
734 FALSE
, // bInheritHandle
735 windowState
.signalName
// lpName
738 ::CloseHandle(hSignal
);
739 #endif // defined(XP_WIN)