/* -*- 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 support only available for Windows" #endif #include #include #include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/GamepadEventTypes.h" #include "mozilla/dom/GamepadBinding.h" #include "mozilla/gfx/DeviceManagerDx.h" #include "mozilla/SharedLibrary.h" #include "OculusSession.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 using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::layers; 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 static const uint32_t kNumOculusButtons = 6; static const uint32_t kNumOculusHaptcs = 1; static const uint32_t kNumOculusAxes = 2; ovrControllerType OculusControllerTypes[2] = {ovrControllerType_LTouch, ovrControllerType_RTouch}; const char* OculusControllerNames[2] = {"Oculus Touch (Left)", "Oculus Touch (Right)"}; dom::GamepadHand OculusControllerHand[2] = {dom::GamepadHand::Left, dom::GamepadHand::Right}; ovrButton OculusControllerButtons[2][kNumOculusButtons] = { {ovrButton_LThumb, (ovrButton)0, (ovrButton)0, ovrButton_X, ovrButton_Y, (ovrButton)0}, {ovrButton_RThumb, (ovrButton)0, (ovrButton)0, ovrButton_A, ovrButton_B, (ovrButton)0}, }; ovrTouch OculusControllerTouches[2][kNumOculusButtons] = { {ovrTouch_LThumb, ovrTouch_LIndexTrigger, (ovrTouch)0, ovrTouch_X, ovrTouch_Y, ovrTouch_LThumbRest}, {ovrTouch_RThumb, ovrTouch_RIndexTrigger, (ovrTouch)0, ovrTouch_A, ovrTouch_B, ovrTouch_RThumbRest}, }; void UpdateButton(const ovrInputState& aInputState, uint32_t aHandIdx, uint32_t aButtonIdx, VRControllerState& aControllerState) { if (aInputState.Buttons & OculusControllerButtons[aHandIdx][aButtonIdx]) { aControllerState.buttonPressed |= ((uint64_t)1 << aButtonIdx); } if (aInputState.Touches & OculusControllerTouches[aHandIdx][aButtonIdx]) { aControllerState.buttonTouched |= ((uint64_t)1 << aButtonIdx); } } 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; } } // anonymous namespace namespace mozilla { namespace gfx { OculusSession::OculusSession() : VRSession(), mOvrLib(nullptr), mSession(nullptr), mInitFlags((ovrInitFlags)0), mTextureSet(nullptr), mQuadVS(nullptr), mQuadPS(nullptr), mLinearSamplerState(nullptr), mVSConstantBuffer(nullptr), mPSConstantBuffer(nullptr), mVertexBuffer(nullptr), mInputLayout(nullptr), mRemainingVibrateTime{}, mHapticPulseIntensity{}, mIsPresenting(false) {} OculusSession::~OculusSession() { Shutdown(); } bool OculusSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, bool aDetectRuntimesOnly) { if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_oculus_enabled_AtStartup()) { return false; } if (aDetectRuntimesOnly) { if (LoadOvrLib()) { aSystemState.displayState.capabilityFlags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR; } return false; } if (!CreateD3DObjects()) { return false; } if (!CreateShaders()) { return false; } // Ideally, we should move LoadOvrLib() up to the first line to avoid // unnecessary D3D objects creation. But it will cause a WPT fail in Win 7 // debug. if (!LoadOvrLib()) { return false; } // We start off with an invisible session, then re-initialize // with visible session once WebVR content starts rendering. if (!ChangeVisibility(false)) { return false; } if (!InitState(aSystemState)) { return false; } mPresentationSize = IntSize(aSystemState.displayState.eyeResolution.width * 2, aSystemState.displayState.eyeResolution.height); return true; } void OculusSession::UpdateVisibility() { // 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. if (mIsPresenting) { // We are currently rendering immersive content. // Avoid interrupting the session return; } if (mInitFlags & ovrInit_Invisible) { // We are already invisible return; } if (mLastPresentationEnd.IsNull()) { // There has been no presentation yet return; } TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd; TimeDuration timeout = TimeDuration::FromMilliseconds( StaticPrefs::dom_vr_oculus_present_timeout()); if (timeout <= TimeDuration(0) || duration >= timeout) { if (!ChangeVisibility(false)) { gfxWarning() << "OculusSession::ChangeVisibility(false) failed"; } } } void OculusSession::CoverTransitions() { // While content is loading or during immersive-mode link // traversal, we need to prevent the user from seeing the // last rendered frame. // We render black frames to cover up the transition. MOZ_ASSERT(mSession); if (mIsPresenting) { // We are currently rendering immersive content. // Avoid interrupting the session return; } if (mInitFlags & ovrInit_Invisible) { // We are invisible, nothing to cover up return; } // Render a black frame ovrLayerEyeFov layer; memset(&layer, 0, sizeof(layer)); layer.Header.Type = ovrLayerType_Disabled; ovrLayerHeader* layers = &layer.Header; ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1); } bool OculusSession::ChangeVisibility(bool bVisible) { ovrInitFlags flags = (ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering); if (StaticPrefs::dom_vr_oculus_invisible_enabled() && !bVisible) { flags = (ovrInitFlags)(flags | ovrInit_Invisible); } if (mInitFlags == flags) { // The new state is the same, nothing to do return true; } // Tear everything down StopRendering(); StopSession(); StopLib(); // Start it back up if (!StartLib(flags)) { return false; } if (!StartSession()) { return false; } return true; } void OculusSession::Shutdown() { StopRendering(); StopSession(); StopLib(); UnloadOvrLib(); DestroyShaders(); } void OculusSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) { if (!mSession) { return; } ovrSessionStatus status; if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) { aSystemState.displayState.isConnected = status.HmdPresent; aSystemState.displayState.isMounted = status.HmdMounted; mShouldQuit = status.ShouldQuit; } else { aSystemState.displayState.isConnected = false; aSystemState.displayState.isMounted = false; } UpdateHaptics(); UpdateVisibility(); CoverTransitions(); } void OculusSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { UpdateHeadsetPose(aSystemState); UpdateEyeParameters(aSystemState); UpdateControllers(aSystemState); UpdateTelemetry(aSystemState); aSystemState.sensorState.inputFrameID++; } bool OculusSession::StartPresentation() { /** * 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. **/ if (!ChangeVisibility(true)) { return false; } if (!StartRendering()) { StopRendering(); return false; } mIsPresenting = true; return true; } void OculusSession::StopPresentation() { mLastPresentationEnd = TimeStamp::Now(); mIsPresenting = false; } bool OculusSession::SubmitFrame( const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, ID3D11Texture2D* aTexture) { if (!IsPresentationReady()) { return false; } D3D11_TEXTURE2D_DESC textureDesc = {0}; aTexture->GetDesc(&textureDesc); int currentRenderTarget = 0; ovrResult orv = ovr_GetTextureSwapChainCurrentIndex(mSession, mTextureSet, ¤tRenderTarget); if (orv != ovrSuccess) { NS_WARNING("ovr_GetTextureSwapChainCurrentIndex failed."); return false; } ID3D11RenderTargetView* view = mRTView[currentRenderTarget]; float clear[] = {0.0f, 0.0f, 0.0f, 1.0f}; mContext->ClearRenderTargetView(view, clear); mContext->OMSetRenderTargets(1, &view, nullptr); Matrix viewMatrix = Matrix::Translation(-1.0, 1.0); viewMatrix.PreScale(2.0f / float(textureDesc.Width), 2.0f / float(textureDesc.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 = textureDesc.Width; viewport.Height = textureDesc.Height; viewport.TopLeftX = 0; viewport.TopLeftY = 0; D3D11_RECT scissor; scissor.left = 0; scissor.right = textureDesc.Width; scissor.top = 0; scissor.bottom = textureDesc.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, textureDesc.Width, textureDesc.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 srView; HRESULT hr = mDevice->CreateShaderResourceView(aTexture, 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); orv = ovr_CommitTextureSwapChain(mSession, mTextureSet); if (orv != ovrSuccess) { NS_WARNING("ovr_CommitTextureSwapChain failed."); return false; } ovrLayerEyeFov layer; memset(&layer, 0, sizeof(layer)); layer.Header.Type = ovrLayerType_EyeFov; layer.Header.Flags = 0; layer.ColorTexture[0] = mTextureSet; layer.ColorTexture[1] = nullptr; layer.Fov[0] = mFOVPort[0]; layer.Fov[1] = mFOVPort[1]; layer.Viewport[0].Pos.x = textureDesc.Width * aLayer.leftEyeRect.x; layer.Viewport[0].Pos.y = textureDesc.Height * aLayer.leftEyeRect.y; layer.Viewport[0].Size.w = textureDesc.Width * aLayer.leftEyeRect.width; layer.Viewport[0].Size.h = textureDesc.Height * aLayer.leftEyeRect.height; layer.Viewport[1].Pos.x = textureDesc.Width * aLayer.rightEyeRect.x; layer.Viewport[1].Pos.y = textureDesc.Height * aLayer.rightEyeRect.y; layer.Viewport[1].Size.w = textureDesc.Width * aLayer.rightEyeRect.width; layer.Viewport[1].Size.h = textureDesc.Height * aLayer.rightEyeRect.height; for (uint32_t i = 0; i < 2; ++i) { layer.RenderPose[i].Orientation.x = mFrameStartPose[i].Orientation.x; layer.RenderPose[i].Orientation.y = mFrameStartPose[i].Orientation.y; layer.RenderPose[i].Orientation.z = mFrameStartPose[i].Orientation.z; layer.RenderPose[i].Orientation.w = mFrameStartPose[i].Orientation.w; layer.RenderPose[i].Position.x = mFrameStartPose[i].Position.x; layer.RenderPose[i].Position.y = mFrameStartPose[i].Position.y; layer.RenderPose[i].Position.z = mFrameStartPose[i].Position.z; } ovrLayerHeader* layers = &layer.Header; orv = ovr_SubmitFrame(mSession, 0, 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; } return true; } bool OculusSession::LoadOvrLib() { if (mOvrLib) { // Already loaded, early exit return true; } #if defined(_WIN32) nsTArray libSearchPaths; nsString libName; nsString searchPath; for (;;) { UINT requiredLength = ::GetSystemDirectoryW( char16ptr_t(searchPath.BeginWriting()), searchPath.Length()); if (!requiredLength) { break; } if (requiredLength < searchPath.Length()) { searchPath.Truncate(requiredLength); libSearchPaths.AppendElement(searchPath); break; } searchPath.SetLength(requiredLength); } 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"); } if (libName.IsEmpty()) { return false; } for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) { nsString& libPath = libSearchPaths[i]; nsString fullName; if (libPath.Length() == 0) { fullName.Assign(libName); } else { fullName.Assign(libPath + NS_LITERAL_STRING(u"\\") + libName); } 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; } void OculusSession::UnloadOvrLib() { if (mOvrLib) { PR_UnloadLibrary(mOvrLib); mOvrLib = nullptr; } } bool OculusSession::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; } void OculusSession::StopLib() { if (mInitFlags) { ovr_Shutdown(); mInitFlags = (ovrInitFlags)0; } } bool OculusSession::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; } void OculusSession::StopSession() { if (mSession) { ovr_Destroy(mSession); mSession = nullptr; } } bool OculusSession::CreateD3DObjects() { RefPtr device = gfx::DeviceManagerDx::Get()->GetVRDevice(); if (!device) { return false; } if (!CreateD3DContext(device)) { return false; } return true; } bool OculusSession::CreateShaders() { if (!mQuadVS) { if (FAILED(mDevice->CreateVertexShader( sLayerQuadVS.mData, sLayerQuadVS.mLength, nullptr, &mQuadVS))) { NS_WARNING("Failed to create vertex shader for Oculus"); return false; } } if (!mQuadPS) { if (FAILED(mDevice->CreatePixelShader(sRGBShader.mData, sRGBShader.mLength, nullptr, &mQuadPS))) { NS_WARNING("Failed to create pixel shader for Oculus"); return false; } } 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 false; } } 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 false; } } 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 false; } } 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 false; } } 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 false; } } memset(&mVSConstants, 0, sizeof(mVSConstants)); memset(&mPSConstants, 0, sizeof(mPSConstants)); return true; } void OculusSession::DestroyShaders() {} bool OculusSession::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 OculusSession::StartRendering() { 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; } int textureCount = 0; orv = ovr_GetTextureSwapChainLength(mSession, mTextureSet, &textureCount); if (orv != ovrSuccess) { NS_WARNING("ovr_GetTextureSwapChainLength failed"); return false; } mTexture.SetLength(textureCount); mRTView.SetLength(textureCount); mSRV.SetLength(textureCount); for (int i = 0; i < textureCount; ++i) { ID3D11Texture2D* texture = nullptr; orv = ovr_GetTextureSwapChainBufferDX(mSession, mTextureSet, i, IID_PPV_ARGS(&texture)); if (orv != ovrSuccess) { NS_WARNING("Failed to create Oculus texture swap chain."); return false; } RefPtr rtView; CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc(D3D11_RTV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM); HRESULT hr = mDevice->CreateRenderTargetView(texture, &rtvDesc, getter_AddRefs(rtView)); if (FAILED(hr)) { NS_WARNING( "Failed to create RenderTargetView for Oculus texture swap chain."); texture->Release(); return false; } RefPtr srv; CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM); hr = mDevice->CreateShaderResourceView(texture, &srvDesc, getter_AddRefs(srv)); if (FAILED(hr)) { NS_WARNING( "Failed to create ShaderResourceView for Oculus texture swap " "chain."); texture->Release(); return false; } mTexture[i] = texture; mRTView[i] = rtView; mSRV[i] = srv; texture->Release(); } } return true; } bool OculusSession::IsPresentationReady() const { return mTextureSet != nullptr; } void OculusSession::StopRendering() { mSRV.Clear(); mRTView.Clear(); mTexture.Clear(); if (mTextureSet && mSession) { ovr_DestroyTextureSwapChain(mSession, mTextureSet); } mTextureSet = nullptr; mIsPresenting = false; } bool OculusSession::InitState(VRSystemState& aSystemState) { VRDisplayState& state = aSystemState.displayState; strncpy(state.displayName, "Oculus VR HMD", kVRDisplayNameMaxLen); state.isConnected = true; state.isMounted = false; ovrHmdDesc desc = ovr_GetHmdDesc(mSession); state.capabilityFlags = VRDisplayCapabilityFlags::Cap_None; if (desc.AvailableTrackingCaps & ovrTrackingCap_Orientation) { state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Orientation; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration; } if (desc.AvailableTrackingCaps & ovrTrackingCap_Position) { state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Position; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_StageParameters; } state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_External; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_MountDetection; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Present; state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR; state.blendMode = VRDisplayBlendMode::Opaque; state.reportsDroppedFrames = true; mFOVPort[VRDisplayState::Eye_Left] = desc.DefaultEyeFov[ovrEye_Left]; mFOVPort[VRDisplayState::Eye_Right] = desc.DefaultEyeFov[ovrEye_Right]; state.eyeFOV[VRDisplayState::Eye_Left] = FromFovPort(mFOVPort[VRDisplayState::Eye_Left]); state.eyeFOV[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, (ovrEyeType)eye, mFOVPort[eye], pixelsPerDisplayPixel); } // take the max of both for eye resolution state.eyeResolution.width = std::max(texSize[VRDisplayState::Eye_Left].w, texSize[VRDisplayState::Eye_Right].w); state.eyeResolution.height = std::max(texSize[VRDisplayState::Eye_Left].h, texSize[VRDisplayState::Eye_Right].h); // default to an identity quaternion aSystemState.sensorState.pose.orientation[3] = 1.0f; UpdateStageParameters(state); UpdateEyeParameters(aSystemState); VRHMDSensorState& sensorState = aSystemState.sensorState; sensorState.flags = (VRDisplayCapabilityFlags)( (int)VRDisplayCapabilityFlags::Cap_Orientation | (int)VRDisplayCapabilityFlags::Cap_Position); sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion return true; } void OculusSession::UpdateStageParameters(VRDisplayState& aState) { ovrVector3f playArea; ovrResult res = ovr_GetBoundaryDimensions(mSession, ovrBoundary_PlayArea, &playArea); if (res == ovrSuccess) { aState.stageSize.width = playArea.x; aState.stageSize.height = playArea.z; } else { // If we fail, fall back to reasonable defaults. // 1m x 1m space aState.stageSize.width = 1.0f; aState.stageSize.height = 1.0f; } float eyeHeight = ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); aState.sittingToStandingTransform[0] = 1.0f; aState.sittingToStandingTransform[1] = 0.0f; aState.sittingToStandingTransform[2] = 0.0f; aState.sittingToStandingTransform[3] = 0.0f; aState.sittingToStandingTransform[4] = 0.0f; aState.sittingToStandingTransform[5] = 1.0f; aState.sittingToStandingTransform[6] = 0.0f; aState.sittingToStandingTransform[7] = 0.0f; aState.sittingToStandingTransform[8] = 0.0f; aState.sittingToStandingTransform[9] = 0.0f; aState.sittingToStandingTransform[10] = 1.0f; aState.sittingToStandingTransform[11] = 0.0f; aState.sittingToStandingTransform[12] = 0.0f; aState.sittingToStandingTransform[13] = eyeHeight; aState.sittingToStandingTransform[14] = 0.0f; aState.sittingToStandingTransform[15] = 1.0f; } void OculusSession::UpdateEyeParameters(VRSystemState& aState) { if (!mSession) { return; } // This must be called every frame in order to // account for continuous adjustments to ipd. gfx::Matrix4x4 headToEyeTransforms[2]; for (uint32_t eye = 0; eye < 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, (ovrEyeType)eye, mFOVPort[eye]); aState.displayState.eyeTranslation[eye].x = renderDesc.HmdToEyePose.Position.x; aState.displayState.eyeTranslation[eye].y = renderDesc.HmdToEyePose.Position.y; aState.displayState.eyeTranslation[eye].z = renderDesc.HmdToEyePose.Position.z; 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(); headToEyeTransforms[eye] = pose; } aState.sensorState.CalcViewMatrices(headToEyeTransforms); Matrix4x4 matView[2]; memcpy(matView[0].components, aState.sensorState.leftViewMatrix, sizeof(float) * 16); memcpy(matView[1].components, aState.sensorState.rightViewMatrix, sizeof(float) * 16); for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) { Point3D eyeTranslation; Quaternion eyeRotation; Point3D eyeScale; if (!matView[eye].Decompose(eyeTranslation, eyeRotation, eyeScale)) { NS_WARNING("Failed to decompose eye pose matrix for Oculus"); } mFrameStartPose[eye].Orientation.x = eyeRotation.x; mFrameStartPose[eye].Orientation.y = eyeRotation.y; mFrameStartPose[eye].Orientation.z = eyeRotation.z; mFrameStartPose[eye].Orientation.w = eyeRotation.w; mFrameStartPose[eye].Position.x = eyeTranslation.x; mFrameStartPose[eye].Position.y = eyeTranslation.y; mFrameStartPose[eye].Position.z = eyeTranslation.z; } } void OculusSession::UpdateHeadsetPose(VRSystemState& aState) { if (!mSession) { return; } double predictedFrameTime = 0.0f; if (StaticPrefs::dom_vr_poseprediction_enabled()) { // 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, 0); } ovrTrackingState trackingState = ovr_GetTrackingState(mSession, predictedFrameTime, true); ovrPoseStatef& pose(trackingState.HeadPose); aState.sensorState.timestamp = pose.TimeInSeconds; if (trackingState.StatusFlags & ovrStatus_OrientationTracked) { aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation; aState.sensorState.pose.orientation[0] = pose.ThePose.Orientation.x; aState.sensorState.pose.orientation[1] = pose.ThePose.Orientation.y; aState.sensorState.pose.orientation[2] = pose.ThePose.Orientation.z; aState.sensorState.pose.orientation[3] = pose.ThePose.Orientation.w; aState.sensorState.pose.angularVelocity[0] = pose.AngularVelocity.x; aState.sensorState.pose.angularVelocity[1] = pose.AngularVelocity.y; aState.sensorState.pose.angularVelocity[2] = pose.AngularVelocity.z; aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration; aState.sensorState.pose.angularAcceleration[0] = pose.AngularAcceleration.x; aState.sensorState.pose.angularAcceleration[1] = pose.AngularAcceleration.y; aState.sensorState.pose.angularAcceleration[2] = pose.AngularAcceleration.z; } else { // default to an identity quaternion aState.sensorState.pose.orientation[3] = 1.0f; } if (trackingState.StatusFlags & ovrStatus_PositionTracked) { float eyeHeight = ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position; aState.sensorState.pose.position[0] = pose.ThePose.Position.x; aState.sensorState.pose.position[1] = pose.ThePose.Position.y - eyeHeight; aState.sensorState.pose.position[2] = pose.ThePose.Position.z; aState.sensorState.pose.linearVelocity[0] = pose.LinearVelocity.x; aState.sensorState.pose.linearVelocity[1] = pose.LinearVelocity.y; aState.sensorState.pose.linearVelocity[2] = pose.LinearVelocity.z; aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration; aState.sensorState.pose.linearAcceleration[0] = pose.LinearAcceleration.x; aState.sensorState.pose.linearAcceleration[1] = pose.LinearAcceleration.y; aState.sensorState.pose.linearAcceleration[2] = pose.LinearAcceleration.z; } aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_External; aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_MountDetection; aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Present; } void OculusSession::UpdateControllers(VRSystemState& aState) { if (!mSession) { return; } ovrInputState inputState; bool hasInputState = ovr_GetInputState(mSession, ovrControllerType_Touch, &inputState) == ovrSuccess; if (!hasInputState) { return; } EnumerateControllers(aState, inputState); UpdateControllerInputs(aState, inputState); UpdateControllerPose(aState, inputState); } void OculusSession::UpdateControllerPose(VRSystemState& aState, const ovrInputState& aInputState) { ovrTrackingState trackingState = ovr_GetTrackingState(mSession, 0.0, false); for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { // Left Touch Controller will always be at index 0 and // and Right Touch Controller will always be at index 1 VRControllerState& controllerState = aState.controllerState[handIdx]; if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { ovrPoseStatef& pose = trackingState.HandPoses[handIdx]; bool bNewController = !(controllerState.flags & dom::GamepadCapabilityFlags::Cap_Orientation); if (bNewController) { controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Orientation; controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Position; controllerState.flags |= dom::GamepadCapabilityFlags::Cap_AngularAcceleration; controllerState.flags |= dom::GamepadCapabilityFlags::Cap_LinearAcceleration; } if (bNewController || trackingState.HandStatusFlags[handIdx] & ovrStatus_OrientationTracked) { controllerState.pose.orientation[0] = pose.ThePose.Orientation.x; controllerState.pose.orientation[1] = pose.ThePose.Orientation.y; controllerState.pose.orientation[2] = pose.ThePose.Orientation.z; controllerState.pose.orientation[3] = pose.ThePose.Orientation.w; controllerState.pose.angularVelocity[0] = pose.AngularVelocity.x; controllerState.pose.angularVelocity[1] = pose.AngularVelocity.y; controllerState.pose.angularVelocity[2] = pose.AngularVelocity.z; controllerState.pose.angularAcceleration[0] = pose.AngularAcceleration.x; controllerState.pose.angularAcceleration[1] = pose.AngularAcceleration.y; controllerState.pose.angularAcceleration[2] = pose.AngularAcceleration.z; controllerState.isOrientationValid = true; } else { controllerState.isOrientationValid = false; } if (bNewController || trackingState.HandStatusFlags[handIdx] & ovrStatus_PositionTracked) { controllerState.pose.position[0] = pose.ThePose.Position.x; controllerState.pose.position[1] = pose.ThePose.Position.y; controllerState.pose.position[2] = pose.ThePose.Position.z; controllerState.pose.linearVelocity[0] = pose.LinearVelocity.x; controllerState.pose.linearVelocity[1] = pose.LinearVelocity.y; controllerState.pose.linearVelocity[2] = pose.LinearVelocity.z; controllerState.pose.linearAcceleration[0] = pose.LinearAcceleration.x; controllerState.pose.linearAcceleration[1] = pose.LinearAcceleration.y; controllerState.pose.linearAcceleration[2] = pose.LinearAcceleration.z; float eyeHeight = ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); controllerState.pose.position[1] -= eyeHeight; controllerState.isPositionValid = true; } else { controllerState.isPositionValid = false; } } } } void OculusSession::EnumerateControllers(VRSystemState& aState, const ovrInputState& aInputState) { for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { // Left Touch Controller will always be at index 0 and // and Right Touch Controller will always be at index 1 VRControllerState& controllerState = aState.controllerState[handIdx]; if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { bool bNewController = false; // Left Touch Controller detected if (controllerState.controllerName[0] == '\0') { // Controller has been just enumerated strncpy(controllerState.controllerName, OculusControllerNames[handIdx], kVRControllerNameMaxLen); controllerState.hand = OculusControllerHand[handIdx]; controllerState.numButtons = kNumOculusButtons; controllerState.numAxes = kNumOculusAxes; controllerState.numHaptics = kNumOculusHaptcs; bNewController = true; } } else { // Left Touch Controller not detected if (controllerState.controllerName[0] != '\0') { // Clear any newly disconnected ontrollers memset(&controllerState, 0, sizeof(VRControllerState)); } } } } void OculusSession::UpdateControllerInputs(VRSystemState& aState, const ovrInputState& aInputState) { const float triggerThreshold = StaticPrefs::dom_vr_controller_trigger_threshold(); for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { // Left Touch Controller will always be at index 0 and // and Right Touch Controller will always be at index 1 VRControllerState& controllerState = aState.controllerState[handIdx]; if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { // Update Button States controllerState.buttonPressed = 0; controllerState.buttonTouched = 0; uint32_t buttonIdx = 0; UpdateButton(aInputState, handIdx, buttonIdx, controllerState); buttonIdx++; UpdateTrigger(controllerState, buttonIdx, aInputState.IndexTrigger[handIdx], triggerThreshold); UpdateButton(aInputState, handIdx, buttonIdx, controllerState); buttonIdx++; UpdateTrigger(controllerState, buttonIdx, aInputState.HandTrigger[handIdx], triggerThreshold); buttonIdx++; UpdateButton(aInputState, handIdx, buttonIdx, controllerState); buttonIdx++; UpdateButton(aInputState, handIdx, buttonIdx, controllerState); buttonIdx++; UpdateButton(aInputState, handIdx, buttonIdx, controllerState); buttonIdx++; MOZ_ASSERT(buttonIdx == kNumOculusButtons); // Update Thumbstick axis uint32_t axisIdx = 0; float axisValue = aInputState.Thumbstick[handIdx].x; if (abs(axisValue) < 0.0000009f) { axisValue = 0.0f; // Clear noise signal } controllerState.axisValue[axisIdx] = axisValue; axisIdx++; // Note that y axis is intentionally inverted! axisValue = -aInputState.Thumbstick[handIdx].y; if (abs(axisValue) < 0.0000009f) { axisValue = 0.0f; // Clear noise signal } controllerState.axisValue[axisIdx] = axisValue; axisIdx++; MOZ_ASSERT(axisIdx == kNumOculusAxes); } } } void OculusSession::UpdateTelemetry(VRSystemState& aSystemState) { if (!mSession) { return; } ovrPerfStats perfStats; if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) { if (perfStats.FrameStatsCount) { aSystemState.displayState.droppedFrameCount = perfStats.FrameStats[0].AppDroppedFrameCount; } } } void OculusSession::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, float aIntensity, float aDuration) { if (!mSession) { return; } if (aDuration <= 0.0f) { StopVibrateHaptic(aControllerIdx); return; } // Vibration amplitude in the [0.0, 1.0] range MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1); mHapticPulseIntensity[aControllerIdx] = aIntensity > 1.0 ? 1.0 : aIntensity; mRemainingVibrateTime[aControllerIdx] = aDuration; ovrControllerType hand = OculusControllerTypes[aControllerIdx]; // The gamepad extensions API does not yet have independent control // of frequency and amplitude. We are always sending 0.0f (160hz) // to the frequency argument. ovrResult result = ovr_SetControllerVibration( mSession, hand, 0.0f, mHapticPulseIntensity[aControllerIdx]); if (result != ovrSuccess) { // This may happen if called when not presenting. gfxWarning() << "ovr_SetControllerVibration failed."; } } void OculusSession::StopVibrateHaptic(uint32_t aControllerIdx) { if (!mSession) { return; } MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1); ovrControllerType hand = OculusControllerTypes[aControllerIdx]; mRemainingVibrateTime[aControllerIdx] = 0.0f; mHapticPulseIntensity[aControllerIdx] = 0.0f; ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f, 0.0f); if (result != ovrSuccess) { // This may happen if called when not presenting. gfxWarning() << "ovr_SetControllerVibration failed."; } } void OculusSession::StopAllHaptics() { // Left Oculus Touch StopVibrateHaptic(0); // Right Oculus Touch StopVibrateHaptic(1); } void OculusSession::UpdateHaptics() { if (!mSession) { return; } // The Oculus API and hardware takes at least 33ms to respond // to haptic state changes, so it is not beneficial to create // a dedicated haptic feedback thread and update multiple // times per frame. // If we wish to support more accurate effects with sub-frame timing, // we should use the buffered haptic feedback API's. TimeStamp now = TimeStamp::Now(); if (mLastHapticUpdate.IsNull()) { mLastHapticUpdate = now; return; } float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds(); mLastHapticUpdate = now; for (int i = 0; i < 2; i++) { if (mRemainingVibrateTime[i] <= 0.0f) { continue; } mRemainingVibrateTime[i] -= deltaTime; ovrControllerType hand = OculusControllerTypes[i]; if (mRemainingVibrateTime[i] > 0.0f) { ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f, mHapticPulseIntensity[i]); if (result != ovrSuccess) { // This may happen if called when not presenting. gfxWarning() << "ovr_SetControllerVibration failed."; } } else { StopVibrateHaptic(i); } } } } // namespace gfx } // namespace mozilla