Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / gfx / vr / service / OpenVRSession.cpp
blobbfb486799db342222bdaa09dce7da5f8cd4317d4
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/. */
7 #include <fstream>
8 #include "mozilla/JSONStringWriteFuncs.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "nsIThread.h"
11 #include "nsString.h"
13 #include "OpenVRSession.h"
14 #include "mozilla/StaticPrefs_dom.h"
16 #if defined(XP_WIN)
17 # include <d3d11.h>
18 # include "mozilla/gfx/DeviceManagerDx.h"
19 #elif defined(XP_MACOSX)
20 # include "mozilla/gfx/MacIOSurface.h"
21 #endif
23 #if !defined(XP_WIN)
24 # include <sys/stat.h> // for umask()
25 #endif
27 #include "mozilla/dom/GamepadEventTypes.h"
28 #include "mozilla/dom/GamepadBinding.h"
29 #include "binding/OpenVRCosmosBinding.h"
30 #include "binding/OpenVRKnucklesBinding.h"
31 #include "binding/OpenVRViveBinding.h"
32 #include "OpenVRCosmosMapper.h"
33 #include "OpenVRDefaultMapper.h"
34 #include "OpenVRKnucklesMapper.h"
35 #include "OpenVRViveMapper.h"
36 #if defined(XP_WIN) // Windows Mixed Reality is only available in Windows.
37 # include "OpenVRWMRMapper.h"
38 # include "binding/OpenVRWMRBinding.h"
39 #endif
41 #include "VRParent.h"
42 #include "VRProcessChild.h"
43 #include "VRThread.h"
45 #if !defined(M_PI)
46 # define M_PI 3.14159265358979323846264338327950288
47 #endif
49 #define BTN_MASK_FROM_ID(_id) ::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
51 // Haptic feedback is updated every 5ms, as this is
52 // the minimum period between new haptic pulse requests.
53 // Effectively, this results in a pulse width modulation
54 // with an interval of 5ms. Through experimentation, the
55 // maximum duty cycle was found to be about 3.9ms
56 const uint32_t kVRHapticUpdateInterval = 5;
58 using namespace mozilla::gfx;
60 namespace mozilla::gfx {
62 namespace {
64 class ControllerManifestFile {
65 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ControllerManifestFile)
67 public:
68 static already_AddRefed<ControllerManifestFile> CreateManifest() {
69 RefPtr<ControllerManifestFile> manifest = new ControllerManifestFile();
70 return manifest.forget();
73 bool IsExisting() {
74 if (mFileName.IsEmpty() ||
75 !std::ifstream(mFileName.BeginReading()).good()) {
76 return false;
78 return true;
81 void SetFileName(const char* aName) { mFileName = aName; }
83 const char* GetFileName() const { return mFileName.BeginReading(); }
85 private:
86 ControllerManifestFile() = default;
88 ~ControllerManifestFile() {
89 if (!mFileName.IsEmpty() && remove(mFileName.BeginReading()) != 0) {
90 MOZ_ASSERT(false, "Delete controller manifest file failed.");
92 mFileName = "";
95 nsCString mFileName;
98 // We wanna keep these temporary files existing
99 // until Firefox is closed instead of following OpenVRSession's lifetime.
100 StaticRefPtr<ControllerManifestFile> sCosmosBindingFile;
101 StaticRefPtr<ControllerManifestFile> sKnucklesBindingFile;
102 StaticRefPtr<ControllerManifestFile> sViveBindingFile;
103 #if defined(XP_WIN)
104 StaticRefPtr<ControllerManifestFile> sWMRBindingFile;
105 #endif
106 StaticRefPtr<ControllerManifestFile> sControllerActionFile;
108 dom::GamepadHand GetControllerHandFromControllerRole(OpenVRHand aRole) {
109 dom::GamepadHand hand;
110 switch (aRole) {
111 case OpenVRHand::None:
112 hand = dom::GamepadHand::_empty;
113 break;
114 case OpenVRHand::Left:
115 hand = dom::GamepadHand::Left;
116 break;
117 case OpenVRHand::Right:
118 hand = dom::GamepadHand::Right;
119 break;
120 default:
121 hand = dom::GamepadHand::_empty;
122 MOZ_ASSERT(false);
123 break;
126 return hand;
129 bool FileIsExisting(const nsCString& aPath) {
130 if (aPath.IsEmpty() || !std::ifstream(aPath.BeginReading()).good()) {
131 return false;
133 return true;
136 }; // anonymous namespace
138 #if defined(XP_WIN)
139 bool GenerateTempFileName(nsCString& aPath) {
140 TCHAR tempPathBuffer[MAX_PATH];
141 TCHAR tempFileName[MAX_PATH];
143 // Gets the temp path env string (no guarantee it's a valid path).
144 DWORD dwRetVal = GetTempPath(MAX_PATH, tempPathBuffer);
145 if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
146 NS_WARNING("OpenVR - Creating temp path failed.");
147 return false;
150 // Generates a temporary file name.
151 UINT uRetVal = GetTempFileName(tempPathBuffer, // directory for tmp files
152 TEXT("mozvr"), // temp file name prefix
153 0, // create unique name
154 tempFileName); // buffer for name
155 if (uRetVal == 0) {
156 NS_WARNING("OpenVR - Creating temp file failed.");
157 return false;
160 aPath.Assign(NS_ConvertUTF16toUTF8(tempFileName));
161 return true;
163 #else
164 bool GenerateTempFileName(nsCString& aPath) {
165 const char tmp[] = "/tmp/mozvrXXXXXX";
166 char fileName[PATH_MAX];
168 strcpy(fileName, tmp);
169 const mode_t prevMask = umask(S_IXUSR | S_IRWXO | S_IRWXG);
170 const int fd = mkstemp(fileName);
171 umask(prevMask);
172 if (fd == -1) {
173 NS_WARNING(nsPrintfCString("OpenVR - Creating temp file failed: %s",
174 strerror(errno))
175 .get());
176 return false;
178 close(fd);
180 aPath.Assign(fileName);
181 return true;
183 #endif // defined(XP_WIN)
185 OpenVRSession::OpenVRSession()
186 : mVRSystem(nullptr),
187 mVRChaperone(nullptr),
188 mVRCompositor(nullptr),
189 mHapticPulseRemaining{},
190 mHapticPulseIntensity{},
191 mIsWindowsMR(false),
192 mControllerHapticStateMutex(
193 "OpenVRSession::mControllerHapticStateMutex") {
194 std::fill_n(mControllerDeviceIndex, kVRControllerMaxCount, OpenVRHand::None);
197 OpenVRSession::~OpenVRSession() {
198 mActionsetFirefox = ::vr::k_ulInvalidActionSetHandle;
199 Shutdown();
202 bool OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState,
203 bool aDetectRuntimesOnly) {
204 if (StaticPrefs::dom_vr_puppet_enabled()) {
205 // Ensure that tests using the VR Puppet do not find real hardware
206 return false;
208 if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_openvr_enabled()) {
209 return false;
211 if (mVRSystem != nullptr) {
212 // Already initialized
213 return true;
215 if (!::vr::VR_IsRuntimeInstalled()) {
216 return false;
218 if (aDetectRuntimesOnly) {
219 aSystemState.displayState.capabilityFlags |=
220 VRDisplayCapabilityFlags::Cap_ImmersiveVR;
221 return false;
223 if (!::vr::VR_IsHmdPresent()) {
224 return false;
227 ::vr::HmdError err;
229 ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
230 if (err) {
231 return false;
234 mVRSystem = (::vr::IVRSystem*)::vr::VR_GetGenericInterface(
235 ::vr::IVRSystem_Version, &err);
236 if (err || !mVRSystem) {
237 Shutdown();
238 return false;
240 mVRChaperone = (::vr::IVRChaperone*)::vr::VR_GetGenericInterface(
241 ::vr::IVRChaperone_Version, &err);
242 if (err || !mVRChaperone) {
243 Shutdown();
244 return false;
246 mVRCompositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(
247 ::vr::IVRCompositor_Version, &err);
248 if (err || !mVRCompositor) {
249 Shutdown();
250 return false;
253 #if defined(XP_WIN)
254 if (!CreateD3DObjects()) {
255 Shutdown();
256 return false;
259 #endif
261 // Configure coordinate system
262 mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated);
264 if (!InitState(aSystemState)) {
265 Shutdown();
266 return false;
268 if (!SetupContollerActions()) {
269 return false;
272 // Succeeded
273 return true;
276 // "actions": [] Action paths must take the form: "/actions/<action
277 // set>/in|out/<action>"
278 #define CreateControllerAction(hand, name, type) \
279 ControllerAction("/actions/firefox/in/" #hand "Hand_" #name, #type)
280 #define CreateControllerOutAction(hand, name, type) \
281 ControllerAction("/actions/firefox/out/" #hand "Hand_" #name, #type)
283 bool OpenVRSession::SetupContollerActions() {
284 if (!vr::VRInput()) {
285 NS_WARNING("OpenVR - vr::VRInput() is null.");
286 return false;
289 // Check if this device binding file has been created.
290 // If it didn't exist yet, create a new temp file.
291 nsCString controllerAction;
292 nsCString viveManifest;
293 nsCString WMRManifest;
294 nsCString knucklesManifest;
295 nsCString cosmosManifest;
297 // Getting / Generating manifest file paths.
298 if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
299 VRParent* vrParent = VRProcessChild::GetVRParent();
300 nsCString output;
302 if (vrParent->GetOpenVRControllerActionPath(&output)) {
303 controllerAction = output;
306 if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::HTCVive,
307 &output)) {
308 viveManifest = output;
310 if (!viveManifest.Length() || !FileIsExisting(viveManifest)) {
311 if (!GenerateTempFileName(viveManifest)) {
312 return false;
314 OpenVRViveBinding viveBinding;
315 std::ofstream viveBindingFile(viveManifest.BeginReading());
316 if (viveBindingFile.is_open()) {
317 viveBindingFile << viveBinding.binding;
318 viveBindingFile.close();
322 #if defined(XP_WIN)
323 if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::MSMR,
324 &output)) {
325 WMRManifest = output;
327 if (!WMRManifest.Length() || !FileIsExisting(WMRManifest)) {
328 if (!GenerateTempFileName(WMRManifest)) {
329 return false;
331 OpenVRWMRBinding WMRBinding;
332 std::ofstream WMRBindingFile(WMRManifest.BeginReading());
333 if (WMRBindingFile.is_open()) {
334 WMRBindingFile << WMRBinding.binding;
335 WMRBindingFile.close();
338 #endif
339 if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::ValveIndex,
340 &output)) {
341 knucklesManifest = output;
343 if (!knucklesManifest.Length() || !FileIsExisting(knucklesManifest)) {
344 if (!GenerateTempFileName(knucklesManifest)) {
345 return false;
347 OpenVRKnucklesBinding knucklesBinding;
348 std::ofstream knucklesBindingFile(knucklesManifest.BeginReading());
349 if (knucklesBindingFile.is_open()) {
350 knucklesBindingFile << knucklesBinding.binding;
351 knucklesBindingFile.close();
354 if (vrParent->GetOpenVRControllerManifestPath(
355 VRControllerType::HTCViveCosmos, &output)) {
356 cosmosManifest = output;
358 if (!cosmosManifest.Length() || !FileIsExisting(cosmosManifest)) {
359 if (!GenerateTempFileName(cosmosManifest)) {
360 return false;
362 OpenVRCosmosBinding cosmosBinding;
363 std::ofstream cosmosBindingFile(cosmosManifest.BeginReading());
364 if (cosmosBindingFile.is_open()) {
365 cosmosBindingFile << cosmosBinding.binding;
366 cosmosBindingFile.close();
369 } else {
370 // Without using VR process
371 if (!sControllerActionFile) {
372 sControllerActionFile = ControllerManifestFile::CreateManifest();
373 NS_DispatchToMainThread(NS_NewRunnableFunction(
374 "ClearOnShutdown ControllerManifestFile",
375 []() { ClearOnShutdown(&sControllerActionFile); }));
377 controllerAction = sControllerActionFile->GetFileName();
379 if (!sViveBindingFile) {
380 sViveBindingFile = ControllerManifestFile::CreateManifest();
381 NS_DispatchToMainThread(
382 NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
383 []() { ClearOnShutdown(&sViveBindingFile); }));
385 if (!sViveBindingFile->IsExisting()) {
386 nsCString viveBindingPath;
387 if (!GenerateTempFileName(viveBindingPath)) {
388 return false;
390 sViveBindingFile->SetFileName(viveBindingPath.BeginReading());
391 OpenVRViveBinding viveBinding;
392 std::ofstream viveBindingFile(sViveBindingFile->GetFileName());
393 if (viveBindingFile.is_open()) {
394 viveBindingFile << viveBinding.binding;
395 viveBindingFile.close();
398 viveManifest = sViveBindingFile->GetFileName();
400 if (!sKnucklesBindingFile) {
401 sKnucklesBindingFile = ControllerManifestFile::CreateManifest();
402 NS_DispatchToMainThread(NS_NewRunnableFunction(
403 "ClearOnShutdown ControllerManifestFile",
404 []() { ClearOnShutdown(&sKnucklesBindingFile); }));
406 if (!sKnucklesBindingFile->IsExisting()) {
407 nsCString knucklesBindingPath;
408 if (!GenerateTempFileName(knucklesBindingPath)) {
409 return false;
411 sKnucklesBindingFile->SetFileName(knucklesBindingPath.BeginReading());
412 OpenVRKnucklesBinding knucklesBinding;
413 std::ofstream knucklesBindingFile(sKnucklesBindingFile->GetFileName());
414 if (knucklesBindingFile.is_open()) {
415 knucklesBindingFile << knucklesBinding.binding;
416 knucklesBindingFile.close();
419 knucklesManifest = sKnucklesBindingFile->GetFileName();
421 if (!sCosmosBindingFile) {
422 sCosmosBindingFile = ControllerManifestFile::CreateManifest();
423 NS_DispatchToMainThread(NS_NewRunnableFunction(
424 "ClearOnShutdown ControllerManifestFile",
425 []() { ClearOnShutdown(&sCosmosBindingFile); }));
427 if (!sCosmosBindingFile->IsExisting()) {
428 nsCString cosmosBindingPath;
429 if (!GenerateTempFileName(cosmosBindingPath)) {
430 return false;
432 sCosmosBindingFile->SetFileName(cosmosBindingPath.BeginReading());
433 OpenVRCosmosBinding cosmosBinding;
434 std::ofstream cosmosBindingFile(sCosmosBindingFile->GetFileName());
435 if (cosmosBindingFile.is_open()) {
436 cosmosBindingFile << cosmosBinding.binding;
437 cosmosBindingFile.close();
440 cosmosManifest = sCosmosBindingFile->GetFileName();
441 #if defined(XP_WIN)
442 if (!sWMRBindingFile) {
443 sWMRBindingFile = ControllerManifestFile::CreateManifest();
444 NS_DispatchToMainThread(
445 NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
446 []() { ClearOnShutdown(&sWMRBindingFile); }));
448 if (!sWMRBindingFile->IsExisting()) {
449 nsCString WMRBindingPath;
450 if (!GenerateTempFileName(WMRBindingPath)) {
451 return false;
453 sWMRBindingFile->SetFileName(WMRBindingPath.BeginReading());
454 OpenVRWMRBinding WMRBinding;
455 std::ofstream WMRBindingFile(sWMRBindingFile->GetFileName());
456 if (WMRBindingFile.is_open()) {
457 WMRBindingFile << WMRBinding.binding;
458 WMRBindingFile.close();
461 WMRManifest = sWMRBindingFile->GetFileName();
462 #endif
464 // End of Getting / Generating manifest file paths.
466 // Setup controller actions.
467 ControllerInfo leftContollerInfo;
468 leftContollerInfo.mActionPose = CreateControllerAction(L, pose, pose);
469 leftContollerInfo.mActionTrackpad_Analog =
470 CreateControllerAction(L, trackpad_analog, vector2);
471 leftContollerInfo.mActionTrackpad_Pressed =
472 CreateControllerAction(L, trackpad_pressed, boolean);
473 leftContollerInfo.mActionTrackpad_Touched =
474 CreateControllerAction(L, trackpad_touched, boolean);
475 leftContollerInfo.mActionTrigger_Value =
476 CreateControllerAction(L, trigger_value, vector1);
477 leftContollerInfo.mActionGrip_Pressed =
478 CreateControllerAction(L, grip_pressed, boolean);
479 leftContollerInfo.mActionGrip_Touched =
480 CreateControllerAction(L, grip_touched, boolean);
481 leftContollerInfo.mActionMenu_Pressed =
482 CreateControllerAction(L, menu_pressed, boolean);
483 leftContollerInfo.mActionMenu_Touched =
484 CreateControllerAction(L, menu_touched, boolean);
485 leftContollerInfo.mActionSystem_Pressed =
486 CreateControllerAction(L, system_pressed, boolean);
487 leftContollerInfo.mActionSystem_Touched =
488 CreateControllerAction(L, system_touched, boolean);
489 leftContollerInfo.mActionA_Pressed =
490 CreateControllerAction(L, A_pressed, boolean);
491 leftContollerInfo.mActionA_Touched =
492 CreateControllerAction(L, A_touched, boolean);
493 leftContollerInfo.mActionB_Pressed =
494 CreateControllerAction(L, B_pressed, boolean);
495 leftContollerInfo.mActionB_Touched =
496 CreateControllerAction(L, B_touched, boolean);
497 leftContollerInfo.mActionThumbstick_Analog =
498 CreateControllerAction(L, thumbstick_analog, vector2);
499 leftContollerInfo.mActionThumbstick_Pressed =
500 CreateControllerAction(L, thumbstick_pressed, boolean);
501 leftContollerInfo.mActionThumbstick_Touched =
502 CreateControllerAction(L, thumbstick_touched, boolean);
503 leftContollerInfo.mActionFingerIndex_Value =
504 CreateControllerAction(L, finger_index_value, vector1);
505 leftContollerInfo.mActionFingerMiddle_Value =
506 CreateControllerAction(L, finger_middle_value, vector1);
507 leftContollerInfo.mActionFingerRing_Value =
508 CreateControllerAction(L, finger_ring_value, vector1);
509 leftContollerInfo.mActionFingerPinky_Value =
510 CreateControllerAction(L, finger_pinky_value, vector1);
511 leftContollerInfo.mActionBumper_Pressed =
512 CreateControllerAction(L, bumper_pressed, boolean);
513 leftContollerInfo.mActionHaptic =
514 CreateControllerOutAction(L, haptic, vibration);
516 ControllerInfo rightContollerInfo;
517 rightContollerInfo.mActionPose = CreateControllerAction(R, pose, pose);
518 rightContollerInfo.mActionTrackpad_Analog =
519 CreateControllerAction(R, trackpad_analog, vector2);
520 rightContollerInfo.mActionTrackpad_Pressed =
521 CreateControllerAction(R, trackpad_pressed, boolean);
522 rightContollerInfo.mActionTrackpad_Touched =
523 CreateControllerAction(R, trackpad_touched, boolean);
524 rightContollerInfo.mActionTrigger_Value =
525 CreateControllerAction(R, trigger_value, vector1);
526 rightContollerInfo.mActionGrip_Pressed =
527 CreateControllerAction(R, grip_pressed, boolean);
528 rightContollerInfo.mActionGrip_Touched =
529 CreateControllerAction(R, grip_touched, boolean);
530 rightContollerInfo.mActionMenu_Pressed =
531 CreateControllerAction(R, menu_pressed, boolean);
532 rightContollerInfo.mActionMenu_Touched =
533 CreateControllerAction(R, menu_touched, boolean);
534 rightContollerInfo.mActionSystem_Pressed =
535 CreateControllerAction(R, system_pressed, boolean);
536 rightContollerInfo.mActionSystem_Touched =
537 CreateControllerAction(R, system_touched, boolean);
538 rightContollerInfo.mActionA_Pressed =
539 CreateControllerAction(R, A_pressed, boolean);
540 rightContollerInfo.mActionA_Touched =
541 CreateControllerAction(R, A_touched, boolean);
542 rightContollerInfo.mActionB_Pressed =
543 CreateControllerAction(R, B_pressed, boolean);
544 rightContollerInfo.mActionB_Touched =
545 CreateControllerAction(R, B_touched, boolean);
546 rightContollerInfo.mActionThumbstick_Analog =
547 CreateControllerAction(R, thumbstick_analog, vector2);
548 rightContollerInfo.mActionThumbstick_Pressed =
549 CreateControllerAction(R, thumbstick_pressed, boolean);
550 rightContollerInfo.mActionThumbstick_Touched =
551 CreateControllerAction(R, thumbstick_touched, boolean);
552 rightContollerInfo.mActionFingerIndex_Value =
553 CreateControllerAction(R, finger_index_value, vector1);
554 rightContollerInfo.mActionFingerMiddle_Value =
555 CreateControllerAction(R, finger_middle_value, vector1);
556 rightContollerInfo.mActionFingerRing_Value =
557 CreateControllerAction(R, finger_ring_value, vector1);
558 rightContollerInfo.mActionFingerPinky_Value =
559 CreateControllerAction(R, finger_pinky_value, vector1);
560 rightContollerInfo.mActionBumper_Pressed =
561 CreateControllerAction(R, bumper_pressed, boolean);
562 rightContollerInfo.mActionHaptic =
563 CreateControllerOutAction(R, haptic, vibration);
565 mControllerHand[OpenVRHand::Left] = leftContollerInfo;
566 mControllerHand[OpenVRHand::Right] = rightContollerInfo;
568 if (!controllerAction.Length() || !FileIsExisting(controllerAction)) {
569 if (!GenerateTempFileName(controllerAction)) {
570 return false;
572 JSONStringWriteFunc<nsCString> actionData;
573 JSONWriter actionWriter(actionData);
574 actionWriter.Start();
576 actionWriter.StringProperty("version",
577 "0.1.0"); // TODO: adding a version check.
578 // "default_bindings": []
579 actionWriter.StartArrayProperty("default_bindings");
581 auto SetupActionWriterByControllerType = [&](const char* aType,
582 const nsCString& aManifest) {
583 actionWriter.StartObjectElement();
584 actionWriter.StringProperty("controller_type", MakeStringSpan(aType));
585 actionWriter.StringProperty("binding_url", aManifest);
586 actionWriter.EndObject();
588 SetupActionWriterByControllerType("vive_controller", viveManifest);
589 SetupActionWriterByControllerType("knuckles", knucklesManifest);
590 SetupActionWriterByControllerType("vive_cosmos_controller", cosmosManifest);
591 #if defined(XP_WIN)
592 SetupActionWriterByControllerType("holographic_controller", WMRManifest);
593 #endif
594 actionWriter.EndArray(); // End "default_bindings": []
596 actionWriter.StartArrayProperty("actions");
598 for (auto& controller : mControllerHand) {
599 auto SetActionsToWriter = [&](const ControllerAction& aAction) {
600 actionWriter.StartObjectElement();
601 actionWriter.StringProperty("name", aAction.name);
602 actionWriter.StringProperty("type", aAction.type);
603 actionWriter.EndObject();
606 SetActionsToWriter(controller.mActionPose);
607 SetActionsToWriter(controller.mActionTrackpad_Analog);
608 SetActionsToWriter(controller.mActionTrackpad_Pressed);
609 SetActionsToWriter(controller.mActionTrackpad_Touched);
610 SetActionsToWriter(controller.mActionTrigger_Value);
611 SetActionsToWriter(controller.mActionGrip_Pressed);
612 SetActionsToWriter(controller.mActionGrip_Touched);
613 SetActionsToWriter(controller.mActionMenu_Pressed);
614 SetActionsToWriter(controller.mActionMenu_Touched);
615 SetActionsToWriter(controller.mActionSystem_Pressed);
616 SetActionsToWriter(controller.mActionSystem_Touched);
617 SetActionsToWriter(controller.mActionA_Pressed);
618 SetActionsToWriter(controller.mActionA_Touched);
619 SetActionsToWriter(controller.mActionB_Pressed);
620 SetActionsToWriter(controller.mActionB_Touched);
621 SetActionsToWriter(controller.mActionThumbstick_Analog);
622 SetActionsToWriter(controller.mActionThumbstick_Pressed);
623 SetActionsToWriter(controller.mActionThumbstick_Touched);
624 SetActionsToWriter(controller.mActionFingerIndex_Value);
625 SetActionsToWriter(controller.mActionFingerMiddle_Value);
626 SetActionsToWriter(controller.mActionFingerRing_Value);
627 SetActionsToWriter(controller.mActionFingerPinky_Value);
628 SetActionsToWriter(controller.mActionBumper_Pressed);
629 SetActionsToWriter(controller.mActionHaptic);
631 actionWriter.EndArray(); // End "actions": []
632 actionWriter.End();
634 std::ofstream actionfile(controllerAction.BeginReading());
635 if (actionfile.is_open()) {
636 actionfile << actionData.StringCRef().get();
637 actionfile.close();
641 vr::EVRInputError err =
642 vr::VRInput()->SetActionManifestPath(controllerAction.BeginReading());
643 if (err != vr::VRInputError_None) {
644 NS_WARNING("OpenVR - SetActionManifestPath failed.");
645 return false;
647 // End of setup controller actions.
649 // Notify the parent process these manifest files are already been recorded.
650 if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
651 NS_DispatchToMainThread(NS_NewRunnableFunction(
652 "SendOpenVRControllerActionPathToParent",
653 [controllerAction, viveManifest, WMRManifest, knucklesManifest,
654 cosmosManifest]() {
655 VRParent* vrParent = VRProcessChild::GetVRParent();
656 Unused << vrParent->SendOpenVRControllerActionPathToParent(
657 controllerAction);
658 Unused << vrParent->SendOpenVRControllerManifestPathToParent(
659 VRControllerType::HTCVive, viveManifest);
660 Unused << vrParent->SendOpenVRControllerManifestPathToParent(
661 VRControllerType::MSMR, WMRManifest);
662 Unused << vrParent->SendOpenVRControllerManifestPathToParent(
663 VRControllerType::ValveIndex, knucklesManifest);
664 Unused << vrParent->SendOpenVRControllerManifestPathToParent(
665 VRControllerType::HTCViveCosmos, cosmosManifest);
666 }));
667 } else {
668 sControllerActionFile->SetFileName(controllerAction.BeginReading());
671 return true;
674 #if defined(XP_WIN)
675 bool OpenVRSession::CreateD3DObjects() {
676 RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
677 if (!device) {
678 return false;
680 if (!CreateD3DContext(device)) {
681 return false;
683 return true;
685 #endif
687 void OpenVRSession::Shutdown() {
688 StopHapticTimer();
689 StopHapticThread();
690 if (mVRSystem || mVRCompositor || mVRChaperone) {
691 ::vr::VR_Shutdown();
692 mVRCompositor = nullptr;
693 mVRChaperone = nullptr;
694 mVRSystem = nullptr;
698 bool OpenVRSession::InitState(VRSystemState& aSystemState) {
699 VRDisplayState& state = aSystemState.displayState;
700 strncpy(state.displayName, "OpenVR HMD", kVRDisplayNameMaxLen);
701 state.eightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ');
702 state.isConnected =
703 mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
704 state.isMounted = false;
705 state.capabilityFlags =
706 (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
707 (int)
708 VRDisplayCapabilityFlags::Cap_Orientation |
709 (int)VRDisplayCapabilityFlags::Cap_Position |
710 (int)VRDisplayCapabilityFlags::Cap_External |
711 (int)VRDisplayCapabilityFlags::Cap_Present |
712 (int)VRDisplayCapabilityFlags::
713 Cap_StageParameters |
714 (int)
715 VRDisplayCapabilityFlags::Cap_ImmersiveVR);
716 state.blendMode = VRDisplayBlendMode::Opaque;
717 state.reportsDroppedFrames = true;
719 ::vr::ETrackedPropertyError err;
720 bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(
721 ::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool,
722 &err);
723 if (err == ::vr::TrackedProp_Success && bHasProximitySensor) {
724 state.capabilityFlags =
725 (VRDisplayCapabilityFlags)((int)state.capabilityFlags |
726 (int)VRDisplayCapabilityFlags::
727 Cap_MountDetection);
730 uint32_t w, h;
731 mVRSystem->GetRecommendedRenderTargetSize(&w, &h);
732 state.eyeResolution.width = w;
733 state.eyeResolution.height = h;
734 state.nativeFramebufferScaleFactor = 1.0f;
736 // default to an identity quaternion
737 aSystemState.sensorState.pose.orientation[3] = 1.0f;
739 UpdateStageParameters(state);
740 UpdateEyeParameters(aSystemState);
742 VRHMDSensorState& sensorState = aSystemState.sensorState;
743 sensorState.flags =
744 (VRDisplayCapabilityFlags)((int)
745 VRDisplayCapabilityFlags::Cap_Orientation |
746 (int)VRDisplayCapabilityFlags::Cap_Position);
747 sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion
749 return true;
752 void OpenVRSession::UpdateStageParameters(VRDisplayState& aState) {
753 float sizeX = 0.0f;
754 float sizeZ = 0.0f;
755 if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) {
756 ::vr::HmdMatrix34_t t =
757 mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
758 aState.stageSize.width = sizeX;
759 aState.stageSize.height = sizeZ;
761 aState.sittingToStandingTransform[0] = t.m[0][0];
762 aState.sittingToStandingTransform[1] = t.m[1][0];
763 aState.sittingToStandingTransform[2] = t.m[2][0];
764 aState.sittingToStandingTransform[3] = 0.0f;
766 aState.sittingToStandingTransform[4] = t.m[0][1];
767 aState.sittingToStandingTransform[5] = t.m[1][1];
768 aState.sittingToStandingTransform[6] = t.m[2][1];
769 aState.sittingToStandingTransform[7] = 0.0f;
771 aState.sittingToStandingTransform[8] = t.m[0][2];
772 aState.sittingToStandingTransform[9] = t.m[1][2];
773 aState.sittingToStandingTransform[10] = t.m[2][2];
774 aState.sittingToStandingTransform[11] = 0.0f;
776 aState.sittingToStandingTransform[12] = t.m[0][3];
777 aState.sittingToStandingTransform[13] = t.m[1][3];
778 aState.sittingToStandingTransform[14] = t.m[2][3];
779 aState.sittingToStandingTransform[15] = 1.0f;
780 } else {
781 // If we fail, fall back to reasonable defaults.
782 // 1m x 1m space, 0.75m high in seated position
783 aState.stageSize.width = 1.0f;
784 aState.stageSize.height = 1.0f;
786 aState.sittingToStandingTransform[0] = 1.0f;
787 aState.sittingToStandingTransform[1] = 0.0f;
788 aState.sittingToStandingTransform[2] = 0.0f;
789 aState.sittingToStandingTransform[3] = 0.0f;
791 aState.sittingToStandingTransform[4] = 0.0f;
792 aState.sittingToStandingTransform[5] = 1.0f;
793 aState.sittingToStandingTransform[6] = 0.0f;
794 aState.sittingToStandingTransform[7] = 0.0f;
796 aState.sittingToStandingTransform[8] = 0.0f;
797 aState.sittingToStandingTransform[9] = 0.0f;
798 aState.sittingToStandingTransform[10] = 1.0f;
799 aState.sittingToStandingTransform[11] = 0.0f;
801 aState.sittingToStandingTransform[12] = 0.0f;
802 aState.sittingToStandingTransform[13] = 0.75f;
803 aState.sittingToStandingTransform[14] = 0.0f;
804 aState.sittingToStandingTransform[15] = 1.0f;
808 void OpenVRSession::UpdateEyeParameters(VRSystemState& aState) {
809 // This must be called every frame in order to
810 // account for continuous adjustments to ipd.
811 gfx::Matrix4x4 headToEyeTransforms[2];
813 for (uint32_t eye = 0; eye < 2; ++eye) {
814 ::vr::HmdMatrix34_t eyeToHead =
815 mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye));
816 aState.displayState.eyeTranslation[eye].x = eyeToHead.m[0][3];
817 aState.displayState.eyeTranslation[eye].y = eyeToHead.m[1][3];
818 aState.displayState.eyeTranslation[eye].z = eyeToHead.m[2][3];
820 float left, right, up, down;
821 mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &left, &right,
822 &up, &down);
823 aState.displayState.eyeFOV[eye].upDegrees = atan(-up) * 180.0 / M_PI;
824 aState.displayState.eyeFOV[eye].rightDegrees = atan(right) * 180.0 / M_PI;
825 aState.displayState.eyeFOV[eye].downDegrees = atan(down) * 180.0 / M_PI;
826 aState.displayState.eyeFOV[eye].leftDegrees = atan(-left) * 180.0 / M_PI;
828 Matrix4x4 pose;
829 // NOTE! eyeToHead.m is a 3x4 matrix, not 4x4. But
830 // because of its arrangement, we can copy the 12 elements in and
831 // then transpose them to the right place.
832 memcpy(&pose._11, &eyeToHead.m, sizeof(eyeToHead.m));
833 pose.Transpose();
834 pose.Invert();
835 headToEyeTransforms[eye] = pose;
837 aState.sensorState.CalcViewMatrices(headToEyeTransforms);
840 void OpenVRSession::UpdateHeadsetPose(VRSystemState& aState) {
841 const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1;
842 ::vr::TrackedDevicePose_t poses[posesSize];
843 // Note: We *must* call WaitGetPoses in order for any rendering to happen at
844 // all.
845 mVRCompositor->WaitGetPoses(poses, posesSize, nullptr, 0);
847 ::vr::Compositor_FrameTiming timing;
848 timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
849 if (mVRCompositor->GetFrameTiming(&timing)) {
850 aState.sensorState.timestamp = timing.m_flSystemTimeInSeconds;
851 } else {
852 // This should not happen, but log it just in case
853 fprintf(stderr, "OpenVR - IVRCompositor::GetFrameTiming failed");
856 if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
857 poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
858 poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult ==
859 ::vr::TrackingResult_Running_OK) {
860 const ::vr::TrackedDevicePose_t& pose =
861 poses[::vr::k_unTrackedDeviceIndex_Hmd];
863 gfx::Matrix4x4 m;
864 // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
865 // because of its arrangement, we can copy the 12 elements in and
866 // then transpose them to the right place. We do this so we can
867 // pull out a Quaternion.
868 memcpy(&m._11, &pose.mDeviceToAbsoluteTracking,
869 sizeof(pose.mDeviceToAbsoluteTracking));
870 m.Transpose();
872 gfx::Quaternion rot;
873 rot.SetFromRotationMatrix(m);
875 aState.sensorState.flags =
876 (VRDisplayCapabilityFlags)((int)aState.sensorState.flags |
877 (int)VRDisplayCapabilityFlags::
878 Cap_Orientation);
879 aState.sensorState.pose.orientation[0] = rot.x;
880 aState.sensorState.pose.orientation[1] = rot.y;
881 aState.sensorState.pose.orientation[2] = rot.z;
882 aState.sensorState.pose.orientation[3] = rot.w;
883 aState.sensorState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0];
884 aState.sensorState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1];
885 aState.sensorState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2];
887 aState.sensorState.flags =
888 (VRDisplayCapabilityFlags)((int)aState.sensorState.flags |
889 (int)VRDisplayCapabilityFlags::Cap_Position);
890 aState.sensorState.pose.position[0] = m._41;
891 aState.sensorState.pose.position[1] = m._42;
892 aState.sensorState.pose.position[2] = m._43;
893 aState.sensorState.pose.linearVelocity[0] = pose.vVelocity.v[0];
894 aState.sensorState.pose.linearVelocity[1] = pose.vVelocity.v[1];
895 aState.sensorState.pose.linearVelocity[2] = pose.vVelocity.v[2];
899 void OpenVRSession::EnumerateControllers(VRSystemState& aState) {
900 MOZ_ASSERT(mVRSystem);
902 MutexAutoLock lock(mControllerHapticStateMutex);
904 bool controllerPresent[kVRControllerMaxCount] = {false};
905 uint32_t stateIndex = 0;
906 mActionsetFirefox = vr::k_ulInvalidActionSetHandle;
907 VRControllerType controllerType = VRControllerType::_empty;
909 if (vr::VRInput()->GetActionSetHandle(
910 "/actions/firefox", &mActionsetFirefox) != vr::VRInputError_None) {
911 return;
914 for (int8_t handIndex = 0; handIndex < OpenVRHand::Total; ++handIndex) {
915 if (handIndex == OpenVRHand::Left) {
916 if (vr::VRInput()->GetInputSourceHandle(
917 "/user/hand/left", &mControllerHand[OpenVRHand::Left].mSource) !=
918 vr::VRInputError_None) {
919 continue;
921 } else if (handIndex == OpenVRHand::Right) {
922 if (vr::VRInput()->GetInputSourceHandle(
923 "/user/hand/right",
924 &mControllerHand[OpenVRHand::Right].mSource) !=
925 vr::VRInputError_None) {
926 continue;
928 } else {
929 MOZ_ASSERT(false, "Unknown OpenVR hand type.");
932 vr::InputOriginInfo_t originInfo;
933 if (vr::VRInput()->GetOriginTrackedDeviceInfo(
934 mControllerHand[handIndex].mSource, &originInfo,
935 sizeof(originInfo)) == vr::VRInputError_None &&
936 originInfo.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid &&
937 mVRSystem->IsTrackedDeviceConnected(originInfo.trackedDeviceIndex)) {
938 const ::vr::ETrackedDeviceClass deviceType =
939 mVRSystem->GetTrackedDeviceClass(originInfo.trackedDeviceIndex);
940 if (deviceType != ::vr::TrackedDeviceClass_Controller &&
941 deviceType != ::vr::TrackedDeviceClass_GenericTracker) {
942 continue;
945 if (mControllerDeviceIndex[stateIndex] != handIndex) {
946 VRControllerState& controllerState = aState.controllerState[stateIndex];
948 // Get controllers' action handles.
949 auto SetActionsToWriter = [&](ControllerAction& aAction) {
950 vr::VRInput()->GetActionHandle(aAction.name.BeginReading(),
951 &aAction.handle);
954 SetActionsToWriter(mControllerHand[handIndex].mActionPose);
955 SetActionsToWriter(mControllerHand[handIndex].mActionHaptic);
956 SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Analog);
957 SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Pressed);
958 SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Touched);
959 SetActionsToWriter(mControllerHand[handIndex].mActionTrigger_Value);
960 SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Pressed);
961 SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Touched);
962 SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Pressed);
963 SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Touched);
964 SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Pressed);
965 SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Touched);
966 SetActionsToWriter(mControllerHand[handIndex].mActionA_Pressed);
967 SetActionsToWriter(mControllerHand[handIndex].mActionA_Touched);
968 SetActionsToWriter(mControllerHand[handIndex].mActionB_Pressed);
969 SetActionsToWriter(mControllerHand[handIndex].mActionB_Touched);
970 SetActionsToWriter(mControllerHand[handIndex].mActionThumbstick_Analog);
971 SetActionsToWriter(
972 mControllerHand[handIndex].mActionThumbstick_Pressed);
973 SetActionsToWriter(
974 mControllerHand[handIndex].mActionThumbstick_Touched);
975 SetActionsToWriter(mControllerHand[handIndex].mActionFingerIndex_Value);
976 SetActionsToWriter(
977 mControllerHand[handIndex].mActionFingerMiddle_Value);
978 SetActionsToWriter(mControllerHand[handIndex].mActionFingerRing_Value);
979 SetActionsToWriter(mControllerHand[handIndex].mActionFingerPinky_Value);
980 SetActionsToWriter(mControllerHand[handIndex].mActionBumper_Pressed);
982 nsCString deviceId;
983 VRControllerType contrlType = VRControllerType::_empty;
984 GetControllerDeviceId(deviceType, originInfo.trackedDeviceIndex,
985 deviceId, contrlType);
986 // Controllers should be the same type with one VR display.
987 MOZ_ASSERT(controllerType == contrlType ||
988 controllerType == VRControllerType::_empty);
989 controllerType = contrlType;
990 strncpy(controllerState.controllerName, deviceId.BeginReading(),
991 kVRControllerNameMaxLen);
992 controllerState.numHaptics = kNumOpenVRHaptics;
993 controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer;
994 controllerState.type = controllerType;
996 controllerPresent[stateIndex] = true;
997 mControllerDeviceIndex[stateIndex] = static_cast<OpenVRHand>(handIndex);
998 ++stateIndex;
1002 // Clear out entries for disconnected controllers
1003 for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
1004 stateIndex++) {
1005 if (!controllerPresent[stateIndex] &&
1006 mControllerDeviceIndex[stateIndex] != OpenVRHand::None) {
1007 mControllerDeviceIndex[stateIndex] = OpenVRHand::None;
1008 memset(&aState.controllerState[stateIndex], 0, sizeof(VRControllerState));
1012 // Create controller mapper
1013 if (controllerType != VRControllerType::_empty) {
1014 switch (controllerType) {
1015 case VRControllerType::HTCVive:
1016 mControllerMapper = MakeUnique<OpenVRViveMapper>();
1017 break;
1018 case VRControllerType::HTCViveCosmos:
1019 mControllerMapper = MakeUnique<OpenVRCosmosMapper>();
1020 break;
1021 #if defined(XP_WIN)
1022 case VRControllerType::MSMR:
1023 mControllerMapper = MakeUnique<OpenVRWMRMapper>();
1024 break;
1025 #endif
1026 case VRControllerType::ValveIndex:
1027 mControllerMapper = MakeUnique<OpenVRKnucklesMapper>();
1028 break;
1029 default:
1030 mControllerMapper = MakeUnique<OpenVRDefaultMapper>();
1031 NS_WARNING("Undefined controller type");
1032 break;
1037 void OpenVRSession::UpdateControllerButtons(VRSystemState& aState) {
1038 MOZ_ASSERT(mVRSystem);
1040 for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
1041 ++stateIndex) {
1042 const OpenVRHand role = mControllerDeviceIndex[stateIndex];
1043 if (role == OpenVRHand::None) {
1044 continue;
1046 VRControllerState& controllerState = aState.controllerState[stateIndex];
1047 controllerState.hand = GetControllerHandFromControllerRole(role);
1048 mControllerMapper->UpdateButtons(controllerState, mControllerHand[role]);
1049 SetControllerSelectionAndSqueezeFrameId(
1050 controllerState, aState.displayState.lastSubmittedFrameId);
1054 void OpenVRSession::UpdateControllerPoses(VRSystemState& aState) {
1055 MOZ_ASSERT(mVRSystem);
1057 for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
1058 ++stateIndex) {
1059 const OpenVRHand role = mControllerDeviceIndex[stateIndex];
1060 if (role == OpenVRHand::None) {
1061 continue;
1063 VRControllerState& controllerState = aState.controllerState[stateIndex];
1064 vr::InputPoseActionData_t poseData;
1065 if (vr::VRInput()->GetPoseActionDataRelativeToNow(
1066 mControllerHand[role].mActionPose.handle,
1067 vr::TrackingUniverseSeated, 0, &poseData, sizeof(poseData),
1068 vr::k_ulInvalidInputValueHandle) != vr::VRInputError_None ||
1069 !poseData.bActive || !poseData.pose.bPoseIsValid) {
1070 controllerState.isOrientationValid = false;
1071 controllerState.isPositionValid = false;
1072 } else {
1073 const ::vr::TrackedDevicePose_t& pose = poseData.pose;
1074 if (pose.bDeviceIsConnected) {
1075 controllerState.flags =
1076 (dom::GamepadCapabilityFlags::Cap_Orientation |
1077 dom::GamepadCapabilityFlags::Cap_Position |
1078 dom::GamepadCapabilityFlags::Cap_GripSpacePosition);
1079 } else {
1080 controllerState.flags = dom::GamepadCapabilityFlags::Cap_None;
1082 if (pose.bPoseIsValid &&
1083 pose.eTrackingResult == ::vr::TrackingResult_Running_OK) {
1084 gfx::Matrix4x4 m;
1086 // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
1087 // because of its arrangement, we can copy the 12 elements in and
1088 // then transpose them to the right place. We do this so we can
1089 // pull out a Quaternion.
1090 memcpy(&m.components, &pose.mDeviceToAbsoluteTracking,
1091 sizeof(pose.mDeviceToAbsoluteTracking));
1092 m.Transpose();
1094 gfx::Quaternion rot;
1095 rot.SetFromRotationMatrix(m);
1097 controllerState.pose.orientation[0] = rot.x;
1098 controllerState.pose.orientation[1] = rot.y;
1099 controllerState.pose.orientation[2] = rot.z;
1100 controllerState.pose.orientation[3] = rot.w;
1101 controllerState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0];
1102 controllerState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1];
1103 controllerState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2];
1104 controllerState.pose.angularAcceleration[0] = 0.0f;
1105 controllerState.pose.angularAcceleration[1] = 0.0f;
1106 controllerState.pose.angularAcceleration[2] = 0.0f;
1107 controllerState.isOrientationValid = true;
1109 controllerState.pose.position[0] = m._41;
1110 controllerState.pose.position[1] = m._42;
1111 controllerState.pose.position[2] = m._43;
1112 controllerState.pose.linearVelocity[0] = pose.vVelocity.v[0];
1113 controllerState.pose.linearVelocity[1] = pose.vVelocity.v[1];
1114 controllerState.pose.linearVelocity[2] = pose.vVelocity.v[2];
1115 controllerState.pose.linearAcceleration[0] = 0.0f;
1116 controllerState.pose.linearAcceleration[1] = 0.0f;
1117 controllerState.pose.linearAcceleration[2] = 0.0f;
1118 controllerState.isPositionValid = true;
1120 // Calculate its target ray space by shifting degrees in x-axis
1121 // for ergonomic.
1122 const float kPointerAngleDegrees = -0.698; // 40 degrees.
1123 gfx::Matrix4x4 rayMtx(m);
1124 rayMtx.RotateX(kPointerAngleDegrees);
1125 gfx::Quaternion rayRot;
1126 rayRot.SetFromRotationMatrix(rayMtx);
1128 controllerState.targetRayPose = controllerState.pose;
1129 controllerState.targetRayPose.orientation[0] = rayRot.x;
1130 controllerState.targetRayPose.orientation[1] = rayRot.y;
1131 controllerState.targetRayPose.orientation[2] = rayRot.z;
1132 controllerState.targetRayPose.orientation[3] = rayRot.w;
1133 controllerState.targetRayPose.position[0] = rayMtx._41;
1134 controllerState.targetRayPose.position[1] = rayMtx._42;
1135 controllerState.targetRayPose.position[2] = rayMtx._43;
1141 void OpenVRSession::GetControllerDeviceId(
1142 ::vr::ETrackedDeviceClass aDeviceType,
1143 ::vr::TrackedDeviceIndex_t aDeviceIndex, nsCString& aId,
1144 VRControllerType& aControllerType) {
1145 switch (aDeviceType) {
1146 case ::vr::TrackedDeviceClass_Controller: {
1147 ::vr::ETrackedPropertyError err;
1148 uint32_t requiredBufferLen;
1149 bool isFound = false;
1150 char charBuf[128];
1151 requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty(
1152 aDeviceIndex, ::vr::Prop_RenderModelName_String, charBuf, 128, &err);
1153 if (requiredBufferLen > 128) {
1154 MOZ_CRASH("Larger than the buffer size.");
1156 MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success);
1157 nsCString deviceId(charBuf);
1158 if (deviceId.Find("vr_controller_vive") != kNotFound) {
1159 aId.AssignLiteral("OpenVR Gamepad");
1160 isFound = true;
1161 aControllerType = VRControllerType::HTCVive;
1162 } else if (deviceId.Find("knuckles") != kNotFound ||
1163 deviceId.Find("valve_controller_knu") != kNotFound) {
1164 aId.AssignLiteral("OpenVR Knuckles");
1165 isFound = true;
1166 aControllerType = VRControllerType::ValveIndex;
1167 } else if (deviceId.Find("vive_cosmos_controller") != kNotFound) {
1168 aId.AssignLiteral("OpenVR Cosmos");
1169 isFound = true;
1170 aControllerType = VRControllerType::HTCViveCosmos;
1172 if (!isFound) {
1173 requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty(
1174 aDeviceIndex, ::vr::Prop_SerialNumber_String, charBuf, 128, &err);
1175 if (requiredBufferLen > 128) {
1176 MOZ_CRASH("Larger than the buffer size.");
1178 MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success);
1179 deviceId.Assign(charBuf);
1180 if (deviceId.Find("MRSOURCE") != kNotFound) {
1181 aId.AssignLiteral("Spatial Controller (Spatial Interaction Source) ");
1182 mIsWindowsMR = true;
1183 isFound = true;
1184 aControllerType = VRControllerType::MSMR;
1187 if (!isFound) {
1188 aId.AssignLiteral("OpenVR Undefined");
1189 aControllerType = VRControllerType::_empty;
1191 break;
1193 case ::vr::TrackedDeviceClass_GenericTracker: {
1194 aId.AssignLiteral("OpenVR Tracker");
1195 aControllerType = VRControllerType::_empty;
1196 break;
1198 default:
1199 MOZ_ASSERT(false);
1200 break;
1204 void OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) {
1205 UpdateHeadsetPose(aSystemState);
1206 UpdateEyeParameters(aSystemState);
1207 EnumerateControllers(aSystemState);
1209 vr::VRActiveActionSet_t actionSet = {0};
1210 actionSet.ulActionSet = mActionsetFirefox;
1211 vr::VRInput()->UpdateActionState(&actionSet, sizeof(actionSet), 1);
1212 UpdateControllerButtons(aSystemState);
1213 UpdateControllerPoses(aSystemState);
1214 UpdateTelemetry(aSystemState);
1217 void OpenVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {
1218 bool isHmdPresent = ::vr::VR_IsHmdPresent();
1219 if (!isHmdPresent) {
1220 mShouldQuit = true;
1223 ::vr::VREvent_t event;
1224 while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
1225 switch (event.eventType) {
1226 case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
1227 if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
1228 aSystemState.displayState.isMounted = true;
1230 break;
1231 case ::vr::VREvent_TrackedDeviceUserInteractionEnded:
1232 if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
1233 aSystemState.displayState.isMounted = false;
1235 break;
1236 case ::vr::EVREventType::VREvent_TrackedDeviceActivated:
1237 if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
1238 aSystemState.displayState.isConnected = true;
1240 break;
1241 case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
1242 if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
1243 aSystemState.displayState.isConnected = false;
1245 break;
1246 case ::vr::EVREventType::VREvent_DriverRequestedQuit:
1247 case ::vr::EVREventType::VREvent_Quit:
1248 // When SteamVR runtime haven't been launched before viewing VR,
1249 // SteamVR will send a VREvent_ProcessQuit event. It will tell the parent
1250 // process to shutdown the VR process, and we need to avoid it.
1251 // case ::vr::EVREventType::VREvent_ProcessQuit:
1252 case ::vr::EVREventType::VREvent_QuitAcknowledged:
1253 mShouldQuit = true;
1254 break;
1255 default:
1256 // ignore
1257 break;
1262 #if defined(XP_WIN)
1263 bool OpenVRSession::SubmitFrame(
1264 const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
1265 ID3D11Texture2D* aTexture) {
1266 return SubmitFrame((void*)aTexture, ::vr::ETextureType::TextureType_DirectX,
1267 aLayer.leftEyeRect, aLayer.rightEyeRect);
1269 #elif defined(XP_MACOSX)
1270 bool OpenVRSession::SubmitFrame(
1271 const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
1272 const VRLayerTextureHandle& aTexture) {
1273 return SubmitFrame(aTexture, ::vr::ETextureType::TextureType_IOSurface,
1274 aLayer.leftEyeRect, aLayer.rightEyeRect);
1276 #endif
1278 bool OpenVRSession::SubmitFrame(const VRLayerTextureHandle& aTextureHandle,
1279 ::vr::ETextureType aTextureType,
1280 const VRLayerEyeRect& aLeftEyeRect,
1281 const VRLayerEyeRect& aRightEyeRect) {
1282 ::vr::Texture_t tex;
1283 #if defined(XP_MACOSX)
1284 // We get aTextureHandle from get_SurfaceDescriptorMacIOSurface() at
1285 // VRDisplayExternal. scaleFactor and opaque are skipped because they always
1286 // are 1.0 and false.
1287 RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(aTextureHandle);
1288 if (!surf) {
1289 NS_WARNING("OpenVRSession::SubmitFrame failed to get a MacIOSurface");
1290 return false;
1293 CFTypeRefPtr<IOSurfaceRef> ioSurface = surf->GetIOSurfaceRef();
1294 tex.handle = (void*)ioSurface.get();
1295 #else
1296 tex.handle = aTextureHandle;
1297 #endif
1298 tex.eType = aTextureType;
1299 tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
1301 ::vr::VRTextureBounds_t bounds;
1302 bounds.uMin = aLeftEyeRect.x;
1303 bounds.vMin = 1.0 - aLeftEyeRect.y;
1304 bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width;
1305 bounds.vMax = 1.0 - (aLeftEyeRect.y + aLeftEyeRect.height);
1307 ::vr::EVRCompositorError err;
1308 err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds);
1309 if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
1310 printf_stderr("OpenVR Compositor Submit() failed.\n");
1313 bounds.uMin = aRightEyeRect.x;
1314 bounds.vMin = 1.0 - aRightEyeRect.y;
1315 bounds.uMax = aRightEyeRect.x + aRightEyeRect.width;
1316 bounds.vMax = 1.0 - (aRightEyeRect.y + aRightEyeRect.height);
1318 err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds);
1319 if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
1320 printf_stderr("OpenVR Compositor Submit() failed.\n");
1323 mVRCompositor->PostPresentHandoff();
1324 return true;
1327 void OpenVRSession::StopPresentation() {
1328 mVRCompositor->ClearLastSubmittedFrame();
1330 ::vr::Compositor_CumulativeStats stats;
1331 mVRCompositor->GetCumulativeStats(&stats,
1332 sizeof(::vr::Compositor_CumulativeStats));
1335 bool OpenVRSession::StartPresentation() { return true; }
1337 void OpenVRSession::VibrateHaptic(uint32_t aControllerIdx,
1338 uint32_t aHapticIndex, float aIntensity,
1339 float aDuration) {
1340 MutexAutoLock lock(mControllerHapticStateMutex);
1342 // Initilize the haptic thread when the first time to do vibration.
1343 if (!mHapticThread) {
1344 NS_DispatchToMainThread(NS_NewRunnableFunction(
1345 "OpenVRSession::StartHapticThread", [this]() { StartHapticThread(); }));
1347 if (aHapticIndex >= kNumOpenVRHaptics ||
1348 aControllerIdx >= kVRControllerMaxCount) {
1349 return;
1352 const OpenVRHand role = mControllerDeviceIndex[aControllerIdx];
1353 if (role == OpenVRHand::None) {
1354 return;
1356 mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
1357 mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
1360 void OpenVRSession::StartHapticThread() {
1361 MOZ_ASSERT(NS_IsMainThread());
1362 if (!mHapticThread) {
1363 mHapticThread = new VRThread("VR_OpenVR_Haptics"_ns);
1365 mHapticThread->Start();
1366 StartHapticTimer();
1369 void OpenVRSession::StopHapticThread() {
1370 if (mHapticThread) {
1371 NS_DispatchToMainThread(NS_NewRunnableFunction(
1372 "mHapticThread::Shutdown",
1373 [thread = mHapticThread]() { thread->Shutdown(); }));
1374 mHapticThread = nullptr;
1378 void OpenVRSession::StartHapticTimer() {
1379 if (!mHapticTimer && mHapticThread) {
1380 mLastHapticUpdate = TimeStamp();
1381 mHapticTimer = NS_NewTimer();
1382 nsCOMPtr<nsIThread> thread = mHapticThread->GetThread();
1383 mHapticTimer->SetTarget(thread);
1384 mHapticTimer->InitWithNamedFuncCallback(
1385 HapticTimerCallback, this, kVRHapticUpdateInterval,
1386 nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
1387 "OpenVRSession::HapticTimerCallback");
1391 void OpenVRSession::StopHapticTimer() {
1392 if (mHapticTimer) {
1393 mHapticTimer->Cancel();
1394 mHapticTimer = nullptr;
1398 /*static*/
1399 void OpenVRSession::HapticTimerCallback(nsITimer* aTimer, void* aClosure) {
1401 * It is safe to use the pointer passed in aClosure to reference the
1402 * OpenVRSession object as the timer is canceled in OpenVRSession::Shutdown,
1403 * which is called by the OpenVRSession destructor, guaranteeing
1404 * that this function runs if and only if the VRManager object is valid.
1406 OpenVRSession* self = static_cast<OpenVRSession*>(aClosure);
1407 MOZ_ASSERT(self);
1408 self->UpdateHaptics();
1411 void OpenVRSession::UpdateHaptics() {
1412 MOZ_ASSERT(mHapticThread->GetThread() == NS_GetCurrentThread());
1413 MOZ_ASSERT(mVRSystem);
1415 MutexAutoLock lock(mControllerHapticStateMutex);
1417 TimeStamp now = TimeStamp::Now();
1418 if (mLastHapticUpdate.IsNull()) {
1419 mLastHapticUpdate = now;
1420 return;
1422 float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds();
1423 mLastHapticUpdate = now;
1425 for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
1426 ++stateIndex) {
1427 const OpenVRHand role = mControllerDeviceIndex[stateIndex];
1428 if (role == OpenVRHand::None) {
1429 continue;
1431 for (uint32_t hapticIdx = 0; hapticIdx < kNumOpenVRHaptics; hapticIdx++) {
1432 float intensity = mHapticPulseIntensity[stateIndex][hapticIdx];
1433 float duration = mHapticPulseRemaining[stateIndex][hapticIdx];
1434 if (duration <= 0.0f || intensity <= 0.0f) {
1435 continue;
1437 vr::VRInput()->TriggerHapticVibrationAction(
1438 mControllerHand[role].mActionHaptic.handle, 0.0f, deltaTime, 4.0f,
1439 intensity > 1.0f ? 1.0f : intensity, vr::k_ulInvalidInputValueHandle);
1441 duration -= deltaTime;
1442 if (duration < 0.0f) {
1443 duration = 0.0f;
1445 mHapticPulseRemaining[stateIndex][hapticIdx] = duration;
1450 void OpenVRSession::StopVibrateHaptic(uint32_t aControllerIdx) {
1451 MutexAutoLock lock(mControllerHapticStateMutex);
1452 if (aControllerIdx >= kVRControllerMaxCount) {
1453 return;
1455 for (int iHaptic = 0; iHaptic < kNumOpenVRHaptics; iHaptic++) {
1456 mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
1460 void OpenVRSession::StopAllHaptics() {
1461 MutexAutoLock lock(mControllerHapticStateMutex);
1462 for (auto& controller : mHapticPulseRemaining) {
1463 for (auto& haptic : controller) {
1464 haptic = 0.0f;
1469 void OpenVRSession::UpdateTelemetry(VRSystemState& aSystemState) {
1470 ::vr::Compositor_CumulativeStats stats;
1471 mVRCompositor->GetCumulativeStats(&stats,
1472 sizeof(::vr::Compositor_CumulativeStats));
1473 aSystemState.displayState.droppedFrameCount = stats.m_nNumReprojectedFrames;
1476 } // namespace mozilla::gfx