Bug 1299928 - Part 3: Enumerate OpenVR Controllers via VRControllerManagerOpenVR; r?kip draft
authorDaosheng Mu <daoshengmu@gmail.com>
Wed, 21 Sep 2016 19:14:40 +0800
changeset 416077 25a39c3e9d1953a39daec42f452eb43f8e12db31
parent 416076 bca8164ed86f12dd3e803219d93a9240d7c442cf
child 531747 e2104c40d3583eaed7a4cdd4cf0e3b3559f90aea
push id30022
push userbmo:dmu@mozilla.com
push dateWed, 21 Sep 2016 11:29:23 +0000
reviewerskip
bugs1299928
milestone51.0a1
Bug 1299928 - Part 3: Enumerate OpenVR Controllers via VRControllerManagerOpenVR; r?kip MozReview-Commit-ID: GjsogY2TDtZ
dom/gamepad/GamepadManager.cpp
dom/gamepad/ipc/GamepadEventChannelParent.cpp
dom/gamepad/ipc/GamepadEventChannelParent.h
dom/gamepad/ipc/PGamepadEventChannel.ipdl
dom/gamepad/moz.build
dom/gamepad/vr/VRControllerManagerOpenVR.cpp
dom/gamepad/vr/VRControllerManagerOpenVR.h
gfx/vr/moz.build
--- a/dom/gamepad/GamepadManager.cpp
+++ b/dom/gamepad/GamepadManager.cpp
@@ -579,18 +579,35 @@ GamepadManager::ActorCreated(PBackground
   if (NS_WARN_IF(!initedChild)) {
     ActorFailed();
     return;
   }
   MOZ_ASSERT(initedChild == child);
   child->SendGamepadListenerAdded((uint32_t)GamepadChannel::eStandard);
   mChannelChildren.AppendElement(child);
 
-  // TODO: Add more event channels to mChannelChildren if you would
-  // like to support more kinds of devices.
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+  // Add OpenVR gamepad channel
+  nsAdoptingCString openvrPath = Preferences::GetCString("gfx.vr.openvr-runtime");
+  if (!openvrPath)
+    return;
+
+  child = new GamepadEventChannelChild();
+  initedChild =
+	  aActor->SendPGamepadEventChannelConstructor(child);
+  if (NS_WARN_IF(!initedChild)) {
+	  ActorFailed();
+	  return;
+  }
+  MOZ_ASSERT(initedChild == child);
+
+  child->SendRuntimePath(NS_ConvertUTF8toUTF16(openvrPath));
+  child->SendGamepadListenerAdded((uint32_t)GamepadChannel::eOpenVR);
+  mChannelChildren.AppendElement(child);
+#endif
 }
 
 //Override nsIIPCBackgroundChildCreateCallback
 void
 GamepadManager::ActorFailed()
 {
   MOZ_CRASH("Gamepad IPC actor create failed!");
 }
