mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-27 12:15:33 +00:00
7a3eb1a6a2
We are refactoring much of the code in gfx/vr, moving
most of the code that runs in the VRListenerThread into
it's own process. The remaining code will be non-blocking
once this refactoring is complete.
In order to resolve some shutdown crashes, it is simpler
to remove the VRListenerThread and the related code
starting and stopping this thread. If this is done
prior to completion of the refactoring for Bug 1473399
(Enable VRService thread by default), there would be a
regression in responsiveness during detection of VR
hardware due to blocking API calls moving off the thread.
Differential Revision: https://phabricator.services.mozilla.com/D7227
--HG--
extra : moz-landing-system : lando
1350 lines
45 KiB
C++
1350 lines
45 KiB
C++
/* -*- 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 <math.h>
|
|
|
|
#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(poses, posesSize, nullptr, 0);
|
|
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> runnable =
|
|
NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t,
|
|
StoreCopyPassByConstLRef<VRManagerPromise>>(
|
|
"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();
|
|
CompositorThreadHolder::Loop()->PostTask(
|
|
NewRunnableMethod<StoreCopyPassByConstLRef<VRManagerPromise>>(
|
|
"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> runnable =
|
|
NewRunnableMethod<::vr::IVRSystem*, uint32_t, double, double, uint64_t,
|
|
StoreCopyPassByConstLRef<VRManagerPromise>>(
|
|
"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>
|
|
VRSystemManagerOpenVR::Create()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<VRSystemManagerOpenVR> 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<RefPtr<VRDisplayHost>>& 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<impl::VRControllerOpenVR> 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;
|
|
}
|
|
|
|
if (!HandleAxisMove(i, axisIdx, state.rAxis[j].x)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++axisIdx;
|
|
|
|
if (!HandleAxisMove(i, axisIdx, state.rAxis[j].y * yAxisInvert)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++axisIdx;
|
|
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
::vr::ButtonMaskFromId(
|
|
static_cast<::vr::EVRButtonId>(::vr::k_EButton_Axis0 + j)),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
|
|
if (mIsWindowsMR) {
|
|
axisIdx = (axisIdx == 4) ? 2 : 4;
|
|
buttonIdx = (buttonIdx == 5) ? 1 : 2;
|
|
}
|
|
break;
|
|
case vr::EVRControllerAxisType::k_eControllerAxis_Trigger:
|
|
if (j <= 2) {
|
|
if (!HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].x)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
++triggerIdx;
|
|
} else {
|
|
// For SteamVR Knuckles.
|
|
if (!HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].x)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
++triggerIdx;
|
|
if (!HandleTriggerPress(i, buttonIdx, triggerIdx, state.rAxis[j].y)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++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)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_A),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
}
|
|
if (supportedButtons &
|
|
BTN_MASK_FROM_ID(k_EButton_Grip)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_Grip),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
}
|
|
if (supportedButtons &
|
|
BTN_MASK_FROM_ID(k_EButton_ApplicationMenu)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_ApplicationMenu),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++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)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Left),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
}
|
|
if (supportedButtons &
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Up)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Up),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
}
|
|
if (supportedButtons &
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Right)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Right),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++buttonIdx;
|
|
}
|
|
if (supportedButtons &
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Down)) {
|
|
if (!HandleButtonPress(i, buttonIdx,
|
|
BTN_MASK_FROM_ID(k_EButton_DPad_Down),
|
|
state.ulButtonPressed, state.ulButtonTouched)) {
|
|
RemoveControllers();
|
|
return;
|
|
}
|
|
++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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
VRSystemManagerOpenVR::HandleButtonPress(uint32_t aControllerIdx,
|
|
uint32_t aButton,
|
|
uint64_t aButtonMask,
|
|
uint64_t aButtonPressed,
|
|
uint64_t aButtonTouched)
|
|
{
|
|
RefPtr<impl::VRControllerOpenVR> controller(mOpenVRController[aControllerIdx]);
|
|
MOZ_ASSERT(controller);
|
|
const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed);
|
|
const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
|
|
|
|
if (!pressedDiff && !touchedDiff) {
|
|
return true;
|
|
}
|
|
|
|
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.
|
|
if (MOZ_UNLIKELY(aButton >= controller->GetControllerInfo().GetNumButtons())) {
|
|
// FIXME: Removing crash log for Bug 1488573 to investigate unmatched count.
|
|
MOZ_CRASH_UNSAFE_PRINTF("OpenVR handleButton(aButton = %d, length = %d, controller: %s.)",
|
|
aButton,
|
|
controller->GetControllerInfo().GetNumButtons(),
|
|
controller->GetControllerInfo().GetControllerName());
|
|
return false;
|
|
}
|
|
NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
|
|
aButtonMask & aButtonTouched,
|
|
(aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VRSystemManagerOpenVR::HandleTriggerPress(uint32_t aControllerIdx,
|
|
uint32_t aButton,
|
|
uint32_t aTrigger,
|
|
float aValue)
|
|
{
|
|
RefPtr<impl::VRControllerOpenVR> 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) {
|
|
if (MOZ_UNLIKELY(aButton >= controller->GetControllerInfo().GetNumButtons())) {
|
|
// FIXME: Removing crash log for Bug 1488573 to investigate unmatched count.
|
|
MOZ_CRASH_UNSAFE_PRINTF("OpenVR handleTrigger(aButton = %d, length = %d, controller: %s.)",
|
|
aButton,
|
|
controller->GetControllerInfo().GetNumButtons(),
|
|
controller->GetControllerInfo().GetControllerName());
|
|
return false;
|
|
}
|
|
NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
|
|
aValue > threshold, aValue);
|
|
controller->SetTrigger(aTrigger, aValue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VRSystemManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
|
|
float aValue)
|
|
{
|
|
RefPtr<impl::VRControllerOpenVR> controller(mOpenVRController[aControllerIdx]);
|
|
MOZ_ASSERT(controller);
|
|
|
|
if (controller->GetAxisMove(aAxis) != aValue) {
|
|
if (MOZ_UNLIKELY(aAxis >= controller->GetControllerInfo().GetNumAxes())) {
|
|
// FIXME: Removing crash log for Bug 1488573 to investigate unmatched count.
|
|
MOZ_CRASH_UNSAFE_PRINTF("OpenVR handleAxis(aAxis = %d, length = %d, controller: %s.)",
|
|
aAxis,
|
|
controller->GetControllerInfo().GetNumAxes(),
|
|
controller->GetControllerInfo().GetControllerName());
|
|
return false;
|
|
}
|
|
NewAxisMove(aControllerIdx, aAxis, aValue);
|
|
controller->SetAxisMove(aAxis, aValue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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<impl::VRControllerOpenVR> 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<impl::VRControllerOpenVR> controller = mOpenVRController[aControllerIdx];
|
|
MOZ_ASSERT(controller);
|
|
|
|
controller->StopVibrateHaptic();
|
|
}
|
|
|
|
void
|
|
VRSystemManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& 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::TrackedDeviceProperty>(
|
|
::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<VRControllerOpenVR> 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;
|
|
}
|
|
}
|