Source code

Revision control

Copy as Markdown

Other Tools

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