forked from mirrors/gecko-dev
		
	 d0b35a2ff4
			
		
	
	
		d0b35a2ff4
		
	
	
	
	
		
			
			MozReview-Commit-ID: I1xHfmAPMxT --HG-- extra : rebase_source : 5d957ff6172f3e730b61878b9067ec506ea4a383 extra : source : 66c97a5d22efc6436aab76756759b9b51517e940
		
			
				
	
	
		
			2026 lines
		
	
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2026 lines
		
	
	
	
		
			65 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/. */
 | |
| 
 | |
| #ifndef XP_WIN
 | |
| #error "Oculus 1.3 runtime support only available for Windows"
 | |
| #endif
 | |
| 
 | |
| #include <math.h>
 | |
| 
 | |
| 
 | |
| #include "prenv.h"
 | |
| #include "gfxPrefs.h"
 | |
| #include "nsString.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/SharedLibrary.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "mozilla/gfx/DeviceManagerDx.h"
 | |
| #include "mozilla/layers/CompositorThread.h"
 | |
| #include "ipc/VRLayerParent.h"
 | |
| 
 | |
| #include "mozilla/gfx/Quaternion.h"
 | |
| 
 | |
| #include <d3d11.h>
 | |
| #include "CompositorD3D11.h"
 | |
| #include "TextureD3D11.h"
 | |
| 
 | |
| #include "gfxVROculus.h"
 | |
| #include "VRManagerParent.h"
 | |
| #include "VRThread.h"
 | |
| 
 | |
| #include "mozilla/dom/GamepadEventTypes.h"
 | |
| #include "mozilla/dom/GamepadBinding.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| 
 | |
| /** XXX The DX11 objects and quad blitting could be encapsulated
 | |
|  *    into a separate object if either Oculus starts supporting
 | |
|  *     non-Windows platforms or the blit is needed by other HMD\
 | |
|  *     drivers.
 | |
|  *     Alternately, we could remove the extra blit for
 | |
|  *     Oculus as well with some more refactoring.
 | |
|  */
 | |
| 
 | |
| // See CompositorD3D11Shaders.h
 | |
| namespace mozilla {
 | |
| namespace layers {
 | |
| struct ShaderBytes { const void* mData; size_t mLength; };
 | |
| extern ShaderBytes sRGBShader;
 | |
| extern ShaderBytes sLayerQuadVS;
 | |
| } // namespace layers
 | |
| } // namespace mozilla
 | |
| #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;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| static pfn_ovr_Initialize ovr_Initialize = nullptr;
 | |
| static pfn_ovr_Shutdown ovr_Shutdown = nullptr;
 | |
| static pfn_ovr_GetLastErrorInfo ovr_GetLastErrorInfo = nullptr;
 | |
| static pfn_ovr_GetVersionString ovr_GetVersionString = nullptr;
 | |
| static pfn_ovr_TraceMessage ovr_TraceMessage = nullptr;
 | |
| static pfn_ovr_IdentifyClient ovr_IdentifyClient = nullptr;
 | |
| static pfn_ovr_GetHmdDesc ovr_GetHmdDesc = nullptr;
 | |
| static pfn_ovr_GetTrackerCount ovr_GetTrackerCount = nullptr;
 | |
| static pfn_ovr_GetTrackerDesc ovr_GetTrackerDesc = nullptr;
 | |
| static pfn_ovr_Create ovr_Create = nullptr;
 | |
| static pfn_ovr_Destroy ovr_Destroy = nullptr;
 | |
| static pfn_ovr_GetSessionStatus ovr_GetSessionStatus = nullptr;
 | |
| static pfn_ovr_IsExtensionSupported ovr_IsExtensionSupported = nullptr;
 | |
| static pfn_ovr_EnableExtension ovr_EnableExtension = nullptr;
 | |
| static pfn_ovr_SetTrackingOriginType ovr_SetTrackingOriginType = nullptr;
 | |
| static pfn_ovr_GetTrackingOriginType ovr_GetTrackingOriginType = nullptr;
 | |
| static pfn_ovr_RecenterTrackingOrigin ovr_RecenterTrackingOrigin = nullptr;
 | |
| static pfn_ovr_SpecifyTrackingOrigin ovr_SpecifyTrackingOrigin = nullptr;
 | |
| static pfn_ovr_ClearShouldRecenterFlag ovr_ClearShouldRecenterFlag = nullptr;
 | |
| static pfn_ovr_GetTrackingState ovr_GetTrackingState = nullptr;
 | |
| static pfn_ovr_GetDevicePoses ovr_GetDevicePoses = nullptr;
 | |
| static pfn_ovr_GetTrackerPose ovr_GetTrackerPose = nullptr;
 | |
| static pfn_ovr_GetInputState ovr_GetInputState = nullptr;
 | |
| static pfn_ovr_GetConnectedControllerTypes ovr_GetConnectedControllerTypes = nullptr;
 | |
| static pfn_ovr_GetTouchHapticsDesc ovr_GetTouchHapticsDesc = nullptr;
 | |
| static pfn_ovr_SetControllerVibration ovr_SetControllerVibration = nullptr;
 | |
| static pfn_ovr_SubmitControllerVibration ovr_SubmitControllerVibration = nullptr;
 | |
| static pfn_ovr_GetControllerVibrationState ovr_GetControllerVibrationState = nullptr;
 | |
| static pfn_ovr_TestBoundary ovr_TestBoundary = nullptr;
 | |
| static pfn_ovr_TestBoundaryPoint ovr_TestBoundaryPoint = nullptr;
 | |
| static pfn_ovr_SetBoundaryLookAndFeel ovr_SetBoundaryLookAndFeel = nullptr;
 | |
| static pfn_ovr_ResetBoundaryLookAndFeel ovr_ResetBoundaryLookAndFeel = nullptr;
 | |
| static pfn_ovr_GetBoundaryGeometry ovr_GetBoundaryGeometry = nullptr;
 | |
| static pfn_ovr_GetBoundaryDimensions ovr_GetBoundaryDimensions = nullptr;
 | |
| static pfn_ovr_GetBoundaryVisible ovr_GetBoundaryVisible = nullptr;
 | |
| static pfn_ovr_RequestBoundaryVisible ovr_RequestBoundaryVisible = nullptr;
 | |
| static pfn_ovr_GetTextureSwapChainLength ovr_GetTextureSwapChainLength = nullptr;
 | |
| static pfn_ovr_GetTextureSwapChainCurrentIndex ovr_GetTextureSwapChainCurrentIndex = nullptr;
 | |
| static pfn_ovr_GetTextureSwapChainDesc ovr_GetTextureSwapChainDesc = nullptr;
 | |
| static pfn_ovr_CommitTextureSwapChain ovr_CommitTextureSwapChain = nullptr;
 | |
| static pfn_ovr_DestroyTextureSwapChain ovr_DestroyTextureSwapChain = nullptr;
 | |
| static pfn_ovr_DestroyMirrorTexture ovr_DestroyMirrorTexture = nullptr;
 | |
| static pfn_ovr_GetFovTextureSize ovr_GetFovTextureSize = nullptr;
 | |
| static pfn_ovr_GetRenderDesc2 ovr_GetRenderDesc2 = nullptr;
 | |
| static pfn_ovr_WaitToBeginFrame ovr_WaitToBeginFrame = nullptr;
 | |
| static pfn_ovr_BeginFrame ovr_BeginFrame = nullptr;
 | |
| static pfn_ovr_EndFrame ovr_EndFrame = nullptr;
 | |
| static pfn_ovr_SubmitFrame ovr_SubmitFrame = nullptr;
 | |
| static pfn_ovr_GetPerfStats ovr_GetPerfStats = nullptr;
 | |
| static pfn_ovr_ResetPerfStats ovr_ResetPerfStats = nullptr;
 | |
| static pfn_ovr_GetPredictedDisplayTime ovr_GetPredictedDisplayTime = nullptr;
 | |
| static pfn_ovr_GetTimeInSeconds ovr_GetTimeInSeconds = nullptr;
 | |
| static pfn_ovr_GetBool ovr_GetBool = nullptr;
 | |
| static pfn_ovr_SetBool ovr_SetBool = nullptr;
 | |
| static pfn_ovr_GetInt ovr_GetInt = nullptr;
 | |
| static pfn_ovr_SetInt ovr_SetInt = nullptr;
 | |
| static pfn_ovr_GetFloat ovr_GetFloat = nullptr;
 | |
| static pfn_ovr_SetFloat ovr_SetFloat = nullptr;
 | |
| static pfn_ovr_GetFloatArray ovr_GetFloatArray = nullptr;
 | |
| static pfn_ovr_SetFloatArray ovr_SetFloatArray = nullptr;
 | |
| static pfn_ovr_GetString ovr_GetString = nullptr;
 | |
| static pfn_ovr_SetString ovr_SetString = nullptr;
 | |
| static pfn_ovr_GetExternalCameras ovr_GetExternalCameras = nullptr;
 | |
| static pfn_ovr_SetExternalCameraProperties ovr_SetExternalCameraProperties = nullptr;
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| static pfn_ovr_CreateTextureSwapChainDX ovr_CreateTextureSwapChainDX = nullptr;
 | |
| static pfn_ovr_GetTextureSwapChainBufferDX ovr_GetTextureSwapChainBufferDX = nullptr;
 | |
| static pfn_ovr_CreateMirrorTextureDX ovr_CreateMirrorTextureDX = nullptr;
 | |
| static pfn_ovr_GetMirrorTextureBufferDX ovr_GetMirrorTextureBufferDX = nullptr;
 | |
| #endif
 | |
| 
 | |
| static pfn_ovr_CreateTextureSwapChainGL ovr_CreateTextureSwapChainGL = nullptr;
 | |
| static pfn_ovr_GetTextureSwapChainBufferGL ovr_GetTextureSwapChainBufferGL = nullptr;
 | |
| static pfn_ovr_CreateMirrorTextureGL ovr_CreateMirrorTextureGL = nullptr;
 | |
| static pfn_ovr_GetMirrorTextureBufferGL ovr_GetMirrorTextureBufferGL = nullptr;
 | |
| 
 | |
| #ifdef HAVE_64BIT_BUILD
 | |
| #define BUILD_BITS 64
 | |
| #else
 | |
| #define BUILD_BITS 32
 | |
| #endif
 | |
| 
 | |
| #define OVR_PRODUCT_VERSION 1
 | |
| #define OVR_MAJOR_VERSION   1
 | |
| #define OVR_MINOR_VERSION   19
 | |
| 
 | |
| enum class OculusLeftControllerButtonType : uint16_t {
 | |
|   LThumb,
 | |
|   IndexTrigger,
 | |
|   HandTrigger,
 | |
|   Button_X,
 | |
|   Button_Y,
 | |
|   LThumbRest,
 | |
|   NumButtonType
 | |
| };
 | |
| 
 | |