--- a/dom/gamepad/ipc/GamepadEventChannelParent.cpp
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -1,15 +1,16 @@
 /* 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 "GamepadEventChannelParent.h"
 #include "GamepadPlatformService.h"
 #include "mozilla/dom/GamepadMonitoring.h"
 #include "nsThreadUtils.h"
+#include "vr/VRControllerManagerOpenVR.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace mozilla::ipc;
 
 namespace {
 
@@ -56,39 +57,59 @@ GamepadEventChannelParent::RecvGamepadLi
   MOZ_ASSERT(!mHasGamepadListener);
 
   switch (aChannelType) {
     case GamepadChannel::eStandard:
     {
       StartGamepadMonitoring();
       break;
     }
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+    case GamepadChannel::eOpenVR:
+    {
+      RefPtr<VRControllerManager> controllerMgr;
+      controllerMgr = VRContollerManagerOpenVR::Create(mRuntimePath);
+
+      if (!controllerMgr)
+        return false;
+
+      controllerMgr->Startup();
+      mControllers.AppendElement(controllerMgr);
+      break;
+    }
+#endif
     default:
       MOZ_ASSERT(false, "Not support this GamepadMappingType");
       return false;
   }
 
   mHasGamepadListener = true;
   return true;
 }
 
 bool
 GamepadEventChannelParent::RecvGamepadListenerRemoved()
 {
   AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mHasGamepadListener);
   mHasGamepadListener = false;
   RefPtr<GamepadPlatformService> service =
     GamepadPlatformService::GetParentService();
   MOZ_ASSERT(service);
   service->RemoveChannelParent(this);
   Unused << Send__delete__(this);
   return true;
 }
 
+bool
+GamepadEventChannelParent::RecvRuntimePath(const nsString & aPath)
+{
+  mRuntimePath = aPath;
+  return true;
+}
+
 void
 GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   // It may be called because IPDL child side crashed, we'll
   // not receive RecvGamepadListenerRemoved in that case
   if (mHasGamepadListener) {
--- a/dom/gamepad/ipc/GamepadEventChannelParent.h
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -15,23 +15,25 @@ class VRControllerManager;
 class GamepadEventChannelParent final : public PGamepadEventChannelParent
 {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent)
   GamepadEventChannelParent();
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   virtual bool RecvGamepadListenerAdded(const uint32_t& aChannelType) override;
   virtual bool RecvGamepadListenerRemoved() override;
+  virtual bool RecvRuntimePath(const nsString& aPath) override;
   void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
   bool HasGamepadListener() const { return mHasGamepadListener; }
  private:
   ~GamepadEventChannelParent() {}
   bool mHasGamepadListener;
   nsCOMPtr<nsIThread> mBackgroundThread;
 
   typedef nsTArray<RefPtr<VRControllerManager>> VRControllerManagerArray;
   VRControllerManagerArray mControllers;
+  nsString mRuntimePath;
 };
 
 }// namespace dom
 }// namespace mozilla
 
 #endif
--- a/dom/gamepad/ipc/PGamepadEventChannel.ipdl
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -8,15 +8,20 @@ namespace mozilla {
 namespace dom {
 
 async protocol PGamepadEventChannel {
   manager PBackground;
 
   parent:
     async GamepadListenerAdded(uint32_t aChannelType);
     async GamepadListenerRemoved();
+    // Because the path of OpenVR runtime it from Preference
+    // that needs be run on the main thread. This temporary
+    // method is getting the path and sending to PBackground
+    // thread via IPC.
+    async RuntimePath(nsString path);
   child:
     async __delete__();
     async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
 };
 
 }
 }
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -30,17 +30,18 @@ if CONFIG['MOZ_GAMEPAD']:
         'GamepadButton.cpp',
         'GamepadManager.cpp',
         'GamepadMonitoring.cpp',
         'GamepadPlatformService.cpp',
         'GamepadServiceTest.cpp',
         'ipc/GamepadEventChannelChild.cpp',
         'ipc/GamepadEventChannelParent.cpp',
         'ipc/GamepadTestChannelChild.cpp',
-        'ipc/GamepadTestChannelParent.cpp'
+        'ipc/GamepadTestChannelParent.cpp',
+        'vr/VRControllerManagerOpenVR.cpp'
         ]
 
     if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub':
         UNIFIED_SOURCES += [
             'fallback/FallbackGamepad.cpp'
         ]
     elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa':
         UNIFIED_SOURCES += [
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/vr/VRControllerManagerOpenVR.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+* 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 "prlink.h"
+#include "nsString.h"
+#include "mozilla/Preferences.h"
+#include "gfxPrefs.h"
+#include "openvr.h"
+
+#include "VRControllerManagerOpenVR.h"
+
+namespace {
+extern "C" {
+typedef uint32_t(VR_CALLTYPE * pfn_VR_InitInternal)(::vr::HmdError *peError, ::vr::EVRApplicationType eApplicationType);
+typedef void (VR_CALLTYPE * pfn_VR_ShutdownInternal)();
+typedef bool (VR_CALLTYPE * pfn_VR_IsRuntimeInstalled)();
+typedef void * (VR_CALLTYPE * pfn_VR_GetGenericInterface)(const char *pchInterfaceVersion, ::vr::HmdError *peError);
+} // extern "C"
+} // namespace
+
+static pfn_VR_InitInternal vr_InitInternal = nullptr;
+static pfn_VR_ShutdownInternal vr_ShutdownInternal = nullptr;
+static pfn_VR_IsRuntimeInstalled vr_IsRuntimeInstalled = nullptr;
+static pfn_VR_GetGenericInterface vr_GetGenericInterface = nullptr;
+
+namespace mozilla {
+namespace dom {
+
+bool
+LoadOpenVRRuntime(const char* aPath)
+{
+  static PRLibrary *openvrLib = nullptr;
+
+  openvrLib = PR_LoadLibrary(aPath);
+  if (!openvrLib)
+    return false;
+
+#define REQUIRE_FUNCTION(_x) do {                                       \
+  *(void **)&vr_##_x = (void *) PR_FindSymbol(openvrLib, "VR_" #_x);  \
+  if (!vr_##_x) { printf_stderr("VR_" #_x " symbol missing\n"); return false; } \
+  } while (0)
+
+  REQUIRE_FUNCTION(InitInternal);
+  REQUIRE_FUNCTION(ShutdownInternal);
+  REQUIRE_FUNCTION(IsRuntimeInstalled);
+  REQUIRE_FUNCTION(GetGenericInterface);
+
+#undef REQUIRE_FUNCTION
+
+  return true;
+}
+
+class VRControllerServiceRunnable final : public Runnable
+{
+public:
+  explicit VRControllerServiceRunnable(VRControllerManager* aVRControllerManager)
+   : mControllerManager(aVRControllerManager) {}
+
+  NS_IMETHOD Run() override
+  {
+    mControllerManager->HandleInput();
+    NS_DispatchToCurrentThread(new VRControllerServiceRunnable(mControllerManager));
+    return NS_OK;
+  }
+
+private:
+  ~VRControllerServiceRunnable() {}
+
+  VRControllerManager MOZ_NON_OWNING_REF *mControllerManager;
+};
+
+VRContollerManagerOpenVR::VRContollerManagerOpenVR()
+  : mTrackedControllerCount(0), mVRSystem(nullptr)
+{
+}
+
+/*static*/ already_AddRefed<VRContollerManagerOpenVR>
+VRContollerManagerOpenVR::Create(const nsString& aPath)
+{
+  if (!gfxPrefs::VREnabled() || !gfxPrefs::VROpenVREnabled()) {
+    return nullptr;
+  }
+
+  if (!LoadOpenVRRuntime(NS_ConvertUTF16toUTF8(aPath).get())) {
+    return nullptr;
+  }
+
+  RefPtr<VRContollerManagerOpenVR> manager = new VRContollerManagerOpenVR();
+  return manager.forget();
+}
+
+bool
+VRContollerManagerOpenVR::Startup()
+{
+  if (mInstalled)
+    return true;
+
+  if (!vr_IsRuntimeInstalled())
+    return false;
+
+  // Loading the OpenVR Runtime
+  vr::EVRInitError err = vr::VRInitError_None;
+
+  vr_InitInternal(&err, vr::VRApplication_Scene);
+  if (err != vr::VRInitError_None) {
+    return false;
+  }
+
+  mVRSystem = (vr::IVRSystem *)vr_GetGenericInterface(vr::IVRSystem_Version, &err);
+  if ((err != vr::VRInitError_None) || !mVRSystem) {
+    vr_ShutdownInternal();
+    return false;
+  }
+
+  ScanForDevices();
+
+  // Start watching gamepad event in loop
+  NS_NewThread(getter_AddRefs(mMonitorThread), new VRControllerServiceRunnable(this));
+
+  mInstalled = true;
+  return true;
+}
+
+void
+VRContollerManagerOpenVR::Shutdown()
+{
+  mMonitorThread->Shutdown();
+
+  mInstalled = false;
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+
+  if (!service) {
+    return;
+  }
+
+  for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
+    service->RemoveGamepad(mChannel, mGamepads.Get(iter.Key()));
+  }
+
+  mGamepads.Clear();
+}
+
+VRContollerManagerOpenVR::~VRContollerManagerOpenVR()
+{
+  Shutdown();
+}
+
+void
+VRContollerManagerOpenVR::HandleInput()
+{
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+
+  if (!service) {
+    return;
+  }
+
+  // Process SteamVR controller state
+  for (vr::TrackedDeviceIndex_t trackedDevice = 0;
+       trackedDevice < vr::k_unMaxTrackedDeviceCount; trackedDevice++ ) {
+    vr::VRControllerState_t state;
+
+    if (mVRSystem->GetTrackedDeviceClass(trackedDevice)
+        != vr::TrackedDeviceClass_Controller) {
+      continue;
+    }
+
+    if (mVRSystem->GetControllerState(trackedDevice, &state)) {
+      if (state.ulButtonPressed) {
+        // TODO: Convert the button mask to an ID button
+        service->NewButtonEvent(mChannel,
+                                mGamepads.Get(trackedDevice),
+                                0,
+                                true);
+      }
+    }
+  }
+
+  return;
+}
+
+void
+VRContollerManagerOpenVR::ScanForDevices()
+{
+  mTrackedControllerCount = 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;
+    }
+
+    if (mVRSystem->GetTrackedDeviceClass(trackedDevice) != vr::TrackedDeviceClass_Controller) {
+      continue;
+    }
+
+		RefPtr<GamepadPlatformService> service =
+      GamepadPlatformService::GetParentService();
+    MOZ_ASSERT(service);
+
+    mChannel = service->GetCurrentChannelId();
+    uint32_t index = service->AddGamepad(mChannel, "OpenVR Gamepad",
+                                         GamepadMappingType::_empty,
+                                         kOpenVRControllerButtons,
+                                         kOpenVRControllerAxes);
+    mGamepads.Put(trackedDevice, index);
+    ++mTrackedControllerCount;
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/vr/VRControllerManagerOpenVR.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_VRControllerOpenVRManager_h_
+#define mozilla_dom_VRControllerOpenVRManager_h_
+
+// OpenVR Interfaces
+namespace vr {
+class IVRSystem;
+};
+
+namespace mozilla {
+namespace dom {
+
+class VRContollerManagerOpenVR : public VRControllerManager
+{
+public:
+  // TODO: aPath argument can be removed when we can get the correct OpenVR runtime
+  // from the system.
+  static already_AddRefed<VRContollerManagerOpenVR> Create(const nsString& aPath);
+
+  virtual bool Startup() override;
+  virtual void Shutdown() override;
+  virtual void HandleInput() override;
+
+private:
+  VRContollerManagerOpenVR();
+  ~VRContollerManagerOpenVR();
+
+  void ScanForDevices();
+
+  nsCOMPtr<nsIThread> mMonitorThread;
+  // Hashtable for converting tracked devices to gamepad index.
+  nsDataHashtable<nsUint32HashKey, uint32_t> mGamepads;
+
+  int mTrackedControllerCount;
+  uint32_t mChannel;
+  vr::IVRSystem *mVRSystem;
+
+  const uint32_t kOpenVRControllerButtons = 8;
+  const uint32_t kOpenVRControllerAxes = 5;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VRControllerOpenVRManager_h_
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'gfxVR.h',
     'ipc/VRLayerChild.h',
     'ipc/VRManagerChild.h',
     'ipc/VRManagerParent.h',
     'ipc/VRMessageUtils.h',
+    'openvr/openvr.h',
     'VRDisplayClient.h',
     'VRDisplayPresentation.h',
     'VRManager.h',
 ]
 
 LOCAL_INCLUDES += [
     '/gfx/layers/d3d11',
     '/gfx/thebes',