summaryrefslogtreecommitdiffstats
path: root/gfx/vr/service/OpenVRSession.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/vr/service/OpenVRSession.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/vr/service/OpenVRSession.cpp')
-rw-r--r--gfx/vr/service/OpenVRSession.cpp1476
1 files changed, 1476 insertions, 0 deletions
diff --git a/gfx/vr/service/OpenVRSession.cpp b/gfx/vr/service/OpenVRSession.cpp
new file mode 100644
index 0000000000..bfb486799d
--- /dev/null
+++ b/gfx/vr/service/OpenVRSession.cpp
@@ -0,0 +1,1476 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <fstream>
+#include "mozilla/JSONStringWriteFuncs.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsIThread.h"
+#include "nsString.h"
+
+#include "OpenVRSession.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+#if defined(XP_WIN)
+# include <d3d11.h>
+# include "mozilla/gfx/DeviceManagerDx.h"
+#elif defined(XP_MACOSX)
+# include "mozilla/gfx/MacIOSurface.h"
+#endif
+
+#if !defined(XP_WIN)
+# include <sys/stat.h> // for umask()
+#endif
+
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "binding/OpenVRCosmosBinding.h"
+#include "binding/OpenVRKnucklesBinding.h"
+#include "binding/OpenVRViveBinding.h"
+#include "OpenVRCosmosMapper.h"
+#include "OpenVRDefaultMapper.h"
+#include "OpenVRKnucklesMapper.h"
+#include "OpenVRViveMapper.h"
+#if defined(XP_WIN) // Windows Mixed Reality is only available in Windows.
+# include "OpenVRWMRMapper.h"
+# include "binding/OpenVRWMRBinding.h"
+#endif
+
+#include "VRParent.h"
+#include "VRProcessChild.h"
+#include "VRThread.h"
+
+#if !defined(M_PI)
+# define M_PI 3.14159265358979323846264338327950288
+#endif
+
+#define BTN_MASK_FROM_ID(_id) ::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
+
+// Haptic feedback is updated every 5ms, as this is
+// the minimum period between new haptic pulse requests.
+// Effectively, this results in a pulse width modulation
+// with an interval of 5ms. Through experimentation, the
+// maximum duty cycle was found to be about 3.9ms
+const uint32_t kVRHapticUpdateInterval = 5;
+
+using namespace mozilla::gfx;
+
+namespace mozilla::gfx {
+
+namespace {
+
+class ControllerManifestFile {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ControllerManifestFile)
+
+ public:
+ static already_AddRefed<ControllerManifestFile> CreateManifest() {
+ RefPtr<ControllerManifestFile> manifest = new ControllerManifestFile();
+ return manifest.forget();
+ }
+
+ bool IsExisting() {
+ if (mFileName.IsEmpty() ||
+ !std::ifstream(mFileName.BeginReading()).good()) {
+ return false;
+ }
+ return true;
+ }
+
+ void SetFileName(const char* aName) { mFileName = aName; }
+
+ const char* GetFileName() const { return mFileName.BeginReading(); }
+
+ private:
+ ControllerManifestFile() = default;
+
+ ~ControllerManifestFile() {
+ if (!mFileName.IsEmpty() && remove(mFileName.BeginReading()) != 0) {
+ MOZ_ASSERT(false, "Delete controller manifest file failed.");
+ }
+ mFileName = "";
+ }
+
+ nsCString mFileName;
+};
+
+// We wanna keep these temporary files existing
+// until Firefox is closed instead of following OpenVRSession's lifetime.
+StaticRefPtr<ControllerManifestFile> sCosmosBindingFile;
+StaticRefPtr<ControllerManifestFile> sKnucklesBindingFile;
+StaticRefPtr<ControllerManifestFile> sViveBindingFile;
+#if defined(XP_WIN)
+StaticRefPtr<ControllerManifestFile> sWMRBindingFile;
+#endif
+StaticRefPtr<ControllerManifestFile> sControllerActionFile;
+
+dom::GamepadHand GetControllerHandFromControllerRole(OpenVRHand aRole) {
+ dom::GamepadHand hand;
+ switch (aRole) {
+ case OpenVRHand::None:
+ hand = dom::GamepadHand::_empty;
+ break;
+ case OpenVRHand::Left:
+ hand = dom::GamepadHand::Left;
+ break;
+ case OpenVRHand::Right:
+ hand = dom::GamepadHand::Right;
+ break;
+ default:
+ hand = dom::GamepadHand::_empty;
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ return hand;
+}
+
+bool FileIsExisting(const nsCString& aPath) {
+ if (aPath.IsEmpty() || !std::ifstream(aPath.BeginReading()).good()) {
+ return false;
+ }
+ return true;
+}
+
+}; // anonymous namespace
+
+#if defined(XP_WIN)
+bool GenerateTempFileName(nsCString& aPath) {
+ TCHAR tempPathBuffer[MAX_PATH];
+ TCHAR tempFileName[MAX_PATH];
+
+ // Gets the temp path env string (no guarantee it's a valid path).
+ DWORD dwRetVal = GetTempPath(MAX_PATH, tempPathBuffer);
+ if (dwRetVal > MAX_PATH || (dwRetVal == 0)) {
+ NS_WARNING("OpenVR - Creating temp path failed.");
+ return false;
+ }
+
+ // Generates a temporary file name.
+ UINT uRetVal = GetTempFileName(tempPathBuffer, // directory for tmp files
+ TEXT("mozvr"), // temp file name prefix
+ 0, // create unique name
+ tempFileName); // buffer for name
+ if (uRetVal == 0) {
+ NS_WARNING("OpenVR - Creating temp file failed.");
+ return false;
+ }
+
+ aPath.Assign(NS_ConvertUTF16toUTF8(tempFileName));
+ return true;
+}
+#else
+bool GenerateTempFileName(nsCString& aPath) {
+ const char tmp[] = "/tmp/mozvrXXXXXX";
+ char fileName[PATH_MAX];
+
+ strcpy(fileName, tmp);
+ const mode_t prevMask = umask(S_IXUSR | S_IRWXO | S_IRWXG);
+ const int fd = mkstemp(fileName);
+ umask(prevMask);
+ if (fd == -1) {
+ NS_WARNING(nsPrintfCString("OpenVR - Creating temp file failed: %s",
+ strerror(errno))
+ .get());
+ return false;
+ }
+ close(fd);
+
+ aPath.Assign(fileName);
+ return true;
+}
+#endif // defined(XP_WIN)
+
+OpenVRSession::OpenVRSession()
+ : mVRSystem(nullptr),
+ mVRChaperone(nullptr),
+ mVRCompositor(nullptr),
+ mHapticPulseRemaining{},
+ mHapticPulseIntensity{},
+ mIsWindowsMR(false),
+ mControllerHapticStateMutex(
+ "OpenVRSession::mControllerHapticStateMutex") {
+ std::fill_n(mControllerDeviceIndex, kVRControllerMaxCount, OpenVRHand::None);
+}
+
+OpenVRSession::~OpenVRSession() {
+ mActionsetFirefox = ::vr::k_ulInvalidActionSetHandle;
+ Shutdown();
+}
+
+bool OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState,
+ bool aDetectRuntimesOnly) {
+ if (StaticPrefs::dom_vr_puppet_enabled()) {
+ // Ensure that tests using the VR Puppet do not find real hardware
+ return false;
+ }
+ if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_openvr_enabled()) {
+ return false;
+ }
+ if (mVRSystem != nullptr) {
+ // Already initialized
+ return true;
+ }
+ if (!::vr::VR_IsRuntimeInstalled()) {
+ return false;
+ }
+ if (aDetectRuntimesOnly) {
+ aSystemState.displayState.capabilityFlags |=
+ VRDisplayCapabilityFlags::Cap_ImmersiveVR;
+ return false;
+ }
+ if (!::vr::VR_IsHmdPresent()) {
+ return false;
+ }
+
+ ::vr::HmdError err;
+
+ ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
+ if (err) {
+ return false;
+ }
+
+ mVRSystem = (::vr::IVRSystem*)::vr::VR_GetGenericInterface(
+ ::vr::IVRSystem_Version, &err);
+ if (err || !mVRSystem) {
+ Shutdown();
+ return false;
+ }
+ mVRChaperone = (::vr::IVRChaperone*)::vr::VR_GetGenericInterface(
+ ::vr::IVRChaperone_Version, &err);
+ if (err || !mVRChaperone) {
+ Shutdown();
+ return false;
+ }
+ mVRCompositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(
+ ::vr::IVRCompositor_Version, &err);
+ if (err || !mVRCompositor) {
+ Shutdown();
+ return false;
+ }
+
+#if defined(XP_WIN)
+ if (!CreateD3DObjects()) {
+ Shutdown();
+ return false;
+ }
+
+#endif
+
+ // Configure coordinate system
+ mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated);
+
+ if (!InitState(aSystemState)) {
+ Shutdown();
+ return false;
+ }
+ if (!SetupContollerActions()) {
+ return false;
+ }
+
+ // Succeeded
+ return true;
+}
+
+// "actions": [] Action paths must take the form: "/actions/<action
+// set>/in|out/<action>"
+#define CreateControllerAction(hand, name, type) \
+ ControllerAction("/actions/firefox/in/" #hand "Hand_" #name, #type)
+#define CreateControllerOutAction(hand, name, type) \
+ ControllerAction("/actions/firefox/out/" #hand "Hand_" #name, #type)
+
+bool OpenVRSession::SetupContollerActions() {
+ if (!vr::VRInput()) {
+ NS_WARNING("OpenVR - vr::VRInput() is null.");
+ return false;
+ }
+
+ // Check if this device binding file has been created.
+ // If it didn't exist yet, create a new temp file.
+ nsCString controllerAction;
+ nsCString viveManifest;
+ nsCString WMRManifest;
+ nsCString knucklesManifest;
+ nsCString cosmosManifest;
+
+ // Getting / Generating manifest file paths.
+ if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
+ VRParent* vrParent = VRProcessChild::GetVRParent();
+ nsCString output;
+
+ if (vrParent->GetOpenVRControllerActionPath(&output)) {
+ controllerAction = output;
+ }
+
+ if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::HTCVive,
+ &output)) {
+ viveManifest = output;
+ }
+ if (!viveManifest.Length() || !FileIsExisting(viveManifest)) {
+ if (!GenerateTempFileName(viveManifest)) {
+ return false;
+ }
+ OpenVRViveBinding viveBinding;
+ std::ofstream viveBindingFile(viveManifest.BeginReading());
+ if (viveBindingFile.is_open()) {
+ viveBindingFile << viveBinding.binding;
+ viveBindingFile.close();
+ }
+ }
+
+#if defined(XP_WIN)
+ if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::MSMR,
+ &output)) {
+ WMRManifest = output;
+ }
+ if (!WMRManifest.Length() || !FileIsExisting(WMRManifest)) {
+ if (!GenerateTempFileName(WMRManifest)) {
+ return false;
+ }
+ OpenVRWMRBinding WMRBinding;
+ std::ofstream WMRBindingFile(WMRManifest.BeginReading());
+ if (WMRBindingFile.is_open()) {
+ WMRBindingFile << WMRBinding.binding;
+ WMRBindingFile.close();
+ }
+ }
+#endif
+ if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::ValveIndex,
+ &output)) {
+ knucklesManifest = output;
+ }
+ if (!knucklesManifest.Length() || !FileIsExisting(knucklesManifest)) {
+ if (!GenerateTempFileName(knucklesManifest)) {
+ return false;
+ }
+ OpenVRKnucklesBinding knucklesBinding;
+ std::ofstream knucklesBindingFile(knucklesManifest.BeginReading());
+ if (knucklesBindingFile.is_open()) {
+ knucklesBindingFile << knucklesBinding.binding;
+ knucklesBindingFile.close();
+ }
+ }
+ if (vrParent->GetOpenVRControllerManifestPath(
+ VRControllerType::HTCViveCosmos, &output)) {
+ cosmosManifest = output;
+ }
+ if (!cosmosManifest.Length() || !FileIsExisting(cosmosManifest)) {
+ if (!GenerateTempFileName(cosmosManifest)) {
+ return false;
+ }
+ OpenVRCosmosBinding cosmosBinding;
+ std::ofstream cosmosBindingFile(cosmosManifest.BeginReading());
+ if (cosmosBindingFile.is_open()) {
+ cosmosBindingFile << cosmosBinding.binding;
+ cosmosBindingFile.close();
+ }
+ }
+ } else {
+ // Without using VR process
+ if (!sControllerActionFile) {
+ sControllerActionFile = ControllerManifestFile::CreateManifest();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ClearOnShutdown ControllerManifestFile",
+ []() { ClearOnShutdown(&sControllerActionFile); }));
+ }
+ controllerAction = sControllerActionFile->GetFileName();
+
+ if (!sViveBindingFile) {
+ sViveBindingFile = ControllerManifestFile::CreateManifest();
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
+ []() { ClearOnShutdown(&sViveBindingFile); }));
+ }
+ if (!sViveBindingFile->IsExisting()) {
+ nsCString viveBindingPath;
+ if (!GenerateTempFileName(viveBindingPath)) {
+ return false;
+ }
+ sViveBindingFile->SetFileName(viveBindingPath.BeginReading());
+ OpenVRViveBinding viveBinding;
+ std::ofstream viveBindingFile(sViveBindingFile->GetFileName());
+ if (viveBindingFile.is_open()) {
+ viveBindingFile << viveBinding.binding;
+ viveBindingFile.close();
+ }
+ }
+ viveManifest = sViveBindingFile->GetFileName();
+
+ if (!sKnucklesBindingFile) {
+ sKnucklesBindingFile = ControllerManifestFile::CreateManifest();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ClearOnShutdown ControllerManifestFile",
+ []() { ClearOnShutdown(&sKnucklesBindingFile); }));
+ }
+ if (!sKnucklesBindingFile->IsExisting()) {
+ nsCString knucklesBindingPath;
+ if (!GenerateTempFileName(knucklesBindingPath)) {
+ return false;
+ }
+ sKnucklesBindingFile->SetFileName(knucklesBindingPath.BeginReading());
+ OpenVRKnucklesBinding knucklesBinding;
+ std::ofstream knucklesBindingFile(sKnucklesBindingFile->GetFileName());
+ if (knucklesBindingFile.is_open()) {
+ knucklesBindingFile << knucklesBinding.binding;
+ knucklesBindingFile.close();
+ }
+ }
+ knucklesManifest = sKnucklesBindingFile->GetFileName();
+
+ if (!sCosmosBindingFile) {
+ sCosmosBindingFile = ControllerManifestFile::CreateManifest();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ClearOnShutdown ControllerManifestFile",
+ []() { ClearOnShutdown(&sCosmosBindingFile); }));
+ }
+ if (!sCosmosBindingFile->IsExisting()) {
+ nsCString cosmosBindingPath;
+ if (!GenerateTempFileName(cosmosBindingPath)) {
+ return false;
+ }
+ sCosmosBindingFile->SetFileName(cosmosBindingPath.BeginReading());
+ OpenVRCosmosBinding cosmosBinding;
+ std::ofstream cosmosBindingFile(sCosmosBindingFile->GetFileName());
+ if (cosmosBindingFile.is_open()) {
+ cosmosBindingFile << cosmosBinding.binding;
+ cosmosBindingFile.close();
+ }
+ }
+ cosmosManifest = sCosmosBindingFile->GetFileName();
+#if defined(XP_WIN)
+ if (!sWMRBindingFile) {
+ sWMRBindingFile = ControllerManifestFile::CreateManifest();
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
+ []() { ClearOnShutdown(&sWMRBindingFile); }));
+ }
+ if (!sWMRBindingFile->IsExisting()) {
+ nsCString WMRBindingPath;
+ if (!GenerateTempFileName(WMRBindingPath)) {
+ return false;
+ }
+ sWMRBindingFile->SetFileName(WMRBindingPath.BeginReading());
+ OpenVRWMRBinding WMRBinding;
+ std::ofstream WMRBindingFile(sWMRBindingFile->GetFileName());
+ if (WMRBindingFile.is_open()) {
+ WMRBindingFile << WMRBinding.binding;
+ WMRBindingFile.close();
+ }
+ }
+ WMRManifest = sWMRBindingFile->GetFileName();
+#endif
+ }
+ // End of Getting / Generating manifest file paths.
+
+ // Setup controller actions.
+ ControllerInfo leftContollerInfo;
+ leftContollerInfo.mActionPose = CreateControllerAction(L, pose, pose);
+ leftContollerInfo.mActionTrackpad_Analog =
+ CreateControllerAction(L, trackpad_analog, vector2);
+ leftContollerInfo.mActionTrackpad_Pressed =
+ CreateControllerAction(L, trackpad_pressed, boolean);
+ leftContollerInfo.mActionTrackpad_Touched =
+ CreateControllerAction(L, trackpad_touched, boolean);
+ leftContollerInfo.mActionTrigger_Value =
+ CreateControllerAction(L, trigger_value, vector1);
+ leftContollerInfo.mActionGrip_Pressed =
+ CreateControllerAction(L, grip_pressed, boolean);
+ leftContollerInfo.mActionGrip_Touched =
+ CreateControllerAction(L, grip_touched, boolean);
+ leftContollerInfo.mActionMenu_Pressed =
+ CreateControllerAction(L, menu_pressed, boolean);
+ leftContollerInfo.mActionMenu_Touched =
+ CreateControllerAction(L, menu_touched, boolean);
+ leftContollerInfo.mActionSystem_Pressed =
+ CreateControllerAction(L, system_pressed, boolean);
+ leftContollerInfo.mActionSystem_Touched =
+ CreateControllerAction(L, system_touched, boolean);
+ leftContollerInfo.mActionA_Pressed =
+ CreateControllerAction(L, A_pressed, boolean);
+ leftContollerInfo.mActionA_Touched =
+ CreateControllerAction(L, A_touched, boolean);
+ leftContollerInfo.mActionB_Pressed =
+ CreateControllerAction(L, B_pressed, boolean);
+ leftContollerInfo.mActionB_Touched =
+ CreateControllerAction(L, B_touched, boolean);
+ leftContollerInfo.mActionThumbstick_Analog =
+ CreateControllerAction(L, thumbstick_analog, vector2);
+ leftContollerInfo.mActionThumbstick_Pressed =
+ CreateControllerAction(L, thumbstick_pressed, boolean);
+ leftContollerInfo.mActionThumbstick_Touched =
+ CreateControllerAction(L, thumbstick_touched, boolean);
+ leftContollerInfo.mActionFingerIndex_Value =
+ CreateControllerAction(L, finger_index_value, vector1);
+ leftContollerInfo.mActionFingerMiddle_Value =
+ CreateControllerAction(L, finger_middle_value, vector1);
+ leftContollerInfo.mActionFingerRing_Value =
+ CreateControllerAction(L, finger_ring_value, vector1);
+ leftContollerInfo.mActionFingerPinky_Value =
+ CreateControllerAction(L, finger_pinky_value, vector1);
+ leftContollerInfo.mActionBumper_Pressed =
+ CreateControllerAction(L, bumper_pressed, boolean);
+ leftContollerInfo.mActionHaptic =
+ CreateControllerOutAction(L, haptic, vibration);
+
+ ControllerInfo rightContollerInfo;
+ rightContollerInfo.mActionPose = CreateControllerAction(R, pose, pose);
+ rightContollerInfo.mActionTrackpad_Analog =
+ CreateControllerAction(R, trackpad_analog, vector2);
+ rightContollerInfo.mActionTrackpad_Pressed =
+ CreateControllerAction(R, trackpad_pressed, boolean);
+ rightContollerInfo.mActionTrackpad_Touched =
+ CreateControllerAction(R, trackpad_touched, boolean);
+ rightContollerInfo.mActionTrigger_Value =
+ CreateControllerAction(R, trigger_value, vector1);
+ rightContollerInfo.mActionGrip_Pressed =
+ CreateControllerAction(R, grip_pressed, boolean);
+ rightContollerInfo.mActionGrip_Touched =
+ CreateControllerAction(R, grip_touched, boolean);
+ rightContollerInfo.mActionMenu_Pressed =
+ CreateControllerAction(R, menu_pressed, boolean);
+ rightContollerInfo.mActionMenu_Touched =
+ CreateControllerAction(R, menu_touched, boolean);
+ rightContollerInfo.mActionSystem_Pressed =
+ CreateControllerAction(R, system_pressed, boolean);
+ rightContollerInfo.mActionSystem_Touched =
+ CreateControllerAction(R, system_touched, boolean);
+ rightContollerInfo.mActionA_Pressed =
+ CreateControllerAction(R, A_pressed, boolean);
+ rightContollerInfo.mActionA_Touched =
+ CreateControllerAction(R, A_touched, boolean);
+ rightContollerInfo.mActionB_Pressed =
+ CreateControllerAction(R, B_pressed, boolean);
+ rightContollerInfo.mActionB_Touched =
+ CreateControllerAction(R, B_touched, boolean);
+ rightContollerInfo.mActionThumbstick_Analog =
+ CreateControllerAction(R, thumbstick_analog, vector2);
+ rightContollerInfo.mActionThumbstick_Pressed =
+ CreateControllerAction(R, thumbstick_pressed, boolean);
+ rightContollerInfo.mActionThumbstick_Touched =
+ CreateControllerAction(R, thumbstick_touched, boolean);
+ rightContollerInfo.mActionFingerIndex_Value =
+ CreateControllerAction(R, finger_index_value, vector1);
+ rightContollerInfo.mActionFingerMiddle_Value =
+ CreateControllerAction(R, finger_middle_value, vector1);
+ rightContollerInfo.mActionFingerRing_Value =
+ CreateControllerAction(R, finger_ring_value, vector1);
+ rightContollerInfo.mActionFingerPinky_Value =
+ CreateControllerAction(R, finger_pinky_value, vector1);
+ rightContollerInfo.mActionBumper_Pressed =
+ CreateControllerAction(R, bumper_pressed, boolean);
+ rightContollerInfo.mActionHaptic =
+ CreateControllerOutAction(R, haptic, vibration);
+
+ mControllerHand[OpenVRHand::Left] = leftContollerInfo;
+ mControllerHand[OpenVRHand::Right] = rightContollerInfo;
+
+ if (!controllerAction.Length() || !FileIsExisting(controllerAction)) {
+ if (!GenerateTempFileName(controllerAction)) {
+ return false;
+ }
+ JSONStringWriteFunc<nsCString> actionData;
+ JSONWriter actionWriter(actionData);
+ actionWriter.Start();
+
+ actionWriter.StringProperty("version",
+ "0.1.0"); // TODO: adding a version check.
+ // "default_bindings": []
+ actionWriter.StartArrayProperty("default_bindings");
+
+ auto SetupActionWriterByControllerType = [&](const char* aType,
+ const nsCString& aManifest) {
+ actionWriter.StartObjectElement();
+ actionWriter.StringProperty("controller_type", MakeStringSpan(aType));
+ actionWriter.StringProperty("binding_url", aManifest);
+ actionWriter.EndObject();
+ };
+ SetupActionWriterByControllerType("vive_controller", viveManifest);
+ SetupActionWriterByControllerType("knuckles", knucklesManifest);
+ SetupActionWriterByControllerType("vive_cosmos_controller", cosmosManifest);
+#if defined(XP_WIN)
+ SetupActionWriterByControllerType("holographic_controller", WMRManifest);
+#endif
+ actionWriter.EndArray(); // End "default_bindings": []
+
+ actionWriter.StartArrayProperty("actions");
+
+ for (auto& controller : mControllerHand) {
+ auto SetActionsToWriter = [&](const ControllerAction& aAction) {
+ actionWriter.StartObjectElement();
+ actionWriter.StringProperty("name", aAction.name);
+ actionWriter.StringProperty("type", aAction.type);
+ actionWriter.EndObject();
+ };
+
+ SetActionsToWriter(controller.mActionPose);
+ SetActionsToWriter(controller.mActionTrackpad_Analog);
+ SetActionsToWriter(controller.mActionTrackpad_Pressed);
+ SetActionsToWriter(controller.mActionTrackpad_Touched);
+ SetActionsToWriter(controller.mActionTrigger_Value);
+ SetActionsToWriter(controller.mActionGrip_Pressed);
+ SetActionsToWriter(controller.mActionGrip_Touched);
+ SetActionsToWriter(controller.mActionMenu_Pressed);
+ SetActionsToWriter(controller.mActionMenu_Touched);
+ SetActionsToWriter(controller.mActionSystem_Pressed);
+ SetActionsToWriter(controller.mActionSystem_Touched);
+ SetActionsToWriter(controller.mActionA_Pressed);
+ SetActionsToWriter(controller.mActionA_Touched);
+ SetActionsToWriter(controller.mActionB_Pressed);
+ SetActionsToWriter(controller.mActionB_Touched);
+ SetActionsToWriter(controller.mActionThumbstick_Analog);
+ SetActionsToWriter(controller.mActionThumbstick_Pressed);
+ SetActionsToWriter(controller.mActionThumbstick_Touched);
+ SetActionsToWriter(controller.mActionFingerIndex_Value);
+ SetActionsToWriter(controller.mActionFingerMiddle_Value);
+ SetActionsToWriter(controller.mActionFingerRing_Value);
+ SetActionsToWriter(controller.mActionFingerPinky_Value);
+ SetActionsToWriter(controller.mActionBumper_Pressed);
+ SetActionsToWriter(controller.mActionHaptic);
+ }
+ actionWriter.EndArray(); // End "actions": []
+ actionWriter.End();
+
+ std::ofstream actionfile(controllerAction.BeginReading());
+ if (actionfile.is_open()) {
+ actionfile << actionData.StringCRef().get();
+ actionfile.close();
+ }
+ }
+
+ vr::EVRInputError err =
+ vr::VRInput()->SetActionManifestPath(controllerAction.BeginReading());
+ if (err != vr::VRInputError_None) {
+ NS_WARNING("OpenVR - SetActionManifestPath failed.");
+ return false;
+ }
+ // End of setup controller actions.
+
+ // Notify the parent process these manifest files are already been recorded.
+ if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SendOpenVRControllerActionPathToParent",
+ [controllerAction, viveManifest, WMRManifest, knucklesManifest,
+ cosmosManifest]() {
+ VRParent* vrParent = VRProcessChild::GetVRParent();
+ Unused << vrParent->SendOpenVRControllerActionPathToParent(
+ controllerAction);
+ Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+ VRControllerType::HTCVive, viveManifest);
+ Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+ VRControllerType::MSMR, WMRManifest);
+ Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+ VRControllerType::ValveIndex, knucklesManifest);
+ Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+ VRControllerType::HTCViveCosmos, cosmosManifest);
+ }));
+ } else {
+ sControllerActionFile->SetFileName(controllerAction.BeginReading());
+ }
+
+ return true;
+}
+
+#if defined(XP_WIN)
+bool OpenVRSession::CreateD3DObjects() {
+ RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
+ if (!device) {
+ return false;
+ }
+ if (!CreateD3DContext(device)) {
+ return false;
+ }
+ return true;
+}
+#endif
+
+void OpenVRSession::Shutdown() {
+ StopHapticTimer();
+ StopHapticThread();
+ if (mVRSystem || mVRCompositor || mVRChaperone) {
+ ::vr::VR_Shutdown();
+ mVRCompositor = nullptr;
+ mVRChaperone = nullptr;
+ mVRSystem = nullptr;
+ }
+}
+
+bool OpenVRSession::InitState(VRSystemState& aSystemState) {
+ VRDisplayState& state = aSystemState.displayState;
+ strncpy(state.displayName, "OpenVR HMD", kVRDisplayNameMaxLen);
+ state.eightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ');
+ state.isConnected =
+ mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
+ state.isMounted = false;
+ state.capabilityFlags =
+ (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
+ (int)
+ VRDisplayCapabilityFlags::Cap_Orientation |
+ (int)VRDisplayCapabilityFlags::Cap_Position |
+ (int)VRDisplayCapabilityFlags::Cap_External |
+ (int)VRDisplayCapabilityFlags::Cap_Present |
+ (int)VRDisplayCapabilityFlags::
+ Cap_StageParameters |
+ (int)
+ VRDisplayCapabilityFlags::Cap_ImmersiveVR);
+ state.blendMode = VRDisplayBlendMode::Opaque;
+ state.reportsDroppedFrames = true;
+
+ ::vr::ETrackedPropertyError err;
+ bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(
+ ::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool,
+ &err);
+ if (err == ::vr::TrackedProp_Success && bHasProximitySensor) {
+ state.capabilityFlags =
+ (VRDisplayCapabilityFlags)((int)state.capabilityFlags |
+ (int)VRDisplayCapabilityFlags::
+ Cap_MountDetection);
+ }
+
+ uint32_t w, h;
+ mVRSystem->GetRecommendedRenderTargetSize(&w, &h);
+ state.eyeResolution.width = w;
+ state.eyeResolution.height = h;
+ state.nativeFramebufferScaleFactor = 1.0f;
+
+ // default to an identity quaternion
+ aSystemState.sensorState.pose.orientation[3] = 1.0f;
+
+ UpdateStageParameters(state);
+ UpdateEyeParameters(aSystemState);
+
+ VRHMDSensorState& sensorState = aSystemState.sensorState;
+ sensorState.flags =
+ (VRDisplayCapabilityFlags)((int)
+ VRDisplayCapabilityFlags::Cap_Orientation |
+ (int)VRDisplayCapabilityFlags::Cap_Position);
+ sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion
+
+ return true;
+}
+
+void OpenVRSession::UpdateStageParameters(VRDisplayState& aState) {
+ float sizeX = 0.0f;
+ float sizeZ = 0.0f;
+ if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) {
+ ::vr::HmdMatrix34_t t =
+ mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
+ aState.stageSize.width = sizeX;
+ aState.stageSize.height = sizeZ;
+
+ aState.sittingToStandingTransform[0] = t.m[0][0];
+ aState.sittingToStandingTransform[1] = t.m[1][0];
+ aState.sittingToStandingTransform[2] = t.m[2][0];
+ aState.sittingToStandingTransform[3] = 0.0f;
+
+ aState.sittingToStandingTransform[4] = t.m[0][1];
+ aState.sittingToStandingTransform[5] = t.m[1][1];
+ aState.sittingToStandingTransform[6] = t.m[2][1];
+ aState.sittingToStandingTransform[7] = 0.0f;
+
+ aState.sittingToStandingTransform[8] = t.m[0][2];
+ aState.sittingToStandingTransform[9] = t.m[1][2];
+ aState.sittingToStandingTransform[10] = t.m[2][2];
+ aState.sittingToStandingTransform[11] = 0.0f;
+
+ aState.sittingToStandingTransform[12] = t.m[0][3];
+ aState.sittingToStandingTransform[13] = t.m[1][3];
+ aState.sittingToStandingTransform[14] = t.m[2][3];
+ aState.sittingToStandingTransform[15] = 1.0f;
+ } else {
+ // If we fail, fall back to reasonable defaults.
+ // 1m x 1m space, 0.75m high in seated position
+ aState.stageSize.width = 1.0f;
+ aState.stageSize.height = 1.0f;
+
+ aState.sittingToStandingTransform[0] = 1.0f;
+ aState.sittingToStandingTransform[1] = 0.0f;
+ aState.sittingToStandingTransform[2] = 0.0f;
+ aState.sittingToStandingTransform[3] = 0.0f;
+
+ aState.sittingToStandingTransform[4] = 0.0f;
+ aState.sittingToStandingTransform[5] = 1.0f;
+ aState.sittingToStandingTransform[6] = 0.0f;
+ aState.sittingToStandingTransform[7] = 0.0f;
+
+ aState.sittingToStandingTransform[8] = 0.0f;
+ aState.sittingToStandingTransform[9] = 0.0f;
+ aState.sittingToStandingTransform[10] = 1.0f;
+ aState.sittingToStandingTransform[11] = 0.0f;
+
+ aState.sittingToStandingTransform[12] = 0.0f;
+ aState.sittingToStandingTransform[13] = 0.75f;
+ aState.sittingToStandingTransform[14] = 0.0f;
+ aState.sittingToStandingTransform[15] = 1.0f;
+ }
+}
+
+void OpenVRSession::UpdateEyeParameters(VRSystemState& aState) {
+ // This must be called every frame in order to
+ // account for continuous adjustments to ipd.
+ gfx::Matrix4x4 headToEyeTransforms[2];
+
+ for (uint32_t eye = 0; eye < 2; ++eye) {
+ ::vr::HmdMatrix34_t eyeToHead =
+ mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye));
+ aState.displayState.eyeTranslation[eye].x = eyeToHead.m[0][3];
+ aState.displayState.eyeTranslation[eye].y = eyeToHead.m[1][3];
+ aState.displayState.eyeTranslation[eye].z = eyeToHead.m[2][3];
+
+ float left, right, up, down;
+ mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &left, &right,
+ &up, &down);
+ aState.displayState.eyeFOV[eye].upDegrees = atan(-up) * 180.0 / M_PI;
+ aState.displayState.eyeFOV[eye].rightDegrees = atan(right) * 180.0 / M_PI;
+ aState.displayState.eyeFOV[eye].downDegrees = atan(down) * 180.0 / M_PI;
+ aState.displayState.eyeFOV[eye].leftDegrees = atan(-left) * 180.0 / M_PI;
+
+ Matrix4x4 pose;
+ // NOTE! eyeToHead.m is a 3x4 matrix, not 4x4. But
+ // because of its arrangement, we can copy the 12 elements in and
+ // then transpose them to the right place.
+ memcpy(&pose._11, &eyeToHead.m, sizeof(eyeToHead.m));
+ pose.Transpose();
+ pose.Invert();
+ headToEyeTransforms[eye] = pose;
+ }
+ aState.sensorState.CalcViewMatrices(headToEyeTransforms);
+}
+
+void OpenVRSession::UpdateHeadsetPose(VRSystemState& aState) {
+ const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1;
+ ::vr::TrackedDevicePose_t poses[posesSize];
+ // Note: We *must* call WaitGetPoses in order for any rendering to happen at
+ // all.
+ mVRCompositor->WaitGetPoses(poses, posesSize, nullptr, 0);
+
+ ::vr::Compositor_FrameTiming timing;
+ timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
+ if (mVRCompositor->GetFrameTiming(&timing)) {
+ aState.sensorState.timestamp = timing.m_flSystemTimeInSeconds;
+ } else {
+ // This should not happen, but log it just in case
+ fprintf(stderr, "OpenVR - IVRCompositor::GetFrameTiming failed");
+ }
+
+ if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
+ poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
+ poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult ==
+ ::vr::TrackingResult_Running_OK) {
+ const ::vr::TrackedDevicePose_t& pose =
+ poses[::vr::k_unTrackedDeviceIndex_Hmd];
+
+ gfx::Matrix4x4 m;
+ // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
+ // because of its arrangement, we can copy the 12 elements in and
+ // then transpose them to the right place. We do this so we can
+ // pull out a Quaternion.
+ memcpy(&m._11, &pose.mDeviceToAbsoluteTracking,
+ sizeof(pose.mDeviceToAbsoluteTracking));
+ m.Transpose();
+
+ gfx::Quaternion rot;
+ rot.SetFromRotationMatrix(m);
+
+ aState.sensorState.flags =
+ (VRDisplayCapabilityFlags)((int)aState.sensorState.flags |
+ (int)VRDisplayCapabilityFlags::
+ Cap_Orientation);
+ aState.sensorState.pose.orientation[0] = rot.x;
+ aState.sensorState.pose.orientation[1] = rot.y;
+ aState.sensorState.pose.orientation[2] = rot.z;
+ aState.sensorState.pose.orientation[3] = rot.w;
+ aState.sensorState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0];
+ aState.sensorState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1];
+ aState.sensorState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2];
+
+ aState.sensorState.flags =
+ (VRDisplayCapabilityFlags)((int)aState.sensorState.flags |
+ (int)VRDisplayCapabilityFlags::Cap_Position);
+ aState.sensorState.pose.position[0] = m._41;
+ aState.sensorState.pose.position[1] = m._42;
+ aState.sensorState.pose.position[2] = m._43;
+ aState.sensorState.pose.linearVelocity[0] = pose.vVelocity.v[0];
+ aState.sensorState.pose.linearVelocity[1] = pose.vVelocity.v[1];
+ aState.sensorState.pose.linearVelocity[2] = pose.vVelocity.v[2];
+ }
+}
+
+void OpenVRSession::EnumerateControllers(VRSystemState& aState) {
+ MOZ_ASSERT(mVRSystem);
+
+ MutexAutoLock lock(mControllerHapticStateMutex);
+
+ bool controllerPresent[kVRControllerMaxCount] = {false};
+ uint32_t stateIndex = 0;
+ mActionsetFirefox = vr::k_ulInvalidActionSetHandle;
+ VRControllerType controllerType = VRControllerType::_empty;
+
+ if (vr::VRInput()->GetActionSetHandle(
+ "/actions/firefox", &mActionsetFirefox) != vr::VRInputError_None) {
+ return;
+ }
+
+ for (int8_t handIndex = 0; handIndex < OpenVRHand::Total; ++handIndex) {
+ if (handIndex == OpenVRHand::Left) {
+ if (vr::VRInput()->GetInputSourceHandle(
+ "/user/hand/left", &mControllerHand[OpenVRHand::Left].mSource) !=
+ vr::VRInputError_None) {
+ continue;
+ }
+ } else if (handIndex == OpenVRHand::Right) {
+ if (vr::VRInput()->GetInputSourceHandle(
+ "/user/hand/right",
+ &mControllerHand[OpenVRHand::Right].mSource) !=
+ vr::VRInputError_None) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT(false, "Unknown OpenVR hand type.");
+ }
+
+ vr::InputOriginInfo_t originInfo;
+ if (vr::VRInput()->GetOriginTrackedDeviceInfo(
+ mControllerHand[handIndex].mSource, &originInfo,
+ sizeof(originInfo)) == vr::VRInputError_None &&
+ originInfo.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid &&
+ mVRSystem->IsTrackedDeviceConnected(originInfo.trackedDeviceIndex)) {
+ const ::vr::ETrackedDeviceClass deviceType =
+ mVRSystem->GetTrackedDeviceClass(originInfo.trackedDeviceIndex);
+ if (deviceType != ::vr::TrackedDeviceClass_Controller &&
+ deviceType != ::vr::TrackedDeviceClass_GenericTracker) {
+ continue;
+ }
+
+ if (mControllerDeviceIndex[stateIndex] != handIndex) {
+ VRControllerState& controllerState = aState.controllerState[stateIndex];
+
+ // Get controllers' action handles.
+ auto SetActionsToWriter = [&](ControllerAction& aAction) {
+ vr::VRInput()->GetActionHandle(aAction.name.BeginReading(),
+ &aAction.handle);
+ };
+
+ SetActionsToWriter(mControllerHand[handIndex].mActionPose);
+ SetActionsToWriter(mControllerHand[handIndex].mActionHaptic);
+ SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Analog);
+ SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionTrigger_Value);
+ SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionA_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionA_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionB_Pressed);
+ SetActionsToWriter(mControllerHand[handIndex].mActionB_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionThumbstick_Analog);
+ SetActionsToWriter(
+ mControllerHand[handIndex].mActionThumbstick_Pressed);
+ SetActionsToWriter(
+ mControllerHand[handIndex].mActionThumbstick_Touched);
+ SetActionsToWriter(mControllerHand[handIndex].mActionFingerIndex_Value);
+ SetActionsToWriter(
+ mControllerHand[handIndex].mActionFingerMiddle_Value);
+ SetActionsToWriter(mControllerHand[handIndex].mActionFingerRing_Value);
+ SetActionsToWriter(mControllerHand[handIndex].mActionFingerPinky_Value);
+ SetActionsToWriter(mControllerHand[handIndex].mActionBumper_Pressed);
+
+ nsCString deviceId;
+ VRControllerType contrlType = VRControllerType::_empty;
+ GetControllerDeviceId(deviceType, originInfo.trackedDeviceIndex,
+ deviceId, contrlType);
+ // Controllers should be the same type with one VR display.
+ MOZ_ASSERT(controllerType == contrlType ||
+ controllerType == VRControllerType::_empty);
+ controllerType = contrlType;
+ strncpy(controllerState.controllerName, deviceId.BeginReading(),
+ kVRControllerNameMaxLen);
+ controllerState.numHaptics = kNumOpenVRHaptics;
+ controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer;
+ controllerState.type = controllerType;
+ }
+ controllerPresent[stateIndex] = true;
+ mControllerDeviceIndex[stateIndex] = static_cast<OpenVRHand>(handIndex);
+ ++stateIndex;
+ }
+ }
+
+ // Clear out entries for disconnected controllers
+ for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
+ stateIndex++) {
+ if (!controllerPresent[stateIndex] &&
+ mControllerDeviceIndex[stateIndex] != OpenVRHand::None) {
+ mControllerDeviceIndex[stateIndex] = OpenVRHand::None;
+ memset(&aState.controllerState[stateIndex], 0, sizeof(VRControllerState));
+ }
+ }
+
+ // Create controller mapper
+ if (controllerType != VRControllerType::_empty) {
+ switch (controllerType) {
+ case VRControllerType::HTCVive:
+ mControllerMapper = MakeUnique<OpenVRViveMapper>();
+ break;
+ case VRControllerType::HTCViveCosmos:
+ mControllerMapper = MakeUnique<OpenVRCosmosMapper>();
+ break;
+#if defined(XP_WIN)
+ case VRControllerType::MSMR:
+ mControllerMapper = MakeUnique<OpenVRWMRMapper>();
+ break;
+#endif
+ case VRControllerType::ValveIndex:
+ mControllerMapper = MakeUnique<OpenVRKnucklesMapper>();
+ break;
+ default:
+ mControllerMapper = MakeUnique<OpenVRDefaultMapper>();
+ NS_WARNING("Undefined controller type");
+ break;
+ }
+ }
+}
+
+void OpenVRSession::UpdateControllerButtons(VRSystemState& aState) {
+ MOZ_ASSERT(mVRSystem);
+
+ for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
+ ++stateIndex) {
+ const OpenVRHand role = mControllerDeviceIndex[stateIndex];
+ if (role == OpenVRHand::None) {
+ continue;
+ }
+ VRControllerState& controllerState = aState.controllerState[stateIndex];
+ controllerState.hand = GetControllerHandFromControllerRole(role);
+ mControllerMapper->UpdateButtons(controllerState, mControllerHand[role]);
+ SetControllerSelectionAndSqueezeFrameId(
+ controllerState, aState.displayState.lastSubmittedFrameId);
+ }
+}
+
+void OpenVRSession::UpdateControllerPoses(VRSystemState& aState) {
+ MOZ_ASSERT(mVRSystem);
+
+ for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
+ ++stateIndex) {
+ const OpenVRHand role = mControllerDeviceIndex[stateIndex];
+ if (role == OpenVRHand::None) {
+ continue;
+ }
+ VRControllerState& controllerState = aState.controllerState[stateIndex];
+ vr::InputPoseActionData_t poseData;
+ if (vr::VRInput()->GetPoseActionDataRelativeToNow(
+ mControllerHand[role].mActionPose.handle,
+ vr::TrackingUniverseSeated, 0, &poseData, sizeof(poseData),
+ vr::k_ulInvalidInputValueHandle) != vr::VRInputError_None ||
+ !poseData.bActive || !poseData.pose.bPoseIsValid) {
+ controllerState.isOrientationValid = false;
+ controllerState.isPositionValid = false;
+ } else {
+ const ::vr::TrackedDevicePose_t& pose = poseData.pose;
+ if (pose.bDeviceIsConnected) {
+ controllerState.flags =
+ (dom::GamepadCapabilityFlags::Cap_Orientation |
+ dom::GamepadCapabilityFlags::Cap_Position |
+ dom::GamepadCapabilityFlags::Cap_GripSpacePosition);
+ } else {
+ controllerState.flags = dom::GamepadCapabilityFlags::Cap_None;
+ }
+ if (pose.bPoseIsValid &&
+ pose.eTrackingResult == ::vr::TrackingResult_Running_OK) {
+ gfx::Matrix4x4 m;
+
+ // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
+ // because of its arrangement, we can copy the 12 elements in and
+ // then transpose them to the right place. We do this so we can
+ // pull out a Quaternion.
+ memcpy(&m.components, &pose.mDeviceToAbsoluteTracking,
+ sizeof(pose.mDeviceToAbsoluteTracking));
+ m.Transpose();
+
+ gfx::Quaternion rot;
+ rot.SetFromRotationMatrix(m);
+
+ controllerState.pose.orientation[0] = rot.x;
+ controllerState.pose.orientation[1] = rot.y;
+ controllerState.pose.orientation[2] = rot.z;
+ controllerState.pose.orientation[3] = rot.w;
+ controllerState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0];
+ controllerState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1];
+ controllerState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2];
+ controllerState.pose.angularAcceleration[0] = 0.0f;
+ controllerState.pose.angularAcceleration[1] = 0.0f;
+ controllerState.pose.angularAcceleration[2] = 0.0f;
+ controllerState.isOrientationValid = true;
+
+ controllerState.pose.position[0] = m._41;
+ controllerState.pose.position[1] = m._42;
+ controllerState.pose.position[2] = m._43;
+ controllerState.pose.linearVelocity[0] = pose.vVelocity.v[0];
+ controllerState.pose.linearVelocity[1] = pose.vVelocity.v[1];
+ controllerState.pose.linearVelocity[2] = pose.vVelocity.v[2];
+ controllerState.pose.linearAcceleration[0] = 0.0f;
+ controllerState.pose.linearAcceleration[1] = 0.0f;
+ controllerState.pose.linearAcceleration[2] = 0.0f;
+ controllerState.isPositionValid = true;
+
+ // Calculate its target ray space by shifting degrees in x-axis
+ // for ergonomic.
+ const float kPointerAngleDegrees = -0.698; // 40 degrees.
+ gfx::Matrix4x4 rayMtx(m);
+ rayMtx.RotateX(kPointerAngleDegrees);
+ gfx::Quaternion rayRot;
+ rayRot.SetFromRotationMatrix(rayMtx);
+
+ controllerState.targetRayPose = controllerState.pose;
+ controllerState.targetRayPose.orientation[0] = rayRot.x;
+ controllerState.targetRayPose.orientation[1] = rayRot.y;
+ controllerState.targetRayPose.orientation[2] = rayRot.z;
+ controllerState.targetRayPose.orientation[3] = rayRot.w;
+ controllerState.targetRayPose.position[0] = rayMtx._41;
+ controllerState.targetRayPose.position[1] = rayMtx._42;
+ controllerState.targetRayPose.position[2] = rayMtx._43;
+ }
+ }
+ }
+}
+
+void OpenVRSession::GetControllerDeviceId(
+ ::vr::ETrackedDeviceClass aDeviceType,
+ ::vr::TrackedDeviceIndex_t aDeviceIndex, nsCString& aId,
+ VRControllerType& aControllerType) {
+ switch (aDeviceType) {
+ case ::vr::TrackedDeviceClass_Controller: {
+ ::vr::ETrackedPropertyError err;
+ uint32_t requiredBufferLen;
+ bool isFound = false;
+ char charBuf[128];
+ requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty(
+ aDeviceIndex, ::vr::Prop_RenderModelName_String, charBuf, 128, &err);
+ if (requiredBufferLen > 128) {
+ MOZ_CRASH("Larger than the buffer size.");
+ }
+ MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success);
+ nsCString deviceId(charBuf);
+ if (deviceId.Find("vr_controller_vive") != kNotFound) {
+ aId.AssignLiteral("OpenVR Gamepad");
+ isFound = true;
+ aControllerType = VRControllerType::HTCVive;
+ } else if (deviceId.Find("knuckles") != kNotFound ||
+ deviceId.Find("valve_controller_knu") != kNotFound) {
+ aId.AssignLiteral("OpenVR Knuckles");
+ isFound = true;
+ aControllerType = VRControllerType::ValveIndex;
+ } else if (deviceId.Find("vive_cosmos_controller") != kNotFound) {
+ aId.AssignLiteral("OpenVR Cosmos");
+ isFound = true;
+ aControllerType = VRControllerType::HTCViveCosmos;
+ }
+ if (!isFound) {
+ requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty(
+ aDeviceIndex, ::vr::Prop_SerialNumber_String, charBuf, 128, &err);
+ if (requiredBufferLen > 128) {
+ MOZ_CRASH("Larger than the buffer size.");
+ }
+ MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success);
+ deviceId.Assign(charBuf);
+ if (deviceId.Find("MRSOURCE") != kNotFound) {
+ aId.AssignLiteral("Spatial Controller (Spatial Interaction Source) ");
+ mIsWindowsMR = true;
+ isFound = true;
+ aControllerType = VRControllerType::MSMR;
+ }
+ }
+ if (!isFound) {
+ aId.AssignLiteral("OpenVR Undefined");
+ aControllerType = VRControllerType::_empty;
+ }
+ break;
+ }
+ case ::vr::TrackedDeviceClass_GenericTracker: {
+ aId.AssignLiteral("OpenVR Tracker");
+ aControllerType = VRControllerType::_empty;
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+}
+
+void OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) {
+ UpdateHeadsetPose(aSystemState);
+ UpdateEyeParameters(aSystemState);
+ EnumerateControllers(aSystemState);
+
+ vr::VRActiveActionSet_t actionSet = {0};
+ actionSet.ulActionSet = mActionsetFirefox;
+ vr::VRInput()->UpdateActionState(&actionSet, sizeof(actionSet), 1);
+ UpdateControllerButtons(aSystemState);
+ UpdateControllerPoses(aSystemState);
+ UpdateTelemetry(aSystemState);
+}
+
+void OpenVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {
+ bool isHmdPresent = ::vr::VR_IsHmdPresent();
+ if (!isHmdPresent) {
+ mShouldQuit = true;
+ }
+
+ ::vr::VREvent_t event;
+ while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
+ switch (event.eventType) {
+ case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
+ if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+ aSystemState.displayState.isMounted = true;
+ }
+ break;
+ case ::vr::VREvent_TrackedDeviceUserInteractionEnded:
+ if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+ aSystemState.displayState.isMounted = false;
+ }
+ break;
+ case ::vr::EVREventType::VREvent_TrackedDeviceActivated:
+ if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+ aSystemState.displayState.isConnected = true;
+ }
+ break;
+ case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
+ if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+ aSystemState.displayState.isConnected = false;
+ }
+ break;
+ case ::vr::EVREventType::VREvent_DriverRequestedQuit:
+ case ::vr::EVREventType::VREvent_Quit:
+ // When SteamVR runtime haven't been launched before viewing VR,
+ // SteamVR will send a VREvent_ProcessQuit event. It will tell the parent
+ // process to shutdown the VR process, and we need to avoid it.
+ // case ::vr::EVREventType::VREvent_ProcessQuit:
+ case ::vr::EVREventType::VREvent_QuitAcknowledged:
+ mShouldQuit = true;
+ break;
+ default:
+ // ignore
+ break;
+ }
+ }
+}
+
+#if defined(XP_WIN)
+bool OpenVRSession::SubmitFrame(
+ const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+ ID3D11Texture2D* aTexture) {
+ return SubmitFrame((void*)aTexture, ::vr::ETextureType::TextureType_DirectX,
+ aLayer.leftEyeRect, aLayer.rightEyeRect);
+}
+#elif defined(XP_MACOSX)
+bool OpenVRSession::SubmitFrame(
+ const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+ const VRLayerTextureHandle& aTexture) {
+ return SubmitFrame(aTexture, ::vr::ETextureType::TextureType_IOSurface,
+ aLayer.leftEyeRect, aLayer.rightEyeRect);
+}
+#endif
+
+bool OpenVRSession::SubmitFrame(const VRLayerTextureHandle& aTextureHandle,
+ ::vr::ETextureType aTextureType,
+ const VRLayerEyeRect& aLeftEyeRect,
+ const VRLayerEyeRect& aRightEyeRect) {
+ ::vr::Texture_t tex;
+#if defined(XP_MACOSX)
+ // We get aTextureHandle from get_SurfaceDescriptorMacIOSurface() at
+ // VRDisplayExternal. scaleFactor and opaque are skipped because they always
+ // are 1.0 and false.
+ RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(aTextureHandle);
+ if (!surf) {
+ NS_WARNING("OpenVRSession::SubmitFrame failed to get a MacIOSurface");
+ return false;
+ }
+
+ CFTypeRefPtr<IOSurfaceRef> ioSurface = surf->GetIOSurfaceRef();
+ tex.handle = (void*)ioSurface.get();
+#else
+ tex.handle = aTextureHandle;
+#endif
+ tex.eType = aTextureType;
+ tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
+
+ ::vr::VRTextureBounds_t bounds;
+ bounds.uMin = aLeftEyeRect.x;
+ bounds.vMin = 1.0 - aLeftEyeRect.y;
+ bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width;
+ bounds.vMax = 1.0 - (aLeftEyeRect.y + aLeftEyeRect.height);
+
+ ::vr::EVRCompositorError err;
+ err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds);
+ if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
+ printf_stderr("OpenVR Compositor Submit() failed.\n");
+ }
+
+ bounds.uMin = aRightEyeRect.x;
+ bounds.vMin = 1.0 - aRightEyeRect.y;
+ bounds.uMax = aRightEyeRect.x + aRightEyeRect.width;
+ bounds.vMax = 1.0 - (aRightEyeRect.y + aRightEyeRect.height);
+
+ err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds);
+ if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
+ printf_stderr("OpenVR Compositor Submit() failed.\n");
+ }
+
+ mVRCompositor->PostPresentHandoff();
+ return true;
+}
+
+void OpenVRSession::StopPresentation() {
+ mVRCompositor->ClearLastSubmittedFrame();
+
+ ::vr::Compositor_CumulativeStats stats;
+ mVRCompositor->GetCumulativeStats(&stats,
+ sizeof(::vr::Compositor_CumulativeStats));
+}
+
+bool OpenVRSession::StartPresentation() { return true; }
+
+void OpenVRSession::VibrateHaptic(uint32_t aControllerIdx,
+ uint32_t aHapticIndex, float aIntensity,
+ float aDuration) {
+ MutexAutoLock lock(mControllerHapticStateMutex);
+
+ // Initilize the haptic thread when the first time to do vibration.
+ if (!mHapticThread) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "OpenVRSession::StartHapticThread", [this]() { StartHapticThread(); }));
+ }
+ if (aHapticIndex >= kNumOpenVRHaptics ||
+ aControllerIdx >= kVRControllerMaxCount) {
+ return;
+ }
+
+ const OpenVRHand role = mControllerDeviceIndex[aControllerIdx];
+ if (role == OpenVRHand::None) {
+ return;
+ }
+ mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
+ mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
+}
+
+void OpenVRSession::StartHapticThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHapticThread) {
+ mHapticThread = new VRThread("VR_OpenVR_Haptics"_ns);
+ }
+ mHapticThread->Start();
+ StartHapticTimer();
+}
+
+void OpenVRSession::StopHapticThread() {
+ if (mHapticThread) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "mHapticThread::Shutdown",
+ [thread = mHapticThread]() { thread->Shutdown(); }));
+ mHapticThread = nullptr;
+ }
+}
+
+void OpenVRSession::StartHapticTimer() {
+ if (!mHapticTimer && mHapticThread) {
+ mLastHapticUpdate = TimeStamp();
+ mHapticTimer = NS_NewTimer();
+ nsCOMPtr<nsIThread> thread = mHapticThread->GetThread();
+ mHapticTimer->SetTarget(thread);
+ mHapticTimer->InitWithNamedFuncCallback(
+ HapticTimerCallback, this, kVRHapticUpdateInterval,
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
+ "OpenVRSession::HapticTimerCallback");
+ }
+}
+
+void OpenVRSession::StopHapticTimer() {
+ if (mHapticTimer) {
+ mHapticTimer->Cancel();
+ mHapticTimer = nullptr;
+ }
+}
+
+/*static*/
+void OpenVRSession::HapticTimerCallback(nsITimer* aTimer, void* aClosure) {
+ /**
+ * It is safe to use the pointer passed in aClosure to reference the
+ * OpenVRSession object as the timer is canceled in OpenVRSession::Shutdown,
+ * which is called by the OpenVRSession destructor, guaranteeing
+ * that this function runs if and only if the VRManager object is valid.
+ */
+ OpenVRSession* self = static_cast<OpenVRSession*>(aClosure);
+ MOZ_ASSERT(self);
+ self->UpdateHaptics();
+}
+
+void OpenVRSession::UpdateHaptics() {
+ MOZ_ASSERT(mHapticThread->GetThread() == NS_GetCurrentThread());
+ MOZ_ASSERT(mVRSystem);
+
+ MutexAutoLock lock(mControllerHapticStateMutex);
+
+ TimeStamp now = TimeStamp::Now();
+ if (mLastHapticUpdate.IsNull()) {
+ mLastHapticUpdate = now;
+ return;
+ }
+ float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds();
+ mLastHapticUpdate = now;
+
+ for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount;
+ ++stateIndex) {
+ const OpenVRHand role = mControllerDeviceIndex[stateIndex];
+ if (role == OpenVRHand::None) {
+ continue;
+ }
+ for (uint32_t hapticIdx = 0; hapticIdx < kNumOpenVRHaptics; hapticIdx++) {
+ float intensity = mHapticPulseIntensity[stateIndex][hapticIdx];
+ float duration = mHapticPulseRemaining[stateIndex][hapticIdx];
+ if (duration <= 0.0f || intensity <= 0.0f) {
+ continue;
+ }
+ vr::VRInput()->TriggerHapticVibrationAction(
+ mControllerHand[role].mActionHaptic.handle, 0.0f, deltaTime, 4.0f,
+ intensity > 1.0f ? 1.0f : intensity, vr::k_ulInvalidInputValueHandle);
+
+ duration -= deltaTime;
+ if (duration < 0.0f) {
+ duration = 0.0f;
+ }
+ mHapticPulseRemaining[stateIndex][hapticIdx] = duration;
+ }
+ }
+}
+
+void OpenVRSession::StopVibrateHaptic(uint32_t aControllerIdx) {
+ MutexAutoLock lock(mControllerHapticStateMutex);
+ if (aControllerIdx >= kVRControllerMaxCount) {
+ return;
+ }
+ for (int iHaptic = 0; iHaptic < kNumOpenVRHaptics; iHaptic++) {
+ mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
+ }
+}
+
+void OpenVRSession::StopAllHaptics() {
+ MutexAutoLock lock(mControllerHapticStateMutex);
+ for (auto& controller : mHapticPulseRemaining) {
+ for (auto& haptic : controller) {
+ haptic = 0.0f;
+ }
+ }
+}
+
+void OpenVRSession::UpdateTelemetry(VRSystemState& aSystemState) {
+ ::vr::Compositor_CumulativeStats stats;
+ mVRCompositor->GetCumulativeStats(&stats,
+ sizeof(::vr::Compositor_CumulativeStats));
+ aSystemState.displayState.droppedFrameCount = stats.m_nNumReprojectedFrames;
+}
+
+} // namespace mozilla::gfx