| enum class OculusRightControllerButtonType : uint16_t {
 | |
|   RThumb,
 | |
|   IndexTrigger,
 | |
|   HandTrigger,
 | |
|   Button_A,
 | |
|   Button_B,
 | |
|   RThumbRest,
 | |
|   NumButtonType
 | |
| };
 | |
| 
 | |
| static const uint32_t kNumOculusButton = static_cast<uint32_t>
 | |
|                                          (OculusLeftControllerButtonType::
 | |
|                                          NumButtonType);
 | |
| static const uint32_t kNumOculusHaptcs = 1;
 | |
| 
 | |
| ovrFovPort
 | |
| ToFovPort(const VRFieldOfView& aFOV)
 | |
| {
 | |
|   ovrFovPort fovPort;
 | |
|   fovPort.LeftTan = tan(aFOV.leftDegrees * M_PI / 180.0);
 | |
|   fovPort.RightTan = tan(aFOV.rightDegrees * M_PI / 180.0);
 | |
|   fovPort.UpTan = tan(aFOV.upDegrees * M_PI / 180.0);
 | |
|   fovPort.DownTan = tan(aFOV.downDegrees * M_PI / 180.0);
 | |
|   return fovPort;
 | |
| }
 | |
| 
 | |
| VRFieldOfView
 | |
| FromFovPort(const ovrFovPort& aFOV)
 | |
