/* -*- 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 #include "prlink.h" #include "prenv.h" #include "gfxPrefs.h" #include "mozilla/Preferences.h" #include "mozilla/gfx/Quaternion.h" #ifdef XP_WIN #include "CompositorD3D11.h" #include "TextureD3D11.h" #elif defined(XP_MACOSX) #include "mozilla/gfx/MacIOSurface.h" #endif #include "gfxVROpenVR.h" #include "VRManagerParent.h" #include "VRManager.h" #include "VRThread.h" #include "nsServiceManagerUtils.h" #include "nsIScreenManager.h" #include "mozilla/dom/GamepadEventTypes.h" #include "mozilla/dom/GamepadBinding.h" #include "mozilla/Telemetry.h" #ifndef M_PI # define M_PI 3.14159265358979323846 #endif using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::gfx::impl; using namespace mozilla::layers; using namespace mozilla::dom; #define BTN_MASK_FROM_ID(_id) \ ::vr::ButtonMaskFromId(vr::EVRButtonId::_id) static const uint32_t kNumOpenVRHaptcs = 1; VRDisplayOpenVR::VRDisplayOpenVR(::vr::IVRSystem *aVRSystem, ::vr::IVRChaperone *aVRChaperone, ::vr::IVRCompositor *aVRCompositor) : VRDisplayLocal(VRDeviceType::OpenVR) , mVRSystem(aVRSystem) , mVRChaperone(aVRChaperone) , mVRCompositor(aVRCompositor) , mIsPresenting(false) { MOZ_COUNT_CTOR_INHERITED(VRDisplayOpenVR, VRDisplayLocal); VRDisplayState& state = mDisplayInfo.mDisplayState; strncpy(state.mDisplayName, "OpenVR HMD", kVRDisplayNameMaxLen); state.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd); state.mIsMounted = false; state.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None | VRDisplayCapabilityFlags::Cap_Orientation | VRDisplayCapabilityFlags::Cap_Position | VRDisplayCapabilityFlags::Cap_External | VRDisplayCapabilityFlags::Cap_Present | VRDisplayCapabilityFlags::Cap_StageParameters; mIsHmdPresent = ::vr::VR_IsHmdPresent(); ::vr::ETrackedPropertyError err; bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err); if (err == ::vr::TrackedProp_Success && bHasProximitySensor) { state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_MountDetection; } mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated); uint32_t w, h; mVRSystem->GetRecommendedRenderTargetSize(&w, &h); state.mEyeResolution.width = w; state.mEyeResolution.height = h; // SteamVR gives the application a single FOV to use; it's not configurable as with Oculus for (uint32_t eye = 0; eye < 2; ++eye) { // get l/r/t/b clip plane coordinates float l, r, t, b; mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &l, &r, &t, &b); state.mEyeFOV[eye].SetFromTanRadians(-t, r, b, -l); } UpdateEyeParameters(); UpdateStageParameters(); } VRDisplayOpenVR::~VRDisplayOpenVR() { Destroy(); MOZ_COUNT_DTOR_INHERITED(VRDisplayOpenVR, VRDisplayLocal); } void VRDisplayOpenVR::Destroy() { StopPresentation(); ::vr::VR_Shutdown(); } void VRDisplayOpenVR::UpdateEyeParameters(gfx::Matrix4x4* aHeadToEyeTransforms /* = nullptr */) { // Note this must be called every frame, as the IPD adjustment can be changed // by the user during a VR session. for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) { ::vr::HmdMatrix34_t eyeToHead = mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye)); mDisplayInfo.mDisplayState.mEyeTranslation[eye].x = eyeToHead.m[0][3]; mDisplayInfo.mDisplayState.mEyeTranslation[eye].y = eyeToHead.m[1][3]; mDisplayInfo.mDisplayState.mEyeTranslation[eye].z = eyeToHead.m[2][3]; if (aHeadToEyeTransforms) { 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(); aHeadToEyeTransforms[eye] = pose; } } } void VRDisplayOpenVR::UpdateStageParameters() { VRDisplayState& state = mDisplayInfo.mDisplayState; float sizeX = 0.0f; float sizeZ = 0.0f; if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) { ::vr::HmdMatrix34_t t = mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose(); state.mStageSize.width = sizeX; state.mStageSize.height = sizeZ; state.mSittingToStandingTransform[0] = t.m[0][0]; state.mSittingToStandingTransform[1] = t.m[1][0]; state.mSittingToStandingTransform[2] = t.m[2][0]; state.mSittingToStandingTransform[3] = 0.0f; state.mSittingToStandingTransform[4] = t.m[0][1]; state.mSittingToStandingTransform[5] = t.m[1][1]; state.mSittingToStandingTransform[6] = t.m[2][1]; state.mSittingToStandingTransform[7] = 0.0f; state.mSittingToStandingTransform[8] = t.m[0][2]; state.mSittingToStandingTransform[9] = t.m[1][2]; state.mSittingToStandingTransform[10] = t.m[2][2]; state.mSittingToStandingTransform[11] = 0.0f; state.mSittingToStandingTransform[12] = t.m[0][3]; state.mSittingToStandingTransform[13] = t.m[1][3]; state.mSittingToStandingTransform[14] = t.m[2][3]; state.mSittingToStandingTransform[15] = 1.0f; } else { // If we fail, fall back to reasonable defaults. // 1m x 1m space, 0.75m high in seated position state.mStageSize.width = 1.0f; state.mStageSize.height = 1.0f; state.mSittingToStandingTransform[0] = 1.0f; state.mSittingToStandingTransform[1] = 0.0f; state.mSittingToStandingTransform[2] = 0.0f; state.mSittingToStandingTransform[3] = 0.0f; state.mSittingToStandingTransform[4] = 0.0f; state.mSittingToStandingTransform[5] = 1.0f; state.mSittingToStandingTransform[6] = 0.0f; state.mSittingToStandingTransform[7] = 0.0f; state.mSittingToStandingTransform[8] = 0.0f; state.mSittingToStandingTransform[9] = 0.0f; state.mSittingToStandingTransform[10] = 1.0f; state.mSittingToStandingTransform[11] = 0.0f; state.mSittingToStandingTransform[12] = 0.0f; state.mSittingToStandingTransform[13] = 0.75f; state.mSittingToStandingTransform[14] = 0.0f; state.mSittingToStandingTransform[15] = 1.0f; } } void VRDisplayOpenVR::ZeroSensor() { mVRSystem->ResetSeatedZeroPose(); UpdateStageParameters(); } bool VRDisplayOpenVR::GetIsHmdPresent() { return mIsHmdPresent; } void VRDisplayOpenVR::Refresh() { mIsHmdPresent = ::vr::VR_IsHmdPresent(); ::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) { mDisplayInfo.mDisplayState.mIsMounted = true; } break; case ::vr::VREvent_TrackedDeviceUserInteractionEnded: if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { mDisplayInfo.mDisplayState.mIsMounted = false; } break; case ::vr::EVREventType::VREvent_TrackedDeviceActivated: if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { mDisplayInfo.mDisplayState.mIsConnected = true; } break; case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated: if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { mDisplayInfo.mDisplayState.mIsConnected = false; } break; case ::vr::EVREventType::VREvent_DriverRequestedQuit: case ::vr::EVREventType::VREvent_Quit: case ::vr::EVREventType::VREvent_ProcessQuit: case ::vr::EVREventType::VREvent_QuitAcknowledged: case ::vr::EVREventType::VREvent_QuitAborted_UserPrompt: mIsHmdPresent = false; break; default: // ignore break; } } } VRHMDSensorState VRDisplayOpenVR::GetSensorState() { 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(nullptr, 0, poses, posesSize); gfx::Matrix4x4 headToEyeTransforms[2]; UpdateEyeParameters(headToEyeTransforms); VRHMDSensorState result{}; ::vr::Compositor_FrameTiming timing; timing.m_nSize = sizeof(::vr::Compositor_FrameTiming); if (mVRCompositor->GetFrameTiming(&timing)) { result.timestamp = timing.m_flSystemTimeInSeconds; } else { // This should not happen, but log it just in case NS_WARNING("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); rot.Invert(); result.flags |= VRDisplayCapabilityFlags::Cap_Orientation; result.pose.orientation[0] = rot.x; result.pose.orientation[1] = rot.y; result.pose.orientation[2] = rot.z; result.pose.orientation[3] = rot.w; result.pose.angularVelocity[0] = pose.vAngularVelocity.v[0]; result.pose.angularVelocity[1] = pose.vAngularVelocity.v[1]; result.pose.angularVelocity[2] = pose.vAngularVelocity.v[2]; result.flags |= VRDisplayCapabilityFlags::Cap_Position; result.pose.position[0] = m._41; result.pose.position[1] = m._42; result.pose.position[2] = m._43; result.pose.linearVelocity[0] = pose.vVelocity.v[0]; result.pose.linearVelocity[1] = pose.vVelocity.v[1]; result.pose.linearVelocity[2] = pose.vVelocity.v[2]; } else { // default to an identity quaternion result.pose.orientation[3] = 1.0f; } result.CalcViewMatrices(headToEyeTransforms); result.inputFrameID = mDisplayInfo.mFrameId; return result; } void VRDisplayOpenVR::StartPresentation() { if (mIsPresenting) { return; } mIsPresenting = true; mTelemetry.Clear(); mTelemetry.mPresentationStart = TimeStamp::Now(); ::vr::Compositor_CumulativeStats stats; mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats)); mTelemetry.mLastDroppedFrameCount = stats.m_nNumReprojectedFrames; } void VRDisplayOpenVR::StopPresentation() { if (!mIsPresenting) { return; } mVRCompositor->ClearLastSubmittedFrame(); mIsPresenting = false; const TimeDuration duration = TimeStamp::Now() - mTelemetry.mPresentationStart; Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 2); Telemetry::Accumulate(Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR, duration.ToMilliseconds()); ::vr::Compositor_CumulativeStats stats; mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats)); const uint32_t droppedFramesPerSec = (stats.m_nNumReprojectedFrames - mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds(); Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR, droppedFramesPerSec); } bool VRDisplayOpenVR::SubmitFrameOpenVRHandle(void* aTextureHandle, ::vr::ETextureType aTextureType, const IntSize& aSize, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread()); if (!mIsPresenting) { return false; } ::vr::Texture_t tex; tex.handle = aTextureHandle; 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.XMost(); bounds.vMax = 1.0 - aLeftEyeRect.YMost(); ::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.XMost(); bounds.vMax = 1.0 - aRightEyeRect.YMost(); 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; } #if defined(XP_WIN) bool VRDisplayOpenVR::SubmitFrame(ID3D11Texture2D* aSource, const IntSize& aSize, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { return SubmitFrameOpenVRHandle((void *)aSource, ::vr::ETextureType::TextureType_DirectX, aSize, aLeftEyeRect, aRightEyeRect); } #elif defined(XP_MACOSX) bool VRDisplayOpenVR::SubmitFrame(MacIOSurface* aMacIOSurface, const IntSize& aSize, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { const void* ioSurface = aMacIOSurface->GetIOSurfacePtr(); bool result = false; if (ioSurface == nullptr) { NS_WARNING("VRDisplayOpenVR::SubmitFrame() could not get an IOSurface"); } else { result = SubmitFrameOpenVRHandle((void *)ioSurface, ::vr::ETextureType::TextureType_IOSurface, aSize, aLeftEyeRect, aRightEyeRect); } return result; } #endif VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aDisplayID, uint32_t aNumButtons, uint32_t aNumTriggers, uint32_t aNumAxes, const nsCString& aId) : VRControllerHost(VRDeviceType::OpenVR, aHand, aDisplayID) , mTrackedIndex(0) , mVibrateThread(nullptr) , mIsVibrateStopped(false) { MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost); VRControllerState& state = mControllerInfo.mControllerState; strncpy(state.controllerName, aId.BeginReading(), kVRControllerNameMaxLen); state.numButtons = aNumButtons; state.numAxes = aNumAxes; state.numHaptics = kNumOpenVRHaptcs; } VRControllerOpenVR::~VRControllerOpenVR() { ShutdownVibrateHapticThread(); MOZ_COUNT_DTOR_INHERITED(VRControllerOpenVR, VRControllerHost); } void VRControllerOpenVR::SetTrackedIndex(uint32_t aTrackedIndex) { mTrackedIndex = aTrackedIndex; } uint32_t VRControllerOpenVR::GetTrackedIndex() { return mTrackedIndex; } float VRControllerOpenVR::GetAxisMove(uint32_t aAxis) { return mControllerInfo.mControllerState.axisValue[aAxis]; } void VRControllerOpenVR::SetAxisMove(uint32_t aAxis, float aValue) { mControllerInfo.mControllerState.axisValue[aAxis] = aValue; } void VRControllerOpenVR::SetTrigger(uint32_t aButton, float aValue) { mControllerInfo.mControllerState.triggerValue[aButton] = aValue; } float VRControllerOpenVR::GetTrigger(uint32_t aButton) { return mControllerInfo.mControllerState.triggerValue[aButton]; } void VRControllerOpenVR::SetHand(dom::GamepadHand aHand) { mControllerInfo.mControllerState.hand = aHand; } void VRControllerOpenVR::UpdateVibrateHaptic(::vr::IVRSystem* aVRSystem, uint32_t aHapticIndex, double aIntensity, double aDuration, uint64_t aVibrateIndex, const VRManagerPromise& aPromise) { // UpdateVibrateHaptic() only can be called by mVibrateThread MOZ_ASSERT(mVibrateThread->GetThread() == NS_GetCurrentThread()); // It has been interrupted by loss focus. if (mIsVibrateStopped) { VibrateHapticComplete(aPromise); return; } // Avoid the previous vibrate event to override the new one. if (mVibrateIndex != aVibrateIndex) { VibrateHapticComplete(aPromise); return; } const double duration = (aIntensity == 0) ? 0 : aDuration; // We expect OpenVR to vibrate for 5 ms, but we found it only response the // commend ~ 3.9 ms. For duration time longer than 3.9 ms, we separate them // to a loop of 3.9 ms for make users feel that is a continuous events. const uint32_t microSec = (duration < 3.9 ? duration : 3.9) * 1000 * aIntensity; aVRSystem->TriggerHapticPulse(GetTrackedIndex(), aHapticIndex, microSec); // In OpenVR spec, it mentions TriggerHapticPulse() may not trigger another haptic pulse // on this controller and axis combination for 5ms. const double kVibrateRate = 5.0; if (duration >= kVibrateRate) { MOZ_ASSERT(mVibrateThread); MOZ_ASSERT(mVibrateThread->IsActive()); RefPtr runnable = NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t, StoreCopyPassByConstLRef>( "VRControllerOpenVR::UpdateVibrateHaptic", this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem, aHapticIndex, aIntensity, duration - kVibrateRate, aVibrateIndex, aPromise); mVibrateThread->PostDelayedTask(runnable.forget(), kVibrateRate); } else { // The pulse has completed VibrateHapticComplete(aPromise); } } void VRControllerOpenVR::VibrateHapticComplete(const VRManagerPromise& aPromise) { VRManager *vm = VRManager::Get(); VRListenerThreadHolder::Loop()->PostTask( NewRunnableMethod>( "VRManager::NotifyVibrateHapticCompleted", vm, &VRManager::NotifyVibrateHapticCompleted, aPromise)); } void VRControllerOpenVR::VibrateHaptic(::vr::IVRSystem* aVRSystem, uint32_t aHapticIndex, double aIntensity, double aDuration, const VRManagerPromise& aPromise) { // Spinning up the haptics thread at the first haptics call. if (!mVibrateThread) { mVibrateThread = new VRThread(NS_LITERAL_CSTRING("OpenVR_Vibration")); } mVibrateThread->Start(); ++mVibrateIndex; mIsVibrateStopped = false; RefPtr runnable = NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t, StoreCopyPassByConstLRef>( "VRControllerOpenVR::UpdateVibrateHaptic", this, &VRControllerOpenVR::UpdateVibrateHaptic, aVRSystem, aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise); mVibrateThread->PostTask(runnable.forget()); } void VRControllerOpenVR::StopVibrateHaptic() { mIsVibrateStopped = true; } void VRControllerOpenVR::ShutdownVibrateHapticThread() { StopVibrateHaptic(); if (mVibrateThread) { mVibrateThread->Shutdown(); mVibrateThread = nullptr; } } VRSystemManagerOpenVR::VRSystemManagerOpenVR() : mVRSystem(nullptr) , mRuntimeCheckFailed(false) , mIsWindowsMR(false) { } /*static*/ already_AddRefed VRSystemManagerOpenVR::Create() { MOZ_ASSERT(NS_IsMainThread()); if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) { return nullptr; } RefPtr manager = new VRSystemManagerOpenVR(); return manager.forget(); } void VRSystemManagerOpenVR::Destroy() { Shutdown(); } void VRSystemManagerOpenVR::Shutdown() { if (mOpenVRHMD) { mOpenVRHMD = nullptr; } RemoveControllers(); mVRSystem = nullptr; } void VRSystemManagerOpenVR::NotifyVSync() { VRSystemManager::NotifyVSync(); // Avoid doing anything unless we have already // successfully enumerated and loaded the OpenVR // runtime. if (mVRSystem == nullptr) { return; } if (mOpenVRHMD) { mOpenVRHMD->Refresh(); if (!mOpenVRHMD->GetIsHmdPresent()) { // OpenVR runtime could be quit accidentally // or a device could be disconnected. // We free up resources and must re-initialize // if a device is detected again later. mOpenVRHMD = nullptr; mVRSystem = nullptr; } } } void VRSystemManagerOpenVR::Enumerate() { if (mOpenVRHMD) { // Already enumerated, nothing more to do return; } if (mRuntimeCheckFailed) { // We have already checked for a runtime and // know that its not installed. return; } if (!::vr::VR_IsRuntimeInstalled()) { // Runtime is not installed, remember so we don't // continue to scan for the files mRuntimeCheckFailed = true; return; } if (!::vr::VR_IsHmdPresent()) { // Avoid initializing if no headset is connected return; } ::vr::HmdError err; ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene); if (err) { return; } ::vr::IVRSystem *system = (::vr::IVRSystem *)::vr::VR_GetGenericInterface(::vr::IVRSystem_Version, &err); if (err || !system) { ::vr::VR_Shutdown(); return; } ::vr::IVRChaperone *chaperone = (::vr::IVRChaperone *)::vr::VR_GetGenericInterface(::vr::IVRChaperone_Version, &err); if (err || !chaperone) { ::vr::VR_Shutdown(); return; } ::vr::IVRCompositor *compositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(::vr::IVRCompositor_Version, &err); if (err || !compositor) { ::vr::VR_Shutdown(); return; } mVRSystem = system; mOpenVRHMD = new VRDisplayOpenVR(system, chaperone, compositor); } bool VRSystemManagerOpenVR::ShouldInhibitEnumeration() { if (VRSystemManager::ShouldInhibitEnumeration()) { return true; } if (mOpenVRHMD) { // When we find an a VR device, don't // allow any further enumeration as it // may get picked up redundantly by other // API's. return true; } return false; } void VRSystemManagerOpenVR::GetHMDs(nsTArray>& aHMDResult) { if (mOpenVRHMD) { aHMDResult.AppendElement(mOpenVRHMD); } } bool VRSystemManagerOpenVR::GetIsPresenting() { if (mOpenVRHMD) { VRDisplayInfo displayInfo(mOpenVRHMD->GetDisplayInfo()); return displayInfo.GetPresentingGroups() != kVRGroupNone; } return false; } void VRSystemManagerOpenVR::HandleInput() { // mVRSystem is available after VRDisplay is created // at GetHMDs(). if (!mVRSystem) { return; } RefPtr controller; // Compare with Edge, we have a wrong implementation for the vertical axis value. // In order to not affect the current VR content, we add a workaround for yAxis. const float yAxisInvert = (mIsWindowsMR) ? -1.0f : 1.0f; ::vr::VRControllerState_t state; ::vr::TrackedDevicePose_t poses[::vr::k_unMaxTrackedDeviceCount]; mVRSystem->GetDeviceToAbsoluteTrackingPose(::vr::TrackingUniverseSeated, 0.0f, poses, ::vr::k_unMaxTrackedDeviceCount); // Process OpenVR controller state for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) { uint32_t axisIdx = 0; uint32_t buttonIdx = 0; uint32_t triggerIdx = 0; controller = mOpenVRController[i]; const uint32_t trackedIndex = controller->GetTrackedIndex(); MOZ_ASSERT(mVRSystem->GetTrackedDeviceClass(trackedIndex) == ::vr::TrackedDeviceClass_Controller || mVRSystem->GetTrackedDeviceClass(trackedIndex) == ::vr::TrackedDeviceClass_GenericTracker); // Sometimes, OpenVR controllers are not located by HMD at the initial time. // That makes us have to update the hand info at runtime although switching controllers // to the other hand does not have new changes at the current OpenVR SDK. But, it makes sense // to detect hand changing at runtime. const ::vr::ETrackedControllerRole role = mVRSystem-> GetControllerRoleForTrackedDeviceIndex( trackedIndex); const dom::GamepadHand hand = GetGamepadHandFromControllerRole(role); if (hand != controller->GetHand()) { controller->SetHand(hand); NewHandChangeEvent(i, hand); } if (mVRSystem->GetControllerState(trackedIndex, &state, sizeof(state))) { for (uint32_t j = 0; j < ::vr::k_unControllerStateAxisCount; ++j) { const uint32_t axisType = mVRSystem->GetInt32TrackedDeviceProperty( trackedIndex, static_cast<::vr::TrackedDeviceProperty>( ::vr::Prop_Axis0Type_Int32 + j)); switch (axisType) { case ::vr::EVRControllerAxisType::k_eControllerAxis_Joystick: case ::vr::EVRControllerAxisType::k_eControllerAxis_TrackPad: if (mIsWindowsMR) { // Adjust the input mapping for Windows MR which has // different order. axisIdx = (axisIdx == 0) ? 2 : 0; buttonIdx = (buttonIdx == 0) ? 4 : 0; } HandleAxisMove(i, axisIdx, state.rAxis[j].x); ++axisIdx; HandleAxisMove(i, axisIdx, state.rAxis[j].y * yAxisInvert); ++axisIdx; HandleButtonPress(i, buttonIdx, ::vr::ButtonMaskFromId( static_cast<::vr::EVRButtonId>(::vr::k_EButton_Axis0 + j)), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; if (mIsWindowsMR) { axisIdx = (axisIdx == 4) ? 2 : 4; buttonIdx = (buttonIdx == 5) ? 1 : 2; } break; case vr::EVRControllerAxisType::k_eControllerAxis_Trigger: if (j <= 2) { HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].x); ++buttonIdx; ++triggerIdx; } else { // For SteamVR Knuckles. HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].x); ++buttonIdx; ++triggerIdx; HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].y); ++buttonIdx; ++triggerIdx; } break; } } MOZ_ASSERT(axisIdx == controller->GetControllerInfo().GetNumAxes()); const uint64_t supportedButtons = mVRSystem->GetUint64TrackedDeviceProperty( trackedIndex, ::vr::Prop_SupportedButtons_Uint64); if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_A)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_A), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_Grip)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_Grip), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_ApplicationMenu)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_ApplicationMenu), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (mIsWindowsMR) { // button 4 in Windows MR has already been assigned // to k_eControllerAxis_TrackPad. ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Left)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_DPad_Left), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Up)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_DPad_Up), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Right)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_DPad_Right), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } if (supportedButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Down)) { HandleButtonPress(i, buttonIdx, BTN_MASK_FROM_ID(k_EButton_DPad_Down), state.ulButtonPressed, state.ulButtonTouched); ++buttonIdx; } MOZ_ASSERT(buttonIdx == controller->GetControllerInfo().GetNumButtons()); controller->SetButtonPressed(state.ulButtonPressed); controller->SetButtonTouched(state.ulButtonTouched); // Start to process pose const ::vr::TrackedDevicePose_t& pose = poses[trackedIndex]; GamepadPoseState poseState; if (pose.bDeviceIsConnected) { poseState.flags |= (GamepadCapabilityFlags::Cap_Orientation | GamepadCapabilityFlags::Cap_Position); } 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); rot.Invert(); poseState.orientation[0] = rot.x; poseState.orientation[1] = rot.y; poseState.orientation[2] = rot.z; poseState.orientation[3] = rot.w; poseState.angularVelocity[0] = pose.vAngularVelocity.v[0]; poseState.angularVelocity[1] = pose.vAngularVelocity.v[1]; poseState.angularVelocity[2] = pose.vAngularVelocity.v[2]; poseState.isOrientationValid = true; poseState.position[0] = m._41; poseState.position[1] = m._42; poseState.position[2] = m._43; poseState.linearVelocity[0] = pose.vVelocity.v[0]; poseState.linearVelocity[1] = pose.vVelocity.v[1]; poseState.linearVelocity[2] = pose.vVelocity.v[2]; poseState.isPositionValid = true; } HandlePoseTracking(i, poseState, controller); } } } void VRSystemManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx, uint32_t aButton, uint64_t aButtonMask, uint64_t aButtonPressed, uint64_t aButtonTouched) { RefPtr controller(mOpenVRController[aControllerIdx]); MOZ_ASSERT(controller); const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed); const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched); if (!pressedDiff && !touchedDiff) { return; } if (pressedDiff & aButtonMask || touchedDiff & aButtonMask) { // diff & (aButtonPressed, aButtonTouched) would be true while a new button pressed or // touched event, otherwise it is an old event and needs to notify // the button has been released. NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed, aButtonMask & aButtonTouched, (aButtonMask & aButtonPressed) ? 1.0L : 0.0L); } } void VRSystemManagerOpenVR::HandleTriggerPress(uint32_t aControllerIdx, uint32_t aButton, uint32_t aTrigger, float aValue) { RefPtr controller(mOpenVRController[aControllerIdx]); MOZ_ASSERT(controller); const float oldValue = controller->GetTrigger(aTrigger); // For OpenVR, the threshold value of ButtonPressed and ButtonTouched is 0.55. // We prefer to let developers to set their own threshold for the adjustment. // Therefore, we don't check ButtonPressed and ButtonTouched with ButtonMask here. // we just check the button value is larger than the threshold value or not. const float threshold = gfxPrefs::VRControllerTriggerThreshold(); // Avoid sending duplicated events in IPC channels. if (oldValue != aValue) { NewButtonEvent(aControllerIdx, aButton, aValue > threshold, aValue > threshold, aValue); controller->SetTrigger(aTrigger, aValue); } } void VRSystemManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis, float aValue) { RefPtr controller(mOpenVRController[aControllerIdx]); MOZ_ASSERT(controller); if (controller->GetAxisMove(aAxis) != aValue) { NewAxisMove(aControllerIdx, aAxis, aValue); controller->SetAxisMove(aAxis, aValue); } } void VRSystemManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx, const GamepadPoseState& aPose, VRControllerHost* aController) { MOZ_ASSERT(aController); if (aPose != aController->GetPose()) { aController->SetPose(aPose); NewPoseState(aControllerIdx, aPose); } } dom::GamepadHand VRSystemManagerOpenVR::GetGamepadHandFromControllerRole( ::vr::ETrackedControllerRole aRole) { dom::GamepadHand hand; switch(aRole) { case ::vr::ETrackedControllerRole::TrackedControllerRole_Invalid: case ::vr::ETrackedControllerRole::TrackedControllerRole_OptOut: hand = dom::GamepadHand::_empty; break; case ::vr::ETrackedControllerRole::TrackedControllerRole_LeftHand: hand = dom::GamepadHand::Left; break; case ::vr::ETrackedControllerRole::TrackedControllerRole_RightHand: hand = dom::GamepadHand::Right; break; default: hand = dom::GamepadHand::_empty; MOZ_ASSERT(false); break; } return hand; } void VRSystemManagerOpenVR::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, double aIntensity, double aDuration, const VRManagerPromise& aPromise) { // mVRSystem is available after VRDisplay is created // at GetHMDs(). if (!mVRSystem || (aControllerIdx >= mOpenVRController.Length())) { return; } RefPtr controller = mOpenVRController[aControllerIdx]; MOZ_ASSERT(controller); controller->VibrateHaptic(mVRSystem, aHapticIndex, aIntensity, aDuration, aPromise); } void VRSystemManagerOpenVR::StopVibrateHaptic(uint32_t aControllerIdx) { // mVRSystem is available after VRDisplay is created // at GetHMDs(). if (!mVRSystem || (aControllerIdx >= mOpenVRController.Length())) { return; } RefPtr controller = mOpenVRController[aControllerIdx]; MOZ_ASSERT(controller); controller->StopVibrateHaptic(); } void VRSystemManagerOpenVR::GetControllers(nsTArray>& aControllerResult) { aControllerResult.Clear(); for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) { aControllerResult.AppendElement(mOpenVRController[i]); } } void VRSystemManagerOpenVR::ScanForControllers() { // mVRSystem is available after VRDisplay is created // at GetHMDs(). if (!mVRSystem) { return; } ::vr::TrackedDeviceIndex_t trackedIndexArray[::vr::k_unMaxTrackedDeviceCount]; uint32_t newControllerCount = 0; // Basically, we would have HMDs in the tracked devices, // but we are just interested in the controllers. for (::vr::TrackedDeviceIndex_t trackedDevice = ::vr::k_unTrackedDeviceIndex_Hmd + 1; trackedDevice < ::vr::k_unMaxTrackedDeviceCount; ++trackedDevice) { if (!mVRSystem->IsTrackedDeviceConnected(trackedDevice)) { continue; } const ::vr::ETrackedDeviceClass deviceType = mVRSystem-> GetTrackedDeviceClass(trackedDevice); if (deviceType != ::vr::TrackedDeviceClass_Controller && deviceType != ::vr::TrackedDeviceClass_GenericTracker) { continue; } trackedIndexArray[newControllerCount] = trackedDevice; ++newControllerCount; } if (newControllerCount != mControllerCount) { RemoveControllers(); // Re-adding controllers to VRControllerManager. for (::vr::TrackedDeviceIndex_t i = 0; i < newControllerCount; ++i) { const ::vr::TrackedDeviceIndex_t trackedDevice = trackedIndexArray[i]; const ::vr::ETrackedDeviceClass deviceType = mVRSystem-> GetTrackedDeviceClass(trackedDevice); const ::vr::ETrackedControllerRole role = mVRSystem-> GetControllerRoleForTrackedDeviceIndex( trackedDevice); const GamepadHand hand = GetGamepadHandFromControllerRole(role); uint32_t numButtons = 0; uint32_t numTriggers = 0; uint32_t numAxes = 0; // Scan the axes that the controllers support for (uint32_t j = 0; j < ::vr::k_unControllerStateAxisCount; ++j) { const uint32_t supportAxis = mVRSystem->GetInt32TrackedDeviceProperty(trackedDevice, static_cast( ::vr::Prop_Axis0Type_Int32 + j)); switch (supportAxis) { case ::vr::EVRControllerAxisType::k_eControllerAxis_Joystick: case ::vr::EVRControllerAxisType::k_eControllerAxis_TrackPad: numAxes += 2; // It has x and y axes. ++numButtons; break; case ::vr::k_eControllerAxis_Trigger: if (j <= 2) { ++numButtons; ++numTriggers; } else { #ifdef DEBUG // SteamVR Knuckles is the only special case for using 2D axis values on triggers. ::vr::ETrackedPropertyError err; uint32_t requiredBufferLen; char charBuf[128]; requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty(trackedDevice, ::vr::Prop_RenderModelName_String, charBuf, 128, &err); MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success); nsCString deviceId(charBuf); MOZ_ASSERT(deviceId.Find("knuckles") != kNotFound); #endif // #ifdef DEBUG numButtons += 2; numTriggers += 2; } break; } } // Scan the buttons that the controllers support const uint64_t supportButtons = mVRSystem->GetUint64TrackedDeviceProperty( trackedDevice, ::vr::Prop_SupportedButtons_Uint64); if (supportButtons & BTN_MASK_FROM_ID(k_EButton_A)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_Grip)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_ApplicationMenu)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Left)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Up)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Right)) { ++numButtons; } if (supportButtons & BTN_MASK_FROM_ID(k_EButton_DPad_Down)) { ++numButtons; } nsCString deviceId; GetControllerDeviceId(deviceType, trackedDevice, deviceId); RefPtr openVRController = new VRControllerOpenVR(hand, mOpenVRHMD->GetDisplayInfo().GetDisplayID(), numButtons, numTriggers, numAxes, deviceId); openVRController->SetTrackedIndex(trackedDevice); mOpenVRController.AppendElement(openVRController); // If the Windows MR controller doesn't has the amount // of buttons or axes as our expectation, switching off // the workaround for Windows MR. if (mIsWindowsMR && (numAxes < 4 || numButtons < 5)) { mIsWindowsMR = false; NS_WARNING("OpenVR - Switching off Windows MR mode."); } // Not already present, add it. AddGamepad(openVRController->GetControllerInfo()); ++mControllerCount; } } } void VRSystemManagerOpenVR::RemoveControllers() { // The controller count is changed, removing the existing gamepads first. for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) { mOpenVRController[i]->ShutdownVibrateHapticThread(); RemoveGamepad(i); } mOpenVRController.Clear(); mControllerCount = 0; } void VRSystemManagerOpenVR::GetControllerDeviceId(::vr::ETrackedDeviceClass aDeviceType, ::vr::TrackedDeviceIndex_t aDeviceIndex, nsCString& aId) { 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("knuckles") != kNotFound) { aId.AssignLiteral("OpenVR Knuckles"); isFound = true; } 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; } if (!isFound) { aId.AssignLiteral("OpenVR Gamepad"); } break; } case ::vr::TrackedDeviceClass_GenericTracker: { aId.AssignLiteral("OpenVR Tracker"); break; } default: MOZ_ASSERT(false); break; } }