diff options
Diffstat (limited to 'gfx/vr/service/OpenVRSession.cpp')
-rw-r--r-- | gfx/vr/service/OpenVRSession.cpp | 1477 |
1 files changed, 1477 insertions, 0 deletions
diff --git a/gfx/vr/service/OpenVRSession.cpp b/gfx/vr/service/OpenVRSession.cpp new file mode 100644 index 0000000000..b0e3ad4f80 --- /dev/null +++ b/gfx/vr/service/OpenVRSession.cpp @@ -0,0 +1,1477 @@ +/* -*- 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() + : VRSession(), + 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 |