| {
 | |
|   VRFieldOfView fovInfo;
 | |
|   fovInfo.leftDegrees = atan(aFOV.LeftTan) * 180.0 / M_PI;
 | |
|   fovInfo.rightDegrees = atan(aFOV.RightTan) * 180.0 / M_PI;
 | |
|   fovInfo.upDegrees = atan(aFOV.UpTan) * 180.0 / M_PI;
 | |
|   fovInfo.downDegrees = atan(aFOV.DownTan) * 180.0 / M_PI;
 | |
|   return fovInfo;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| VROculusSession::VROculusSession()
 | |
|   : mOvrLib(nullptr)
 | |
|   , mSession(nullptr)
 | |
|   , mInitFlags((ovrInitFlags)0)
 | |
|   , mTextureSet(nullptr)
 | |
|   , mRequestPresentation(false)
 | |
|   , mRequestTracking(false)
 | |
|   , mDrawBlack(false)
 | |
|   , mIsConnected(false)
 | |
|   , mIsMounted(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| ovrSession
 | |
| VROculusSession::Get()
 | |
| {
 | |
|   MOZ_ASSERT(mSession);
 | |
|   return mSession;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::IsTrackingReady() const
 | |
| {
 | |
|   // We should return true only if the HMD is connected and we
 | |
|   // are ready for tracking
 | |
|   MOZ_ASSERT(!mIsConnected || mSession);
 | |
|   return mIsConnected;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::IsPresentationReady() const
 | |
| {
 | |
|   return !mRenderTargets.IsEmpty();
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::IsMounted() const
 | |
| {
 | |
|   return mIsMounted;
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StopTracking()
 | |
| {
 | |
|   if (mRequestTracking) {
 | |
|     mRequestTracking = false;
 | |
|     Refresh();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StartTracking()
 | |
| {
 | |
|   if (!mRequestTracking) {
 | |
|     mRequestTracking = true;
 | |
|     Refresh();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StartPresentation(const IntSize& aSize)
 | |
| {
 | |
|   if (!mRequestPresentation) {
 | |
|     mRequestPresentation = true;
 | |
|     mTelemetry.Clear();
 | |
|     mTelemetry.mPresentationStart = TimeStamp::Now();
 | |
| 
 | |
|     ovrPerfStats perfStats;
 | |
|     if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) {
 | |
|       if (perfStats.FrameStatsCount) {
 | |
|         mTelemetry.mLastDroppedFrameCount = perfStats.FrameStats[0].AppDroppedFrameCount;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Update the size, even when we are already presenting.
 | |
|   mPresentationSize = aSize;
 | |
|   Refresh();
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StopPresentation()
 | |
| {
 | |
|   if (mRequestPresentation) {
 | |
|     mLastPresentationEnd = TimeStamp::Now();
 | |
|     mRequestPresentation = false;
 | |
| 
 | |
|     const TimeDuration duration = mLastPresentationEnd - mTelemetry.mPresentationStart;
 | |
|     Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 1);
 | |
|     Telemetry::Accumulate(Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS,
 | |
|                           duration.ToMilliseconds());
 | |
| 
 | |
|     if (mTelemetry.IsLastDroppedFrameValid() && duration.ToSeconds()) {
 | |
|       ovrPerfStats perfStats;
 | |
|       if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) {
 | |
|         if (perfStats.FrameStatsCount) {
 | |
|           const uint32_t droppedFramesPerSec = (perfStats.FrameStats[0].AppDroppedFrameCount -
 | |
|                                                 mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds();
 | |
|           Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS, droppedFramesPerSec);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     Refresh();
 | |
|   }
 | |
| }
 | |
| 
 | |
| VROculusSession::~VROculusSession()
 | |
| {
 | |
|   mSubmitThread = nullptr;
 | |
|   Uninitialize();
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::Uninitialize()
 | |
| {
 | |
|   StopRendering();
 | |
|   StopSession();
 | |
|   StopLib();
 | |
|   UnloadOvrLib();
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StopRendering()
 | |
| {
 | |
|   if (!mRenderTargets.IsEmpty()) {
 | |
|     mRenderTargets.Clear();
 | |
|   }
 | |
|   if (mTextureSet && mSession) {
 | |
|     ovr_DestroyTextureSwapChain(mSession, mTextureSet);
 | |
|   }
 | |
|   mTextureSet = nullptr;
 | |
|   mDevice = nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StopSession()
 | |
| {
 | |
|   if (mSession) {
 | |
|     ovr_Destroy(mSession);
 | |
|     mIsConnected = false;
 | |
|     mIsMounted = false;
 | |
|     mSession = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::StopLib()
 | |
| {
 | |
|   if (mInitFlags) {
 | |
|     ovr_Shutdown();
 | |
|     mInitFlags = (ovrInitFlags)0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::Refresh(bool aForceRefresh)
 | |
| {
 | |
|   // We are waiting for drawing the black layer command for
 | |
|   // Compositor thread. Ignore Refresh() calls from other threads.
 | |
|   if (mDrawBlack && !aForceRefresh) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mRequestTracking) {
 | |
|     Uninitialize();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ovrInitFlags flags = (ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering);
 | |
|   bool bInvisible = true;
 | |
|   if (!gfxPrefs::VROculusInvisibleEnabled()) {
 | |
|     bInvisible = false;
 | |
|   }
 | |
|   if (mRequestPresentation) {
 | |
|     bInvisible = false;
 | |
|   } else if (!mLastPresentationEnd.IsNull()) {
 | |
|     TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd;
 | |
|     TimeDuration timeout = TimeDuration::FromMilliseconds(gfxPrefs::VROculusPresentTimeout());
 | |
|     if (timeout > TimeDuration(0) && duration < timeout) {
 | |
|       // Do not immediately re-initialize with an invisible session after
 | |
|       // the end of a VR presentation.  Waiting for the configured duraction
 | |
|       // ensures that the user will not drop to Oculus Home during VR link
 | |
|       // traversal.
 | |
|       bInvisible = false;
 | |
| 
 | |
|       // While we are waiting for either the timeout or a new presentation,
 | |
|       // fill the HMD with black / no layers.
 | |
|       if (mSession && mTextureSet) {
 | |
|         if (!aForceRefresh) {
 | |
|           // VROculusSession didn't start submitting frames yet.
 | |
|           // Or, the VR thread has been shut down already.
 | |
|           if (!mSubmitThread || !mSubmitThread->IsActive()) {
 | |
|             return;
 | |
|           }
 | |
|           // ovr_SubmitFrame is running at VR Submit thread,
 | |
|           // so we post this task to VR Submit thread and let it paint
 | |
|           // a black frame.
 | |
|           mDrawBlack = true;
 | |
|           mSubmitThread->PostTask(NewRunnableMethod<bool>(
 | |
|             "gfx::VROculusSession::Refresh",
 | |
|             this,
 | |
|             &VROculusSession::Refresh, true));
 | |
|           return;
 | |
|         }
 | |
|         ovrLayerEyeFov layer;
 | |
|         memset(&layer, 0, sizeof(layer));
 | |
|         layer.Header.Type = ovrLayerType_Disabled;
 | |
|         ovrLayerHeader *layers = &layer.Header;
 | |
|         ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1);
 | |
|         mDrawBlack = false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (bInvisible) {
 | |
|     flags = (ovrInitFlags)(flags | ovrInit_Invisible);
 | |
|   }
 | |
| 
 | |
|   if (mInitFlags != flags) {
 | |
|     Uninitialize();
 | |
|   }
 | |
| 
 | |
|   if(!Initialize(flags)) {
 | |
|     // If we fail to initialize, ensure the Oculus libraries
 | |
|     // are unloaded, as we can't poll for ovrSessionStatus::ShouldQuit
 | |
|     // without an active ovrSession.
 | |
|     Uninitialize();
 | |
|   }
 | |
| 
 | |
|   if (mSession) {
 | |
|     ovrSessionStatus status;
 | |
|     if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) {
 | |
|       mIsConnected = status.HmdPresent;
 | |
|       mIsMounted = status.HmdMounted;
 | |
|       if (status.ShouldQuit) {
 | |
|         mLastShouldQuit = TimeStamp::Now();
 | |
|         Uninitialize();
 | |
|       }
 | |
|     } else {
 | |
|       mIsConnected = false;
 | |
|       mIsMounted = false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::IsQuitTimeoutActive()
 | |
| {
 | |
|   // If Oculus asked us to quit our session, do not try to initialize again
 | |
|   // immediately.
 | |
|   if (!mLastShouldQuit.IsNull()) {
 | |
|     TimeDuration duration = TimeStamp::Now() - mLastShouldQuit;
 | |
|     TimeDuration timeout = TimeDuration::FromMilliseconds(gfxPrefs::VROculusQuitTimeout());
 | |
|     if (timeout > TimeDuration(0) && duration < timeout) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::Initialize(ovrInitFlags aFlags)
 | |
| {
 | |
|   if (IsQuitTimeoutActive()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!LoadOvrLib()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (!StartLib(aFlags)) {
 | |
|     return false;
 | |
|   }
 | |
|   if (!StartSession()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (!StartRendering()) {
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::StartRendering()
 | |
| {
 | |
|   if (!mRequestPresentation) {
 | |
|     // Nothing to do if we aren't presenting
 | |
|     return true;
 | |
|   }
 | |
|   if (!mDevice) {
 | |
|     mDevice = gfx::DeviceManagerDx::Get()->GetVRDevice();
 | |
|     if (!mDevice) {
 | |
|       NS_WARNING("Failed to get a D3D11Device for Oculus");
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mTextureSet) {
 | |
|     /**
 | |
|     * The presentation format is determined by content, which describes the
 | |
|     * left and right eye rectangles in the VRLayer.  The default, if no
 | |
|     * coordinates are passed is to place the left and right eye textures
 | |
|     * side-by-side within the buffer.
 | |
|     *
 | |
|     * XXX - An optimization would be to dynamically resize this buffer
 | |
|     *       to accomodate sites that are choosing to render in a lower
 | |
|     *       resolution or are using space outside of the left and right
 | |
|     *       eye textures for other purposes.  (Bug 1291443)
 | |
|     */
 | |
| 
 | |
|     ovrTextureSwapChainDesc desc;
 | |
|     memset(&desc, 0, sizeof(desc));
 | |
|     desc.Type = ovrTexture_2D;
 | |
|     desc.ArraySize = 1;
 | |
|     desc.Format = OVR_FORMAT_B8G8R8A8_UNORM_SRGB;
 | |
|     desc.Width = mPresentationSize.width;
 | |
|     desc.Height = mPresentationSize.height;
 | |
|     desc.MipLevels = 1;
 | |
|     desc.SampleCount = 1;
 | |
|     desc.StaticImage = false;
 | |
|     desc.MiscFlags = ovrTextureMisc_DX_Typeless;
 | |
|     desc.BindFlags = ovrTextureBind_DX_RenderTarget;
 | |
| 
 | |
|     ovrResult orv = ovr_CreateTextureSwapChainDX(mSession, mDevice, &desc, &mTextureSet);
 | |
|     if (orv != ovrSuccess) {
 | |
|       NS_WARNING("ovr_CreateTextureSwapChainDX failed");
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mTextureSet && mRenderTargets.IsEmpty()) {
 | |
|     int textureCount = 0;
 | |
|     ovrResult orv = ovr_GetTextureSwapChainLength(mSession, mTextureSet, &textureCount);
 | |
|     if (orv != ovrSuccess) {
 | |
|       NS_WARNING("ovr_GetTextureSwapChainLength failed");
 | |
|       return false;
 | |
|     }
 | |
|     mRenderTargets.SetLength(textureCount);
 | |
|     for (int i = 0; i < textureCount; ++i) {
 | |
|       RefPtr<CompositingRenderTargetD3D11> rt;
 | |
|       ID3D11Texture2D* texture = nullptr;
 | |
|       orv = ovr_GetTextureSwapChainBufferDX(mSession, mTextureSet, i, IID_PPV_ARGS(&texture));
 | |
|       MOZ_ASSERT(orv == ovrSuccess, "ovr_GetTextureSwapChainBufferDX failed.");
 | |
|       rt = new CompositingRenderTargetD3D11(texture, IntPoint(0, 0), DXGI_FORMAT_B8G8R8A8_UNORM);
 | |
|       rt->SetSize(mPresentationSize);
 | |
|       mRenderTargets[i] = rt;
 | |
|       texture->Release();
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::StartLib(ovrInitFlags aFlags)
 | |
| {
 | |
|   if (mInitFlags == 0) {
 | |
|     ovrInitParams params;
 | |
|     memset(¶ms, 0, sizeof(params));
 | |
|     params.Flags = aFlags;
 | |
|     params.RequestedMinorVersion = OVR_MINOR_VERSION;
 | |
|     params.LogCallback = nullptr;
 | |
|     params.ConnectionTimeoutMS = 0;
 | |
| 
 | |
|     ovrResult orv = ovr_Initialize(¶ms);
 | |
| 
 | |
|     if (orv == ovrSuccess) {
 | |
|       mInitFlags = aFlags;
 | |
|     }
 | |
|     else {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   MOZ_ASSERT(mInitFlags == aFlags);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::StartSession()
 | |
| {
 | |
|   // ovr_Create can be slow when no HMD is present and we wish
 | |
|   // to keep the same oculus session when possible, so we detect
 | |
|   // presence of an HMD with ovr_GetHmdDesc before calling ovr_Create
 | |
|   ovrHmdDesc desc = ovr_GetHmdDesc(NULL);
 | |
|   if (desc.Type == ovrHmd_None) {
 | |
|     // No HMD connected, destroy any existing session
 | |
|     if (mSession) {
 | |
|       ovr_Destroy(mSession);
 | |
|       mSession = nullptr;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
|   if (mSession != nullptr) {
 | |
|     // HMD Detected and we already have a session, let's keep using it.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // HMD Detected and we don't have a session yet,
 | |
|   // try to create a new session
 | |
|   ovrSession session;
 | |
|   ovrGraphicsLuid luid;
 | |
|   ovrResult orv = ovr_Create(&session, &luid);
 | |
|   if (orv == ovrSuccess) {
 | |
|     orv = ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel);
 | |
|     if (orv != ovrSuccess) {
 | |
|       NS_WARNING("ovr_SetTrackingOriginType failed.\n");
 | |
|     }
 | |
|     mSession = session;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Failed to create a session for the HMD
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VROculusSession::LoadOvrLib()
 | |
| {
 | |
|   if (mOvrLib) {
 | |
|     // Already loaded, early exit
 | |
|     return true;
 | |
|   }
 | |
| #if defined(_WIN32)
 | |
|   nsTArray<nsString> libSearchPaths;
 | |
|   nsString libName;
 | |
|   nsString searchPath;
 | |
| 
 | |
|   static const char dirSep = '\\';
 | |
|   static const int pathLen = 260;
 | |
|   searchPath.SetCapacity(pathLen);
 | |
|   int realLen = ::GetSystemDirectoryW(char16ptr_t(searchPath.BeginWriting()),
 | |
|                                       pathLen);
 | |
|   if (realLen != 0 && realLen < pathLen) {
 | |
|     searchPath.SetLength(realLen);
 | |
|     libSearchPaths.AppendElement(searchPath);
 | |
|   }
 | |
|   libName.AppendPrintf("LibOVRRT%d_%d.dll", BUILD_BITS, OVR_PRODUCT_VERSION);
 | |
| 
 | |
|   // search the path/module dir
 | |
|   libSearchPaths.InsertElementsAt(0, 1, EmptyString());
 | |
| 
 | |
|   // If the env var is present, we override libName
 | |
|   if (_wgetenv(L"OVR_LIB_PATH")) {
 | |
|     searchPath = _wgetenv(L"OVR_LIB_PATH");
 | |
|     libSearchPaths.InsertElementsAt(0, 1, searchPath);
 | |
|   }
 | |
| 
 | |
|   if (_wgetenv(L"OVR_LIB_NAME")) {
 | |
|     libName = _wgetenv(L"OVR_LIB_NAME");
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) {
 | |
|     nsString& libPath = libSearchPaths[i];
 | |
|     nsString fullName;
 | |
|     if (libPath.Length() == 0) {
 | |
|       fullName.Assign(libName);
 | |
|     } else {
 | |
|       fullName.AppendPrintf("%s%c%s", libPath.get(), dirSep, libName.get());
 | |
|     }
 | |
| 
 | |
|     mOvrLib = LoadLibraryWithFlags(fullName.get());
 | |
|     if (mOvrLib) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| #else
 | |
| #error "Unsupported platform!"
 | |
| #endif
 | |
| 
 | |
|   if (!mOvrLib) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
| #define REQUIRE_FUNCTION(_x) do { \
 | |
|     *(void **)&_x = (void *) PR_FindSymbol(mOvrLib, #_x);                \
 | |
|     if (!_x) { printf_stderr(#_x " symbol missing\n"); goto fail; }       \
 | |
|   } while (0)
 | |
| 
 | |
|   REQUIRE_FUNCTION(ovr_Initialize);
 | |
|   REQUIRE_FUNCTION(ovr_Shutdown);
 | |
|   REQUIRE_FUNCTION(ovr_GetLastErrorInfo);
 | |
|   REQUIRE_FUNCTION(ovr_GetVersionString);
 | |
|   REQUIRE_FUNCTION(ovr_TraceMessage);
 | |
|   REQUIRE_FUNCTION(ovr_IdentifyClient);
 | |
|   REQUIRE_FUNCTION(ovr_GetHmdDesc);
 | |
|   REQUIRE_FUNCTION(ovr_GetTrackerCount);
 | |
|   REQUIRE_FUNCTION(ovr_GetTrackerDesc);
 | |
|   REQUIRE_FUNCTION(ovr_Create);
 | |
|   REQUIRE_FUNCTION(ovr_Destroy);
 | |
|   REQUIRE_FUNCTION(ovr_GetSessionStatus);
 | |
|   REQUIRE_FUNCTION(ovr_IsExtensionSupported);
 | |
|   REQUIRE_FUNCTION(ovr_EnableExtension);
 | |
|   REQUIRE_FUNCTION(ovr_SetTrackingOriginType);
 | |
|   REQUIRE_FUNCTION(ovr_GetTrackingOriginType);
 | |
|   REQUIRE_FUNCTION(ovr_RecenterTrackingOrigin);
 | |
|   REQUIRE_FUNCTION(ovr_SpecifyTrackingOrigin);
 | |
|   REQUIRE_FUNCTION(ovr_ClearShouldRecenterFlag);
 | |
|   REQUIRE_FUNCTION(ovr_GetTrackingState);
 | |
|   REQUIRE_FUNCTION(ovr_GetDevicePoses);
 | |
|   REQUIRE_FUNCTION(ovr_GetTrackerPose);
 | |
|   REQUIRE_FUNCTION(ovr_GetInputState);
 | |
|   REQUIRE_FUNCTION(ovr_GetConnectedControllerTypes);
 | |
|   REQUIRE_FUNCTION(ovr_GetTouchHapticsDesc);
 | |
|   REQUIRE_FUNCTION(ovr_SetControllerVibration);
 | |
|   REQUIRE_FUNCTION(ovr_SubmitControllerVibration);
 | |
|   REQUIRE_FUNCTION(ovr_GetControllerVibrationState);
 | |
|   REQUIRE_FUNCTION(ovr_TestBoundary);
 | |
|   REQUIRE_FUNCTION(ovr_TestBoundaryPoint);
 | |
|   REQUIRE_FUNCTION(ovr_SetBoundaryLookAndFeel);
 | |
|   REQUIRE_FUNCTION(ovr_ResetBoundaryLookAndFeel);
 | |
|   REQUIRE_FUNCTION(ovr_GetBoundaryGeometry);
 | |
|   REQUIRE_FUNCTION(ovr_GetBoundaryDimensions);
 | |
|   REQUIRE_FUNCTION(ovr_GetBoundaryVisible);
 | |
|   REQUIRE_FUNCTION(ovr_RequestBoundaryVisible);
 | |
|   REQUIRE_FUNCTION(ovr_GetTextureSwapChainLength);
 | |
|   REQUIRE_FUNCTION(ovr_GetTextureSwapChainCurrentIndex);
 | |
|   REQUIRE_FUNCTION(ovr_GetTextureSwapChainDesc);
 | |
|   REQUIRE_FUNCTION(ovr_CommitTextureSwapChain);
 | |
|   REQUIRE_FUNCTION(ovr_DestroyTextureSwapChain);
 | |
|   REQUIRE_FUNCTION(ovr_DestroyMirrorTexture);
 | |
|   REQUIRE_FUNCTION(ovr_GetFovTextureSize);
 | |
|   REQUIRE_FUNCTION(ovr_GetRenderDesc2);
 | |
|   REQUIRE_FUNCTION(ovr_WaitToBeginFrame);
 | |
|   REQUIRE_FUNCTION(ovr_BeginFrame);
 | |
|   REQUIRE_FUNCTION(ovr_EndFrame);
 | |
|   REQUIRE_FUNCTION(ovr_SubmitFrame);
 | |
|   REQUIRE_FUNCTION(ovr_GetPerfStats);
 | |
|   REQUIRE_FUNCTION(ovr_ResetPerfStats);
 | |
|   REQUIRE_FUNCTION(ovr_GetPredictedDisplayTime);
 | |
|   REQUIRE_FUNCTION(ovr_GetTimeInSeconds);
 | |
|   REQUIRE_FUNCTION(ovr_GetBool);
 | |
|   REQUIRE_FUNCTION(ovr_SetBool);
 | |
|   REQUIRE_FUNCTION(ovr_GetInt);
 | |
|   REQUIRE_FUNCTION(ovr_SetInt);
 | |
|   REQUIRE_FUNCTION(ovr_GetFloat);
 | |
|   REQUIRE_FUNCTION(ovr_SetFloat);
 | |
|   REQUIRE_FUNCTION(ovr_GetFloatArray);
 | |
|   REQUIRE_FUNCTION(ovr_SetFloatArray);
 | |
|   REQUIRE_FUNCTION(ovr_GetString);
 | |
|   REQUIRE_FUNCTION(ovr_SetString);
 | |
|   REQUIRE_FUNCTION(ovr_GetExternalCameras);
 | |
|   REQUIRE_FUNCTION(ovr_SetExternalCameraProperties);
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| 
 | |
|   REQUIRE_FUNCTION(ovr_CreateTextureSwapChainDX);
 | |
|   REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferDX);
 | |
|   REQUIRE_FUNCTION(ovr_CreateMirrorTextureDX);
 | |
|   REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferDX);
 | |
| 
 | |
| #endif
 | |
| 
 | |
|   REQUIRE_FUNCTION(ovr_CreateTextureSwapChainGL);
 | |
|   REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferGL);
 | |
|   REQUIRE_FUNCTION(ovr_CreateMirrorTextureGL);
 | |
|   REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferGL);
 | |
| 
 | |
| #undef REQUIRE_FUNCTION
 | |
| 
 | |
|   return true;
 | |
| 
 | |
|  fail:
 | |
|   ovr_Initialize = nullptr;
 | |
|   PR_UnloadLibrary(mOvrLib);
 | |
|   mOvrLib = nullptr;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| already_AddRefed<CompositingRenderTargetD3D11>
 | |
| VROculusSession::GetNextRenderTarget()
 | |
| {
 | |
|   int currentRenderTarget = 0;
 | |
|   DebugOnly<ovrResult> orv = ovr_GetTextureSwapChainCurrentIndex(mSession, mTextureSet, ¤tRenderTarget);
 | |
|   MOZ_ASSERT(orv == ovrSuccess, "ovr_GetTextureSwapChainCurrentIndex failed.");
 | |
| 
 | |
|   mRenderTargets[currentRenderTarget]->ClearOnBind();
 | |
|   RefPtr<CompositingRenderTargetD3D11> rt = mRenderTargets[currentRenderTarget];
 | |
|   return rt.forget();
 | |
| }
 | |
| 
 | |
| ovrTextureSwapChain
 | |
| VROculusSession::GetSwapChain()
 | |
| {
 | |
|   MOZ_ASSERT(mTextureSet);
 | |
|   return mTextureSet;
 | |
| }
 | |
| 
 | |
| void
 | |
| VROculusSession::UnloadOvrLib()
 | |
| {
 | |
|   if (mOvrLib) {
 | |
|     PR_UnloadLibrary(mOvrLib);
 | |
|     mOvrLib = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| VRDisplayOculus::VRDisplayOculus(VROculusSession* aSession)
 | |
|   : VRDisplayLocal(VRDeviceType::Oculus)
 | |
|   , mSession(aSession)
 | |
|   , mQuadVS(nullptr)
 | |
|   , mQuadPS(nullptr)
 | |
|   , mLinearSamplerState(nullptr)
 | |
|   , mVSConstantBuffer(nullptr)
 | |
|   , mPSConstantBuffer(nullptr)
 | |
|   , mVertexBuffer(nullptr)
 | |
|   , mInputLayout(nullptr)
 | |
|   , mEyeHeight(OVR_DEFAULT_EYE_HEIGHT)
 | |
| {
 | |
|   MOZ_COUNT_CTOR_INHERITED(VRDisplayOculus, VRDisplayLocal);
 | |
|   VRDisplayState& state = mDisplayInfo.mDisplayState;
 | |
|   strncpy(state.mDisplayName, "Oculus VR HMD", kVRDisplayNameMaxLen);
 | |
|   state.mIsConnected = true;
 | |
|   state.mIsMounted = false;
 | |
| 
 | |
|   mDesc = ovr_GetHmdDesc(aSession->Get());
 | |
| 
 | |
|   state.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None;
 | |
|   if (mDesc.AvailableTrackingCaps & ovrTrackingCap_Orientation) {
 | |
|     state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_Orientation;
 | |
|     state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
 | |
|   }
 | |
|   if (mDesc.AvailableTrackingCaps & ovrTrackingCap_Position) {
 | |
|     state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_Position;
 | |
|     state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration;
 | |
|     state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_StageParameters;
 | |
|   }
 | |
|   state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_External;
 | |
|   state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_MountDetection;
 | |
|   state.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_Present;
 | |
| 
 | |
|   mFOVPort[VRDisplayState::Eye_Left] = mDesc.DefaultEyeFov[ovrEye_Left];
 | |
|   mFOVPort[VRDisplayState::Eye_Right] = mDesc.DefaultEyeFov[ovrEye_Right];
 | |
| 
 | |
|   state.mEyeFOV[VRDisplayState::Eye_Left] = FromFovPort(mFOVPort[VRDisplayState::Eye_Left]);
 | |
|   state.mEyeFOV[VRDisplayState::Eye_Right] = FromFovPort(mFOVPort[VRDisplayState::Eye_Right]);
 | |
| 
 | |
|   float pixelsPerDisplayPixel = 1.0;
 | |
|   ovrSizei texSize[2];
 | |
| 
 | |
|   // get eye texture sizes
 | |
|   for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) {
 | |
|     texSize[eye] = ovr_GetFovTextureSize(mSession->Get(), (ovrEyeType)eye, mFOVPort[eye], pixelsPerDisplayPixel);
 | |
|   }
 | |
| 
 | |
|   // take the max of both for eye resolution
 | |
|   state.mEyeResolution.width = std::max(texSize[VRDisplayState::Eye_Left].w, texSize[VRDisplayState::Eye_Right].w);
 | |
|   state.mEyeResolution.height = std::max(texSize[VRDisplayState::Eye_Left].h, texSize[VRDisplayState::Eye_Right].h);
 | |
| 
 | |
|   UpdateEyeParameters();
 | |
|   UpdateStageParameters();
 | |
| }
 | |
| 
 | |
| VRDisplayOculus::~VRDisplayOculus() {
 | |
|   Destroy();
 | |
|   MOZ_COUNT_DTOR_INHERITED(VRDisplayOculus, VRDisplayLocal);
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::Destroy()
 | |
| {
 | |
|   StopPresentation();
 | |
|   mSession = nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::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++) {
 | |
|     // As of Oculus 1.17 SDK, we must use the ovr_GetRenderDesc2 function to return the updated
 | |
|     // version of ovrEyeRenderDesc.  This is normally done by the Oculus static lib shim, but we
 | |
|     // need to do this explicitly as we are loading the Oculus runtime dll directly.
 | |
|     ovrEyeRenderDesc renderDesc = ovr_GetRenderDesc2(mSession->Get(), (ovrEyeType)eye, mFOVPort[eye]);
 | |
|     VRDisplayState& state = mDisplayInfo.mDisplayState;
 | |
|     state.mEyeTranslation[eye].x = renderDesc.HmdToEyePose.Position.x;
 | |
|     state.mEyeTranslation[eye].y = renderDesc.HmdToEyePose.Position.y;
 | |
|     state.mEyeTranslation[eye].z = renderDesc.HmdToEyePose.Position.z;
 | |
|     if (aHeadToEyeTransforms) {
 | |
|       Matrix4x4 pose;
 | |
|       pose.SetRotationFromQuaternion(gfx::Quaternion(renderDesc.HmdToEyePose.Orientation.x, renderDesc.HmdToEyePose.Orientation.y, renderDesc.HmdToEyePose.Orientation.z, renderDesc.HmdToEyePose.Orientation.w));
 | |
|       pose.PreTranslate(renderDesc.HmdToEyePose.Position.x, renderDesc.HmdToEyePose.Position.y, renderDesc.HmdToEyePose.Position.z);
 | |
|       pose.Invert();
 | |
|       aHeadToEyeTransforms[eye] = pose;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::UpdateStageParameters()
 | |
| {
 | |
|   if (!mSession->IsTrackingReady()) {
 | |
|     return;
 | |
|   }
 | |
|   VRDisplayState& state = mDisplayInfo.mDisplayState;
 | |
|   ovrVector3f playArea;
 | |
|   ovrResult res = ovr_GetBoundaryDimensions(mSession->Get(), ovrBoundary_PlayArea, &playArea);
 | |
|   if (res == ovrSuccess) {
 | |
|     state.mStageSize.width = playArea.x;
 | |
|     state.mStageSize.height = playArea.z;
 | |
|   } else {
 | |
|     // If we fail, fall back to reasonable defaults.
 | |
|     // 1m x 1m space
 | |
|     state.mStageSize.width = 1.0f;
 | |
|     state.mStageSize.height = 1.0f;
 | |
|   }
 | |
| 
 | |
|   mEyeHeight = ovr_GetFloat(mSession->Get(), OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT);
 | |
| 
 | |
|   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] = mEyeHeight;
 | |
|   state.mSittingToStandingTransform[14] = 0.0f;
 | |
|   state.mSittingToStandingTransform[15] = 1.0f;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::ZeroSensor()
 | |
| {
 | |
|   if (!mSession->IsTrackingReady()) {
 | |
|     return;
 | |
|   }
 | |
|   ovr_RecenterTrackingOrigin(mSession->Get());
 | |
|   UpdateStageParameters();
 | |
| }
 | |
| 
 | |
| VRHMDSensorState
 | |
| VRDisplayOculus::GetSensorState()
 | |
| {
 | |
|   VRHMDSensorState result{};
 | |
|   if (mSession->IsTrackingReady()) {
 | |
|     gfx::Matrix4x4 headToEyeTransforms[2];
 | |
|     UpdateEyeParameters(headToEyeTransforms);
 | |
|     double predictedFrameTime = 0.0f;
 | |
|     if (gfxPrefs::VRPosePredictionEnabled()) {
 | |
|       // XXX We might need to call ovr_GetPredictedDisplayTime even if we don't use the result.
 | |
|       // If we don't call it, the Oculus driver will spew out many warnings...
 | |
|       predictedFrameTime = ovr_GetPredictedDisplayTime(mSession->Get(), 0);
 | |
|     }
 | |
|     result = GetSensorState(predictedFrameTime);
 | |
|     result.pose.position[1] -= mEyeHeight;
 | |
|     result.CalcViewMatrices(headToEyeTransforms);
 | |
|   }
 | |
|   result.inputFrameID = mDisplayInfo.mFrameId;
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| VRHMDSensorState
 | |
| VRDisplayOculus::GetSensorState(double absTime)
 | |
| {
 | |
|   VRHMDSensorState result{};
 | |
| 
 | |
|   ovrTrackingState state = ovr_GetTrackingState(mSession->Get(), absTime, true);
 | |
|   ovrPoseStatef& pose(state.HeadPose);
 | |
| 
 | |
|   result.timestamp = pose.TimeInSeconds;
 | |
| 
 | |
|   if (state.StatusFlags & ovrStatus_OrientationTracked) {
 | |
|     result.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
 | |
| 
 | |
|     result.pose.orientation[0] = pose.ThePose.Orientation.x;
 | |
|     result.pose.orientation[1] = pose.ThePose.Orientation.y;
 | |
|     result.pose.orientation[2] = pose.ThePose.Orientation.z;
 | |
|     result.pose.orientation[3] = pose.ThePose.Orientation.w;
 | |
| 
 | |
|     result.pose.angularVelocity[0] = pose.AngularVelocity.x;
 | |
|     result.pose.angularVelocity[1] = pose.AngularVelocity.y;
 | |
|     result.pose.angularVelocity[2] = pose.AngularVelocity.z;
 | |
| 
 | |
|     result.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
 | |
| 
 | |
|     result.pose.angularAcceleration[0] = pose.AngularAcceleration.x;
 | |
|     result.pose.angularAcceleration[1] = pose.AngularAcceleration.y;
 | |
|     result.pose.angularAcceleration[2] = pose.AngularAcceleration.z;
 | |
|   } else {
 | |
|     // default to an identity quaternion
 | |
|     result.pose.orientation[3] = 1.0f;
 | |
|   }
 | |
| 
 | |
|   if (state.StatusFlags & ovrStatus_PositionTracked) {
 | |
|     result.flags |= VRDisplayCapabilityFlags::Cap_Position;
 | |
| 
 | |
|     result.pose.position[0] = pose.ThePose.Position.x;
 | |
|     result.pose.position[1] = pose.ThePose.Position.y;
 | |
|     result.pose.position[2] = pose.ThePose.Position.z;
 | |
| 
 | |
|     result.pose.linearVelocity[0] = pose.LinearVelocity.x;
 | |
|     result.pose.linearVelocity[1] = pose.LinearVelocity.y;
 | |
|     result.pose.linearVelocity[2] = pose.LinearVelocity.z;
 | |
| 
 | |
|     result.flags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration;
 | |
| 
 | |
|     result.pose.linearAcceleration[0] = pose.LinearAcceleration.x;
 | |
|     result.pose.linearAcceleration[1] = pose.LinearAcceleration.y;
 | |
|     result.pose.linearAcceleration[2] = pose.LinearAcceleration.z;
 | |
|   }
 | |
|   result.flags |= VRDisplayCapabilityFlags::Cap_External;
 | |
|   result.flags |= VRDisplayCapabilityFlags::Cap_MountDetection;
 | |
|   result.flags |= VRDisplayCapabilityFlags::Cap_Present;
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::StartPresentation()
 | |
| {
 | |
|   if (!CreateD3DObjects()) {
 | |
|     return;
 | |
|   }
 | |
|   mSession->StartPresentation(IntSize(mDisplayInfo.mDisplayState.mEyeResolution.width * 2, mDisplayInfo.mDisplayState.mEyeResolution.height));
 | |
|   if (!mSession->IsPresentationReady()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mQuadVS) {
 | |
|     if (FAILED(mDevice->CreateVertexShader(sLayerQuadVS.mData, sLayerQuadVS.mLength, nullptr, &mQuadVS))) {
 | |
|       NS_WARNING("Failed to create vertex shader for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mQuadPS) {
 | |
|     if (FAILED(mDevice->CreatePixelShader(sRGBShader.mData, sRGBShader.mLength, nullptr, &mQuadPS))) {
 | |
|       NS_WARNING("Failed to create pixel shader for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   CD3D11_BUFFER_DESC cBufferDesc(sizeof(layers::VertexShaderConstants),
 | |
|     D3D11_BIND_CONSTANT_BUFFER,
 | |
|     D3D11_USAGE_DYNAMIC,
 | |
|     D3D11_CPU_ACCESS_WRITE);
 | |
| 
 | |
|   if (!mVSConstantBuffer) {
 | |
|     if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mVSConstantBuffer)))) {
 | |
|       NS_WARNING("Failed to vertex shader constant buffer for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mPSConstantBuffer) {
 | |
|     cBufferDesc.ByteWidth = sizeof(layers::PixelShaderConstants);
 | |
|     if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mPSConstantBuffer)))) {
 | |
|       NS_WARNING("Failed to pixel shader constant buffer for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mLinearSamplerState) {
 | |
|     CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT);
 | |
|     if (FAILED(mDevice->CreateSamplerState(&samplerDesc, getter_AddRefs(mLinearSamplerState)))) {
 | |
|       NS_WARNING("Failed to create sampler state for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mInputLayout) {
 | |
|     D3D11_INPUT_ELEMENT_DESC layout[] =
 | |
|     {
 | |
|       { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
 | |
|     };
 | |
| 
 | |
|     if (FAILED(mDevice->CreateInputLayout(layout,
 | |
|                                               sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC),
 | |
|                                               sLayerQuadVS.mData,
 | |
|                                               sLayerQuadVS.mLength,
 | |
|                                               getter_AddRefs(mInputLayout)))) {
 | |
|       NS_WARNING("Failed to create input layout for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mVertexBuffer) {
 | |
|     Vertex vertices[] = { { { 0.0, 0.0 } },{ { 1.0, 0.0 } },{ { 0.0, 1.0 } },{ { 1.0, 1.0 } } };
 | |
|     CD3D11_BUFFER_DESC bufferDesc(sizeof(vertices), D3D11_BIND_VERTEX_BUFFER);
 | |
|     D3D11_SUBRESOURCE_DATA data;
 | |
|     data.pSysMem = (void*)vertices;
 | |
| 
 | |
|     if (FAILED(mDevice->CreateBuffer(&bufferDesc, &data, getter_AddRefs(mVertexBuffer)))) {
 | |
|       NS_WARNING("Failed to create vertex buffer for Oculus");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   memset(&mVSConstants, 0, sizeof(mVSConstants));
 | |
|   memset(&mPSConstants, 0, sizeof(mPSConstants));
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::StopPresentation()
 | |
| {
 | |
|   if (mSession) {
 | |
|     mSession->StopPresentation();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| VRDisplayOculus::UpdateConstantBuffers()
 | |
| {
 | |
|   HRESULT hr;
 | |
|   D3D11_MAPPED_SUBRESOURCE resource;
 | |
|   resource.pData = nullptr;
 | |
| 
 | |
|   hr = mContext->Map(mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
 | |
|   if (FAILED(hr) || !resource.pData) {
 | |
|     return false;
 | |
|   }
 | |
|   *(VertexShaderConstants*)resource.pData = mVSConstants;
 | |
|   mContext->Unmap(mVSConstantBuffer, 0);
 | |
|   resource.pData = nullptr;
 | |
| 
 | |
|   hr = mContext->Map(mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
 | |
|   if (FAILED(hr) || !resource.pData) {
 | |
|     return false;
 | |
|   }
 | |
|   *(PixelShaderConstants*)resource.pData = mPSConstants;
 | |
|   mContext->Unmap(mPSConstantBuffer, 0);
 | |
| 
 | |
|   ID3D11Buffer *buffer = mVSConstantBuffer;
 | |
|   mContext->VSSetConstantBuffers(0, 1, &buffer);
 | |
|   buffer = mPSConstantBuffer;
 | |
|   mContext->PSSetConstantBuffers(0, 1, &buffer);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool
 | |
| VRDisplayOculus::SubmitFrame(ID3D11Texture2D* aSource,
 | |
|                              const IntSize& aSize,
 | |
|                              const gfx::Rect& aLeftEyeRect,
 | |
|                              const gfx::Rect& aRightEyeRect)
 | |
| {
 | |
|   MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
 | |
|   if (!CreateD3DObjects()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   AutoRestoreRenderState restoreState(this);
 | |
|   if (!restoreState.IsSuccess()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!mSession->IsPresentationReady()) {
 | |
|     return false;
 | |
|   }
 | |
|   /**
 | |
|     * XXX - We should resolve fail the promise returned by
 | |
|     *       VRDisplay.requestPresent() when the DX11 resources fail allocation
 | |
|     *       in VRDisplayOculus::StartPresentation().
 | |
|     *       Bailing out here prevents the crash but content should be aware
 | |
|     *       that frames are not being presented.
 | |
|     *       See Bug 1299309.
 | |
|     **/
 | |
| 
 | |
|   RefPtr<CompositingRenderTargetD3D11> surface = mSession->GetNextRenderTarget();
 | |
| 
 | |
|   surface->BindRenderTarget(mContext);
 | |
| 
 | |
|   Matrix viewMatrix = Matrix::Translation(-1.0, 1.0);
 | |
|   viewMatrix.PreScale(2.0f / float(aSize.width), 2.0f / float(aSize.height));
 | |
|   viewMatrix.PreScale(1.0f, -1.0f);
 | |
|   Matrix4x4 projection = Matrix4x4::From2D(viewMatrix);
 | |
|   projection._33 = 0.0f;
 | |
| 
 | |
|   Matrix transform2d;
 | |
|   gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d);
 | |
| 
 | |
|   D3D11_VIEWPORT viewport;
 | |
|   viewport.MinDepth = 0.0f;
 | |
|   viewport.MaxDepth = 1.0f;
 | |
|   viewport.Width = aSize.width;
 | |
|   viewport.Height = aSize.height;
 | |
|   viewport.TopLeftX = 0;
 | |
|   viewport.TopLeftY = 0;
 | |
| 
 | |
|   D3D11_RECT scissor;
 | |
|   scissor.left = 0;
 | |
|   scissor.right = aSize.width;
 | |
|   scissor.top = 0;
 | |
|   scissor.bottom = aSize.height;
 | |
| 
 | |
|   memcpy(&mVSConstants.layerTransform, &transform._11, sizeof(mVSConstants.layerTransform));
 | |
|   memcpy(&mVSConstants.projection, &projection._11, sizeof(mVSConstants.projection));
 | |
|   mVSConstants.renderTargetOffset[0] = 0.0f;
 | |
|   mVSConstants.renderTargetOffset[1] = 0.0f;
 | |
|   mVSConstants.layerQuad = Rect(0.0f, 0.0f, aSize.width, aSize.height);
 | |
|   mVSConstants.textureCoords = Rect(0.0f, 1.0f, 1.0f, -1.0f);
 | |
| 
 | |
|   mPSConstants.layerOpacity[0] = 1.0f;
 | |
| 
 | |
|   ID3D11Buffer* vbuffer = mVertexBuffer;
 | |
|   UINT vsize = sizeof(Vertex);
 | |
|   UINT voffset = 0;
 | |
|   mContext->IASetVertexBuffers(0, 1, &vbuffer, &vsize, &voffset);
 | |
|   mContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
 | |
|   mContext->IASetInputLayout(mInputLayout);
 | |
|   mContext->RSSetViewports(1, &viewport);
 | |
|   mContext->RSSetScissorRects(1, &scissor);
 | |
|   mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
 | |
|   mContext->VSSetShader(mQuadVS, nullptr, 0);
 | |
|   mContext->PSSetShader(mQuadPS, nullptr, 0);
 | |
| 
 | |
|   RefPtr<ID3D11ShaderResourceView> srView;
 | |
|   HRESULT hr = mDevice->CreateShaderResourceView(aSource, nullptr, getter_AddRefs(srView));
 | |
|   if (FAILED(hr)) {
 | |
|     gfxWarning() << "Could not create shader resource view for Oculus: " << hexa(hr);
 | |
|     return false;
 | |
|   }
 | |
|   ID3D11ShaderResourceView* viewPtr = srView.get();
 | |
|   mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &viewPtr);
 | |
|   // XXX Use Constant from TexSlot in CompositorD3D11.cpp?
 | |
| 
 | |
|   ID3D11SamplerState *sampler = mLinearSamplerState;
 | |
|   mContext->PSSetSamplers(0, 1, &sampler);
 | |
| 
 | |
|   if (!UpdateConstantBuffers()) {
 | |
|     NS_WARNING("Failed to update constant buffers for Oculus");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mContext->Draw(4, 0);
 | |
| 
 | |
|   ovrResult orv = ovr_CommitTextureSwapChain(mSession->Get(), mSession->GetSwapChain());
 | |
|   if (orv != ovrSuccess) {
 | |
|     NS_WARNING("ovr_CommitTextureSwapChain failed.\n");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   ovrLayerEyeFov layer;
 | |
|   memset(&layer, 0, sizeof(layer));
 | |
|   layer.Header.Type = ovrLayerType_EyeFov;
 | |
|   layer.Header.Flags = 0;
 | |
|   layer.ColorTexture[0] = mSession->GetSwapChain();
 | |
|   layer.ColorTexture[1] = nullptr;
 | |
|   layer.Fov[0] = mFOVPort[0];
 | |
|   layer.Fov[1] = mFOVPort[1];
 | |
|   layer.Viewport[0].Pos.x = aSize.width * aLeftEyeRect.X();
 | |
|   layer.Viewport[0].Pos.y = aSize.height * aLeftEyeRect.Y();
 | |
|   layer.Viewport[0].Size.w = aSize.width * aLeftEyeRect.Width();
 | |
|   layer.Viewport[0].Size.h = aSize.height * aLeftEyeRect.Height();
 | |
|   layer.Viewport[1].Pos.x = aSize.width * aRightEyeRect.X();
 | |
|   layer.Viewport[1].Pos.y = aSize.height * aRightEyeRect.Y();
 | |
|   layer.Viewport[1].Size.w = aSize.width * aRightEyeRect.Width();
 | |
|   layer.Viewport[1].Size.h = aSize.height * aRightEyeRect.Height();
 | |
| 
 | |
|   const VRHMDSensorState& sensorState = mDisplayInfo.GetSensorState();
 | |
|   gfx::Matrix4x4 matView[2];
 | |
|   memcpy(matView[0].components, sensorState.leftViewMatrix, sizeof(sensorState.leftViewMatrix));
 | |
|   memcpy(matView[1].components, sensorState.rightViewMatrix, sizeof(sensorState.rightViewMatrix));
 | |
| 
 | |
|   for (uint32_t i = 0; i < 2; ++i) {
 | |
|     Point3D eyeTranslation;
 | |
|     Quaternion eyeRotation;
 | |
|     Point3D eyeScale;
 | |
|     if (!matView[i].Decompose(eyeTranslation, eyeRotation, eyeScale)) {
 | |
|       NS_WARNING("Failed to decompose eye pose matrix for Oculus");
 | |
|     }
 | |
|     layer.RenderPose[i].Orientation.x = eyeRotation.x;
 | |
|     layer.RenderPose[i].Orientation.y = eyeRotation.y;
 | |
|     layer.RenderPose[i].Orientation.z = eyeRotation.z;
 | |
|     layer.RenderPose[i].Orientation.w = eyeRotation.w;
 | |
|     layer.RenderPose[i].Position.x = eyeTranslation.x;
 | |
|     layer.RenderPose[i].Position.y = eyeTranslation.y;
 | |
|     layer.RenderPose[i].Position.z = eyeTranslation.z;
 | |
|   }
 | |
| 
 | |
|   ovrLayerHeader *layers = &layer.Header;
 | |
|   orv = ovr_SubmitFrame(mSession->Get(), mDisplayInfo.mFrameId, nullptr, &layers, 1);
 | |
|   // ovr_SubmitFrame will fail during the Oculus health and safety warning.
 | |
|   // and will start succeeding once the warning has been dismissed by the user.
 | |
| 
 | |
|   if (!OVR_UNQUALIFIED_SUCCESS(orv)) {
 | |
|     /**
 | |
|      * We wish to throttle the framerate for any case that the rendered
 | |
|      * result is not visible.  In some cases, such as during the Oculus
 | |
|      * "health and safety warning", orv will be > 0 (OVR_SUCCESS but not
 | |
|      * OVR_UNQUALIFIED_SUCCESS) and ovr_SubmitFrame will not block.
 | |
|      * In this case, returning true would have resulted in an unthrottled
 | |
|      * render loop hiting excessive frame rates and consuming resources.
 | |
|      */
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mSession->mSubmitThread = mSubmitThread;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRDisplayOculus::Refresh()
 | |
| {
 | |
|   mDisplayInfo.mDisplayState.mIsConnected = mSession->IsTrackingReady();
 | |
|   mDisplayInfo.mDisplayState.mIsMounted = mSession->IsMounted();
 | |
| }
 | |
| 
 | |
| VRControllerOculus::VRControllerOculus(dom::GamepadHand aHand, uint32_t aDisplayID)
 | |
|   : VRControllerHost(VRDeviceType::Oculus, aHand, aDisplayID)
 | |
|   , mIndexTrigger(0.0f)
 | |
|   , mHandTrigger(0.0f)
 | |
|   , mVibrateThread(nullptr)
 | |
|   , mIsVibrateStopped(false)
 | |
| {
 | |
|   MOZ_COUNT_CTOR_INHERITED(VRControllerOculus, VRControllerHost);
 | |
| 
 | |
|   VRControllerState& state = mControllerInfo.mControllerState;
 | |
| 
 | |
|   char* touchID = "";
 | |
|   switch (aHand) {
 | |
|     case dom::GamepadHand::Left:
 | |
|       touchID = "Oculus Touch (Left)";
 | |
|       break;
 | |
|     case dom::GamepadHand::Right:
 | |
|       touchID = "Oculus Touch (Right)";
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_ASSERT(false);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   strncpy(state.controllerName, touchID, kVRControllerNameMaxLen);
 | |
| 
 | |
|   MOZ_ASSERT(kNumOculusButton ==
 | |
|              static_cast<uint32_t>(OculusLeftControllerButtonType::NumButtonType)
 | |
|              && kNumOculusButton ==
 | |
|              static_cast<uint32_t>(OculusRightControllerButtonType::NumButtonType));
 | |
| 
 | |
|   state.numButtons = kNumOculusButton;
 | |
|   state.numAxes = static_cast<uint32_t>(
 | |
|                    OculusControllerAxisType::NumVRControllerAxisType);
 | |
|   state.numHaptics = kNumOculusHaptcs;
 | |
| }
 | |
| 
 | |
| float
 | |
| VRControllerOculus::GetAxisMove(uint32_t aAxis)
 | |
| {
 | |
|   return mAxisMove[aAxis];
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::SetAxisMove(uint32_t aAxis, float aValue)
 | |
| {
 | |
|   mAxisMove[aAxis] = aValue;
 | |
| }
 | |
| 
 | |
| float
 | |
| VRControllerOculus::GetIndexTrigger()
 | |
| {
 | |
|   return mIndexTrigger;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::SetIndexTrigger(float aValue)
 | |
| {
 | |
|   mIndexTrigger = aValue;
 | |
| }
 | |
| 
 | |
| float
 | |
| VRControllerOculus::GetHandTrigger()
 | |
| {
 | |
|   return mHandTrigger;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::SetHandTrigger(float aValue)
 | |
| {
 | |
|   mHandTrigger = aValue;
 | |
| }
 | |
| 
 | |
| VRControllerOculus::~VRControllerOculus()
 | |
| {
 | |
|   ShutdownVibrateHapticThread();
 | |
|   MOZ_COUNT_DTOR_INHERITED(VRControllerOculus, VRControllerHost);
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::UpdateVibrateHaptic(ovrSession aSession,
 | |
|                                         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(aSession, aPromise, true);
 | |
|     return;
 | |
|   }
 | |
|   // Avoid the previous vibrate event to override the new one.
 | |
|   if (mVibrateIndex != aVibrateIndex) {
 | |
|     VibrateHapticComplete(aSession, aPromise, false);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const double duration = (aIntensity == 0) ? 0 : aDuration;
 | |
|   // Vibration amplitude in the [0.0, 1.0] range.
 | |
|   const float amplitude = aIntensity > 1.0 ? 1.0 : aIntensity;
 | |
|   // Vibration is enabled by specifying the frequency.
 | |
|   // Specifying 0.0f will disable the vibration, 0.5f will vibrate at 160Hz,
 | |
|   // and 1.0f will vibrate at 320Hz.
 | |
|   const float frequency = (duration > 0) ? 1.0f : 0.0f;
 | |
|   ovrControllerType hand;
 | |
| 
 | |
|   switch (GetHand()) {
 | |
|     case GamepadHand::Left:
 | |
|       hand = ovrControllerType::ovrControllerType_LTouch;
 | |
|       break;
 | |
|     case GamepadHand::Right:
 | |
|       hand = ovrControllerType::ovrControllerType_RTouch;
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_ASSERT(false);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // Oculus Touch only can get the response from ovr_SetControllerVibration()
 | |
|   // at the presenting mode.
 | |
|   ovrResult result = ovr_SetControllerVibration(aSession, hand, frequency,
 | |
|                                                 (frequency == 0.0f) ? 0.0f : amplitude);
 | |
|   if (result != ovrSuccess) {
 | |
|     printf_stderr("%s hand ovr_SetControllerVibration skipped.\n",
 | |
|                   GamepadHandValues::strings[uint32_t(GetHand())].value);
 | |
|   }
 | |
| 
 | |
|   // In Oculus dev doc, it mentions vibration lasts for a maximum of 2.5 seconds
 | |
|   // at ovr_SetControllerVibration(), but we found 2.450 sec is more close to the
 | |
|   // real looping use case.
 | |
|   const double kVibrateRate = 2450.0;
 | |
|   const double remainingTime = (duration > kVibrateRate)
 | |
|                                 ? (duration - kVibrateRate) : duration;
 | |
| 
 | |
|   if (remainingTime) {
 | |
|     MOZ_ASSERT(mVibrateThread);
 | |
| 
 | |
|     RefPtr<Runnable> runnable =
 | |
|       NewRunnableMethod<ovrSession, uint32_t, double, double, uint64_t,
 | |
|         StoreCopyPassByConstLRef<VRManagerPromise>>(
 | |
|           "VRControllerOculus::UpdateVibrateHaptic",
 | |
|           this, &VRControllerOculus::UpdateVibrateHaptic, aSession,
 | |
|           aHapticIndex, aIntensity, (duration > kVibrateRate) ? remainingTime : 0, aVibrateIndex, aPromise);
 | |
|     mVibrateThread->PostDelayedTask(runnable.forget(), (duration > kVibrateRate) ? kVibrateRate : remainingTime);
 | |
|   } else {
 | |
|     VibrateHapticComplete(aSession, aPromise, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::VibrateHapticComplete(ovrSession aSession, const VRManagerPromise& aPromise,
 | |
|                                           bool aStop)
 | |
| {
 | |
|   if (aStop) {
 | |
|     ovrControllerType hand;
 | |
| 
 | |
|     switch (GetHand()) {
 | |
|       case GamepadHand::Left:
 | |
|         hand = ovrControllerType::ovrControllerType_LTouch;
 | |
|         break;
 | |
|       case GamepadHand::Right:
 | |
|         hand = ovrControllerType::ovrControllerType_RTouch;
 | |
|         break;
 | |
|       default:
 | |
|         MOZ_ASSERT(false);
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     ovrResult result = ovr_SetControllerVibration(aSession, hand, 0.0f, 0.0f);
 | |
|     if (result != ovrSuccess) {
 | |
|       printf_stderr("%s Haptics skipped.\n",
 | |
|                     GamepadHandValues::strings[uint32_t(GetHand())].value);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   VRManager *vm = VRManager::Get();
 | |
|   MOZ_ASSERT(vm);
 | |
| 
 | |
|   VRListenerThreadHolder::Loop()->PostTask(
 | |
|     NewRunnableMethod<StoreCopyPassByConstLRef<VRManagerPromise>>(
 | |
|       "VRManager::NotifyVibrateHapticCompleted",
 | |
|       vm, &VRManager::NotifyVibrateHapticCompleted, aPromise));
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::VibrateHaptic(ovrSession aSession,
 | |
|                                   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("Oculus_Vibration"));
 | |
|   }
 | |
|   mVibrateThread->Start();
 | |
|   ++mVibrateIndex;
 | |
|   mIsVibrateStopped = false;
 | |
| 
 | |
|   RefPtr<Runnable> runnable =
 | |
|     NewRunnableMethod<ovrSession, uint32_t, double, double, uint64_t,
 | |
|       StoreCopyPassByConstLRef<VRManagerPromise>>(
 | |
|         "VRControllerOculus::UpdateVibrateHaptic",
 | |
|         this, &VRControllerOculus::UpdateVibrateHaptic, aSession,
 | |
|         aHapticIndex, aIntensity, aDuration, mVibrateIndex, aPromise);
 | |
|   mVibrateThread->PostTask(runnable.forget());
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::StopVibrateHaptic()
 | |
| {
 | |
|   mIsVibrateStopped = true;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRControllerOculus::ShutdownVibrateHapticThread()
 | |
| {
 | |
|   StopVibrateHaptic();
 | |
|   if (mVibrateThread) {
 | |
|     mVibrateThread->Shutdown();
 | |
|     mVibrateThread = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*static*/ already_AddRefed<VRSystemManagerOculus>
 | |
| VRSystemManagerOculus::Create()
 | |
| {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!gfxPrefs::VREnabled() || !gfxPrefs::VROculusEnabled())
 | |
|   {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<VRSystemManagerOculus> manager = new VRSystemManagerOculus();
 | |
|   return manager.forget();
 | |
| }
 | |
| 
 | |
| VRSystemManagerOculus::VRSystemManagerOculus()
 | |
|   : mSession(nullptr)
 | |
| {
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::Destroy()
 | |
| {
 | |
|   Shutdown();
 | |
|   mSession = nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::Shutdown()
 | |
| {
 | |
|   if (mSession) {
 | |
|     mSession->StopTracking();
 | |
|   }
 | |
|   RemoveControllers();
 | |
|   if (mDisplay) {
 | |
|     mDisplay->Destroy();
 | |
|   }
 | |
|   mDisplay = nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::NotifyVSync()
 | |
| {
 | |
|   VRSystemManager::NotifyVSync();
 | |
|   if (!mSession) {
 | |
|     return;
 | |
|   }
 | |
|   mSession->Refresh();
 | |
|   if (mDisplay) {
 | |
|     mDisplay->Refresh();
 | |
|   }
 | |
|   // Detect disconnection
 | |
|   if (!mSession->IsTrackingReady()) {
 | |
|     // No HMD connected
 | |
|     mDisplay = nullptr;
 | |
|     // Prevent enumeration from continuously running
 | |
|     // after the headset is disconnected.
 | |
|     mSession->StopTracking();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| VRSystemManagerOculus::ShouldInhibitEnumeration()
 | |
| {
 | |
|   if (VRSystemManager::ShouldInhibitEnumeration()) {
 | |
|     return true;
 | |
|   }
 | |
|   if (mDisplay) {
 | |
|     // When we find an Oculus VR device, don't
 | |
|     // allow any further enumeration as it
 | |
|     // may get picked up redundantly by other
 | |
|     // API's such as OpenVR.
 | |
|     return true;
 | |
|   }
 | |
|   if (mSession && mSession->IsQuitTimeoutActive()) {
 | |
|     // When we are responding to ShouldQuit, we return true here
 | |
|     // to prevent further enumeration by other VRSystemManager's such as
 | |
|     // VRSystemManagerOpenVR which would also enumerate the connected Oculus
 | |
|     // HMD, resulting in interference with the Oculus runtime software updates.
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::Enumerate()
 | |
| {
 | |
|   if (!mSession) {
 | |
|     mSession = new VROculusSession();
 | |
|   }
 | |
|   mSession->StartTracking();
 | |
|   if (mDisplay == nullptr && mSession->IsTrackingReady()) {
 | |
|     // HMD Detected
 | |
|     mDisplay = new VRDisplayOculus(mSession);
 | |
|   }
 | |
|   if (mDisplay == nullptr) {
 | |
|     // Prevent enumeration from continuously running
 | |
|     // when a headset was not found.
 | |
|     mSession->StopTracking();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
 | |
| {
 | |
|   if (mDisplay) {
 | |
|     aHMDResult.AppendElement(mDisplay);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| VRSystemManagerOculus::GetIsPresenting()
 | |
| {
 | |
|   if (mDisplay) {
 | |
|     VRDisplayInfo displayInfo(mDisplay->GetDisplayInfo());
 | |
|     return displayInfo.GetPresentingGroups() != 0;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleInput()
 | |
| {
 | |
|   // The session is available after VRDisplay is created
 | |
|   // at GetHMDs().
 | |
|   if (!mSession || !mSession->IsTrackingReady()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<impl::VRControllerOculus> controller;
 | |
|   ovrInputState inputState;
 | |
|   uint32_t axis = 0;
 | |
|   const bool hasInputState = ovr_GetInputState(mSession->Get(), ovrControllerType_Touch,
 | |
|                                                &inputState) == ovrSuccess;
 | |
| 
 | |
|   if (!hasInputState) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
 | |
|     controller = mOculusController[i];
 | |
|     const GamepadHand hand = controller->GetHand();
 | |
|     const uint32_t handIdx = static_cast<uint32_t>(hand) - 1;
 | |
|     uint32_t buttonIdx = 0;
 | |
|     ovrHandType oculusHandType;
 | |
| 
 | |
|     switch (hand) {
 | |
|       case dom::GamepadHand::Left:
 | |
|         oculusHandType = ovrHand_Left;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_LThumb, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx], ovrTouch_LIndexTrigger, inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
 | |
|         ++buttonIdx;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_X, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_Y, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleTouchEvent(i, buttonIdx, ovrTouch_LThumbRest, inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         break;
 | |
|       case dom::GamepadHand::Right:
 | |
|         oculusHandType = ovrHand_Right;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_RThumb, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleIndexTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx], ovrTouch_RIndexTrigger, inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
 | |
|         ++buttonIdx;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_A, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleButtonPress(i, buttonIdx, ovrButton_B, inputState.Buttons,
 | |
|                           inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         HandleTouchEvent(i, buttonIdx, ovrTouch_RThumbRest, inputState.Touches);
 | |
|         ++buttonIdx;
 | |
|         break;
 | |
|       default:
 | |
|         MOZ_ASSERT(false);
 | |
|         break;
 | |
|     }
 | |
|     controller->SetButtonPressed(inputState.Buttons);
 | |
|     controller->SetButtonTouched(inputState.Touches);
 | |
| 
 | |
|     axis = static_cast<uint32_t>(OculusControllerAxisType::ThumbstickXAxis);
 | |
|     HandleAxisMove(i, axis, inputState.Thumbstick[oculusHandType].x);
 | |
| 
 | |
|     axis = static_cast<uint32_t>(OculusControllerAxisType::ThumbstickYAxis);
 | |
|     HandleAxisMove(i, axis, -inputState.Thumbstick[oculusHandType].y);
 | |
| 
 | |
|     // Process pose state.
 | |
|     GamepadPoseState poseState;
 | |
|     GetControllerPoseState(handIdx, poseState);
 | |
|     HandlePoseTracking(i, poseState, controller);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::GetControllerPoseState(uint32_t aHandIdx, GamepadPoseState& aPoseState,
 | |
|                                               bool aForceUpdate)
 | |
| {
 | |
|     ovrTrackingState state = ovr_GetTrackingState(mSession->Get(), 0.0, false);
 | |
| 
 | |
|     // HandPoses is ordered by ovrControllerType_LTouch and ovrControllerType_RTouch,
 | |
|     // therefore, we can't get its state by the index of mOculusController.
 | |
|     ovrPoseStatef& pose(state.HandPoses[aHandIdx]);
 | |
| 
 | |
|     if (aForceUpdate || state.HandStatusFlags[aHandIdx] & ovrStatus_OrientationTracked) {
 | |
|       aPoseState.flags |= GamepadCapabilityFlags::Cap_Orientation;
 | |
|       aPoseState.orientation[0] = pose.ThePose.Orientation.x;
 | |
|       aPoseState.orientation[1] = pose.ThePose.Orientation.y;
 | |
|       aPoseState.orientation[2] = pose.ThePose.Orientation.z;
 | |
|       aPoseState.orientation[3] = pose.ThePose.Orientation.w;
 | |
|       aPoseState.angularVelocity[0] = pose.AngularVelocity.x;
 | |
|       aPoseState.angularVelocity[1] = pose.AngularVelocity.y;
 | |
|       aPoseState.angularVelocity[2] = pose.AngularVelocity.z;
 | |
| 
 | |
|       aPoseState.flags |= GamepadCapabilityFlags::Cap_AngularAcceleration;
 | |
|       aPoseState.angularAcceleration[0] = pose.AngularAcceleration.x;
 | |
|       aPoseState.angularAcceleration[1] = pose.AngularAcceleration.y;
 | |
|       aPoseState.angularAcceleration[2] = pose.AngularAcceleration.z;
 | |
|       aPoseState.isOrientationValid = true;
 | |
|     }
 | |
|     if (aForceUpdate || state.HandStatusFlags[aHandIdx] & ovrStatus_PositionTracked) {
 | |
|       aPoseState.flags |= GamepadCapabilityFlags::Cap_Position;
 | |
|       aPoseState.position[0] = pose.ThePose.Position.x;
 | |
|       aPoseState.position[1] = pose.ThePose.Position.y;
 | |
|       aPoseState.position[2] = pose.ThePose.Position.z;
 | |
|       aPoseState.linearVelocity[0] = pose.LinearVelocity.x;
 | |
|       aPoseState.linearVelocity[1] = pose.LinearVelocity.y;
 | |
|       aPoseState.linearVelocity[2] = pose.LinearVelocity.z;
 | |
| 
 | |
|       aPoseState.flags |= GamepadCapabilityFlags::Cap_LinearAcceleration;
 | |
|       aPoseState.linearAcceleration[0] = pose.LinearAcceleration.x;
 | |
|       aPoseState.linearAcceleration[1] = pose.LinearAcceleration.y;
 | |
|       aPoseState.linearAcceleration[2] = pose.LinearAcceleration.z;
 | |
| 
 | |
|       float eyeHeight = ovr_GetFloat(mSession->Get(), OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT);
 | |
|       aPoseState.position[1] -= eyeHeight;
 | |
|       aPoseState.isPositionValid = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleButtonPress(uint32_t aControllerIdx,
 | |
|                                          uint32_t aButton,
 | |
|                                          uint64_t aButtonMask,
 | |
|                                          uint64_t aButtonPressed,
 | |
|                                          uint64_t aButtonTouched)
 | |
| {
 | |
|   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
 | |
|   MOZ_ASSERT(controller);
 | |
|   const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed);
 | |
|   const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
 | |
| 
 | |
|   if (!pressedDiff && !touchedDiff) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (pressedDiff & aButtonMask ||
 | |
|       touchedDiff & aButtonMask) {
 | |
|     // diff & (aButtonPressed, aButtonTouched) would be true while a new button pressed or
 | |
|     // touched event, otherwise it is an old event and needs to notify
 | |
|     // the button has been released.
 | |
|     NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
 | |
|                    aButtonMask & aButtonTouched,
 | |
|                    (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleIndexTriggerPress(uint32_t aControllerIdx,
 | |
|                                                uint32_t aButton,
 | |
|                                                float aValue,
 | |
|                                                uint64_t aTouchMask,
 | |
|                                                uint64_t aButtonTouched)
 | |
| {
 | |
|   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
 | |
|   MOZ_ASSERT(controller);
 | |
| 
 | |
|   const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
 | |
|   const float oldValue = controller->GetIndexTrigger();
 | |
|   // We prefer to let developers to set their own threshold for the adjustment.
 | |
|   // Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask 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 ||
 | |
|       touchedDiff & aTouchMask) {
 | |
|     NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
 | |
|                    aButtonTouched & aTouchMask, aValue);
 | |
|     controller->SetIndexTrigger(aValue);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleHandTriggerPress(uint32_t aControllerIdx,
 | |
|                                               uint32_t aButton,
 | |
|                                               float aValue)
 | |
| {
 | |
|   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
 | |
|   MOZ_ASSERT(controller);
 | |
|   const float oldValue = controller->GetHandTrigger();
 | |
|   // We prefer to let developers to set their own threshold for the adjustment.
 | |
|   // Therefore, we don't check ButtonPressed and ButtonTouched with TouchMask here.
 | |
|   // we just check the button value is larger than the threshold value or not.
 | |
|   const float threshold = gfxPrefs::VRControllerTriggerThreshold();
 | |
| 
 | |
|   // Avoid sending duplicated events in IPC channels.
 | |
|   if (oldValue != aValue) {
 | |
|     NewButtonEvent(aControllerIdx, aButton, aValue > threshold,
 | |
|                    aValue > threshold, aValue);
 | |
|     controller->SetHandTrigger(aValue);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
 | |
|                                         uint64_t aTouchMask, uint64_t aButtonTouched)
 | |
| {
 | |
|   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
 | |
|   MOZ_ASSERT(controller);
 | |
|   const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
 | |
| 
 | |
|   if (touchedDiff & aTouchMask) {
 | |
|     NewButtonEvent(aControllerIdx, aButton, false, aTouchMask & aButtonTouched, 0.0f);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
 | |
|                                       float aValue)
 | |
| {
 | |
|   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
 | |
|   MOZ_ASSERT(controller);
 | |
|   float value = aValue;
 | |
| 
 | |
|   if (abs(aValue) < 0.0000009f) {
 | |
|     value = 0.0f; // Clear noise signal
 | |
|   }
 | |
| 
 | |
|   if (controller->GetAxisMove(aAxis) != value) {
 | |
|     NewAxisMove(aControllerIdx, aAxis, value);
 | |
|     controller->SetAxisMove(aAxis, value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::HandlePoseTracking(uint32_t aControllerIdx,
 | |
|                                           const GamepadPoseState& aPose,
 | |
|                                           VRControllerHost* aController)
 | |
| {
 | |
|   MOZ_ASSERT(aController);
 | |
|   if (aPose != aController->GetPose()) {
 | |
|     aController->SetPose(aPose);
 | |
|     NewPoseState(aControllerIdx, aPose);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::VibrateHaptic(uint32_t aControllerIdx,
 | |
|                                      uint32_t aHapticIndex,
 | |
|                                      double aIntensity,
 | |
|                                      double aDuration,
 | |
|                                      const VRManagerPromise& aPromise)
 | |
| {
 | |
|   // The session is available after VRDisplay is created
 | |
|   // at GetHMDs().
 | |
|   if (!mSession || !mSession->IsTrackingReady() ||
 | |
|       (aControllerIdx >= mOculusController.Length())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<impl::VRControllerOculus> controller = mOculusController[aControllerIdx];
 | |
|   MOZ_ASSERT(controller);
 | |
| 
 | |
|   controller->VibrateHaptic(mSession->Get(), aHapticIndex, aIntensity, aDuration, aPromise);
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::StopVibrateHaptic(uint32_t aControllerIdx)
 | |
| {
 | |
|   // The session is available after VRDisplay is created
 | |
|   // at GetHMDs().
 | |
|   if (!mSession || !mSession->IsTrackingReady() ||
 | |
|       (aControllerIdx >= mOculusController.Length())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<impl::VRControllerOculus> controller = mOculusController[aControllerIdx];
 | |
|   MOZ_ASSERT(controller);
 | |
| 
 | |
|   controller->StopVibrateHaptic();
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::GetControllers(nsTArray<RefPtr<VRControllerHost>>&
 | |
|                                       aControllerResult)
 | |
| {
 | |
|   aControllerResult.Clear();
 | |
|   for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
 | |
|     aControllerResult.AppendElement(mOculusController[i]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::ScanForControllers()
 | |
| {
 | |
|   // mSession is available after VRDisplay is created
 | |
|   // at GetHMDs().
 | |
|   if (!mSession || !mSession->IsTrackingReady()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ovrInputState inputState;
 | |
|   bool hasInputState = ovr_GetInputState(mSession->Get(), ovrControllerType_Touch,
 | |
|                                          &inputState) == ovrSuccess;
 | |
| 
 | |
|   if (!hasInputState) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ovrControllerType activeControllerArray[2];
 | |
|   uint32_t newControllerCount = 0;
 | |
| 
 | |
|   if (inputState.ControllerType & ovrControllerType_LTouch) {
 | |
|     activeControllerArray[newControllerCount] = ovrControllerType_LTouch;
 | |
|     ++newControllerCount;
 | |
|   }
 | |
| 
 | |
|   if (inputState.ControllerType & ovrControllerType_RTouch) {
 | |
|     activeControllerArray[newControllerCount] = ovrControllerType_RTouch;
 | |
|     ++newControllerCount;
 | |
|   }
 | |
| 
 | |
|   if (newControllerCount != mControllerCount) {
 | |
|     RemoveControllers();
 | |
| 
 | |
|     // Re-adding controllers to VRControllerManager.
 | |
|     for (uint32_t i = 0; i < newControllerCount; ++i) {
 | |
|       GamepadHand hand;
 | |
| 
 | |
|       switch (activeControllerArray[i]) {
 | |
|         case ovrControllerType::ovrControllerType_LTouch:
 | |
|           hand = GamepadHand::Left;
 | |
|           break;
 | |
|         case ovrControllerType::ovrControllerType_RTouch:
 | |
|           hand = GamepadHand::Right;
 | |
|           break;
 | |
|       }
 | |
|       RefPtr<VRControllerOculus> oculusController = new VRControllerOculus(hand,
 | |
|                                                       mDisplay->GetDisplayInfo().GetDisplayID());
 | |
|       mOculusController.AppendElement(oculusController);
 | |
| 
 | |
|       // Not already present, add it.
 | |
|       AddGamepad(oculusController->GetControllerInfo());
 | |
| 
 | |
|       // Process pose state.
 | |
|       // We wanna Oculus Touch has the right position when it shows up,
 | |
|       // so we force to update the pose no matter if it has OrientationTracked
 | |
|       // or PositionTracked.
 | |
|       const uint32_t handIdx = static_cast<uint32_t>(hand) - 1;
 | |
|       GamepadPoseState poseState;
 | |
|       GetControllerPoseState(handIdx, poseState, true);
 | |
|       HandlePoseTracking(i, poseState, oculusController);
 | |
| 
 | |
|       ++mControllerCount;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| VRSystemManagerOculus::RemoveControllers()
 | |
| {
 | |
|   // controller count is changed, removing the existing gamepads first.
 | |
|   for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
 | |
|     mOculusController[i]->ShutdownVibrateHapticThread();
 | |
|     RemoveGamepad(i);
 | |
|   }
 | |
| 
 | |
|   mOculusController.Clear();
 | |
|   mControllerCount = 0;
 | |
| }
 |