diff options
Diffstat (limited to 'gfx/vr/service/OSVRSession.cpp')
-rw-r--r-- | gfx/vr/service/OSVRSession.cpp | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/gfx/vr/service/OSVRSession.cpp b/gfx/vr/service/OSVRSession.cpp new file mode 100644 index 0000000000..486cb03286 --- /dev/null +++ b/gfx/vr/service/OSVRSession.cpp @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OSVRSession.h" +#include "prenv.h" +#include "nsString.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/SharedLibrary.h" +#include "mozilla/gfx/Quaternion.h" + +#if defined(XP_WIN) +# include <d3d11.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#endif // defined(XP_WIN) + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace { +// need to typedef functions that will be used in the code below +extern "C" { +typedef OSVR_ClientContext (*pfn_osvrClientInit)( + const char applicationIdentifier[], uint32_t flags); +typedef OSVR_ReturnCode (*pfn_osvrClientShutdown)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientUpdate)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientCheckStatus)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientGetInterface)( + OSVR_ClientContext ctx, const char path[], OSVR_ClientInterface* iface); +typedef OSVR_ReturnCode (*pfn_osvrClientFreeInterface)( + OSVR_ClientContext ctx, OSVR_ClientInterface iface); +typedef OSVR_ReturnCode (*pfn_osvrGetOrientationState)( + OSVR_ClientInterface iface, OSVR_TimeValue* timestamp, + OSVR_OrientationState* state); +typedef OSVR_ReturnCode (*pfn_osvrGetPositionState)(OSVR_ClientInterface iface, + OSVR_TimeValue* timestamp, + OSVR_PositionState* state); +typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplay)(OSVR_ClientContext ctx, + OSVR_DisplayConfig* disp); +typedef OSVR_ReturnCode (*pfn_osvrClientFreeDisplay)(OSVR_DisplayConfig disp); +typedef OSVR_ReturnCode (*pfn_osvrClientGetNumEyesForViewer)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount* eyes); +typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyePose)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_Pose3* pose); +typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplayDimensions)( + OSVR_DisplayConfig disp, OSVR_DisplayInputCount displayInputIndex, + OSVR_DisplayDimension* width, OSVR_DisplayDimension* height); +typedef OSVR_ReturnCode ( + *pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, double* left, double* right, double* bottom, + double* top); +typedef OSVR_ReturnCode (*pfn_osvrClientGetRelativeViewportForViewerEyeSurface)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_ViewportDimension* left, + OSVR_ViewportDimension* bottom, OSVR_ViewportDimension* width, + OSVR_ViewportDimension* height); +typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, float near, float far, + OSVR_MatrixConventions flags, float* matrix); +typedef OSVR_ReturnCode (*pfn_osvrClientCheckDisplayStartup)( + OSVR_DisplayConfig disp); +typedef OSVR_ReturnCode (*pfn_osvrClientSetRoomRotationUsingHead)( + OSVR_ClientContext ctx); +} + +static pfn_osvrClientInit osvr_ClientInit = nullptr; +static pfn_osvrClientShutdown osvr_ClientShutdown = nullptr; +static pfn_osvrClientUpdate osvr_ClientUpdate = nullptr; +static pfn_osvrClientCheckStatus osvr_ClientCheckStatus = nullptr; +static pfn_osvrClientGetInterface osvr_ClientGetInterface = nullptr; +static pfn_osvrClientFreeInterface osvr_ClientFreeInterface = nullptr; +static pfn_osvrGetOrientationState osvr_GetOrientationState = nullptr; +static pfn_osvrGetPositionState osvr_GetPositionState = nullptr; +static pfn_osvrClientGetDisplay osvr_ClientGetDisplay = nullptr; +static pfn_osvrClientFreeDisplay osvr_ClientFreeDisplay = nullptr; +static pfn_osvrClientGetNumEyesForViewer osvr_ClientGetNumEyesForViewer = + nullptr; +static pfn_osvrClientGetViewerEyePose osvr_ClientGetViewerEyePose = nullptr; +static pfn_osvrClientGetDisplayDimensions osvr_ClientGetDisplayDimensions = + nullptr; +static pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes + osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes = nullptr; +static pfn_osvrClientGetRelativeViewportForViewerEyeSurface + osvr_ClientGetRelativeViewportForViewerEyeSurface = nullptr; +static pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf + osvr_ClientGetViewerEyeSurfaceProjectionMatrixf = nullptr; +static pfn_osvrClientCheckDisplayStartup osvr_ClientCheckDisplayStartup = + nullptr; +static pfn_osvrClientSetRoomRotationUsingHead + osvr_ClientSetRoomRotationUsingHead = nullptr; + +bool LoadOSVRRuntime() { + static PRLibrary* osvrUtilLib = nullptr; + static PRLibrary* osvrCommonLib = nullptr; + static PRLibrary* osvrClientLib = nullptr; + static PRLibrary* osvrClientKitLib = nullptr; + // this looks up the path in the about:config setting, from greprefs.js or + // modules\libpref\init\all.js we need all the libs to be valid +#ifdef XP_WIN + constexpr static auto* pfnGetPathStringPref = mozilla::Preferences::GetString; + nsAutoString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath; +#else + constexpr static auto* pfnGetPathStringPref = + mozilla::Preferences::GetCString; + nsAutoCString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath; +#endif + if (NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.utilLibPath", osvrUtilPath, + PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.commonLibPath", + osvrCommonPath, PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientLibPath", + osvrClientPath, PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientKitLibPath", + osvrClientKitPath, PrefValueKind::User))) { + return false; + } + + osvrUtilLib = LoadLibraryWithFlags(osvrUtilPath.get()); + osvrCommonLib = LoadLibraryWithFlags(osvrCommonPath.get()); + osvrClientLib = LoadLibraryWithFlags(osvrClientPath.get()); + osvrClientKitLib = LoadLibraryWithFlags(osvrClientKitPath.get()); + + if (!osvrUtilLib) { + printf_stderr("[OSVR] Failed to load OSVR Util library!\n"); + return false; + } + if (!osvrCommonLib) { + printf_stderr("[OSVR] Failed to load OSVR Common library!\n"); + return false; + } + if (!osvrClientLib) { + printf_stderr("[OSVR] Failed to load OSVR Client library!\n"); + return false; + } + if (!osvrClientKitLib) { + printf_stderr("[OSVR] Failed to load OSVR ClientKit library!\n"); + return false; + } + +// make sure all functions that we'll be using are available +#define REQUIRE_FUNCTION(_x) \ + do { \ + *(void**)&osvr_##_x = (void*)PR_FindSymbol(osvrClientKitLib, "osvr" #_x); \ + if (!osvr_##_x) { \ + printf_stderr("osvr" #_x " symbol missing\n"); \ + goto fail; \ + } \ + } while (0) + + REQUIRE_FUNCTION(ClientInit); + REQUIRE_FUNCTION(ClientShutdown); + REQUIRE_FUNCTION(ClientUpdate); + REQUIRE_FUNCTION(ClientCheckStatus); + REQUIRE_FUNCTION(ClientGetInterface); + REQUIRE_FUNCTION(ClientFreeInterface); + REQUIRE_FUNCTION(GetOrientationState); + REQUIRE_FUNCTION(GetPositionState); + REQUIRE_FUNCTION(ClientGetDisplay); + REQUIRE_FUNCTION(ClientFreeDisplay); + REQUIRE_FUNCTION(ClientGetNumEyesForViewer); + REQUIRE_FUNCTION(ClientGetViewerEyePose); + REQUIRE_FUNCTION(ClientGetDisplayDimensions); + REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionClippingPlanes); + REQUIRE_FUNCTION(ClientGetRelativeViewportForViewerEyeSurface); + REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionMatrixf); + REQUIRE_FUNCTION(ClientCheckDisplayStartup); + REQUIRE_FUNCTION(ClientSetRoomRotationUsingHead); + +#undef REQUIRE_FUNCTION + + return true; + +fail: + return false; +} + +} // namespace + +mozilla::gfx::VRFieldOfView SetFromTanRadians(double left, double right, + double bottom, double top) { + mozilla::gfx::VRFieldOfView fovInfo; + fovInfo.leftDegrees = atan(left) * 180.0 / M_PI; + fovInfo.rightDegrees = atan(right) * 180.0 / M_PI; + fovInfo.upDegrees = atan(top) * 180.0 / M_PI; + fovInfo.downDegrees = atan(bottom) * 180.0 / M_PI; + return fovInfo; +} + +OSVRSession::OSVRSession() + : VRSession(), + mRuntimeLoaded(false), + mOSVRInitialized(false), + mClientContextInitialized(false), + mDisplayConfigInitialized(false), + mInterfaceInitialized(false), + m_ctx(nullptr), + m_iface(nullptr), + m_display(nullptr) {} +OSVRSession::~OSVRSession() { Shutdown(); } + +bool OSVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) { + if (StaticPrefs::dom_vr_puppet_enabled()) { + // Ensure that tests using the VR Puppet do not find real hardware + return false; + } + if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_osvr_enabled()) { + return false; + } + if (mOSVRInitialized) { + return true; + } + if (!LoadOSVRRuntime()) { + return false; + } + mRuntimeLoaded = true; + + if (aDetectRuntimesOnly) { + aSystemState.displayState.capabilityFlags |= + VRDisplayCapabilityFlags::Cap_ImmersiveVR; + return false; + } + + // initialize client context + InitializeClientContext(); + // try to initialize interface + InitializeInterface(); + // try to initialize display object + InitializeDisplay(); + // verify all components are initialized + CheckOSVRStatus(); + + if (!mOSVRInitialized) { + return false; + } + + if (!InitState(aSystemState)) { + return false; + } + + return true; +} + +void OSVRSession::CheckOSVRStatus() { + if (mOSVRInitialized) { + return; + } + + // client context must be initialized first + InitializeClientContext(); + + // update client context + osvr_ClientUpdate(m_ctx); + + // initialize interface and display if they are not initialized yet + InitializeInterface(); + InitializeDisplay(); + + // OSVR is fully initialized now + if (mClientContextInitialized && mDisplayConfigInitialized && + mInterfaceInitialized) { + mOSVRInitialized = true; + } +} + +void OSVRSession::InitializeClientContext() { + // already initialized + if (mClientContextInitialized) { + return; + } + + // first time creating + if (!m_ctx) { + // get client context + m_ctx = osvr_ClientInit("com.osvr.webvr", 0); + // update context + osvr_ClientUpdate(m_ctx); + // verify we are connected + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) { + mClientContextInitialized = true; + } + } + // client context exists but not up and running yet + else { + // update context + osvr_ClientUpdate(m_ctx); + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) { + mClientContextInitialized = true; + } + } +} + +void OSVRSession::InitializeInterface() { + // already initialized + if (mInterfaceInitialized) { + return; + } + // Client context must be initialized before getting interface + if (mClientContextInitialized) { + // m_iface will remain nullptr if no interface is returned + if (OSVR_RETURN_SUCCESS == + osvr_ClientGetInterface(m_ctx, "/me/head", &m_iface)) { + mInterfaceInitialized = true; + } + } +} + +void OSVRSession::InitializeDisplay() { + // display is fully configured + if (mDisplayConfigInitialized) { + return; + } + + // Client context must be initialized before getting interface + if (mClientContextInitialized) { + // first time creating display object + if (m_display == nullptr) { + OSVR_ReturnCode ret = osvr_ClientGetDisplay(m_ctx, &m_display); + + if (ret == OSVR_RETURN_SUCCESS) { + osvr_ClientUpdate(m_ctx); + // display object may have been created but not fully startup + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) { + mDisplayConfigInitialized = true; + } + } + + // Typically once we get Display object, pose data is available after + // clientUpdate but sometimes it takes ~ 200 ms to get + // a succesfull connection, so we might have to run a few update cycles + } else { + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) { + mDisplayConfigInitialized = true; + } + } + } +} + +bool OSVRSession::InitState(mozilla::gfx::VRSystemState& aSystemState) { + VRDisplayState& state = aSystemState.displayState; + strncpy(state.displayName, "OSVR HMD", kVRDisplayNameMaxLen); + state.eightCC = GFX_VR_EIGHTCC('O', 'S', 'V', 'R', ' ', ' ', ' ', ' '); + state.isConnected = true; + state.isMounted = false; + state.capabilityFlags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_None | + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position | + (int)VRDisplayCapabilityFlags::Cap_External | + (int)VRDisplayCapabilityFlags::Cap_Present | + (int)VRDisplayCapabilityFlags::Cap_ImmersiveVR); + state.blendMode = VRDisplayBlendMode::Opaque; + state.reportsDroppedFrames = false; + + // XXX OSVR display topology allows for more than one viewer + // will assume only one viewer for now (most likely stay that way) + + OSVR_EyeCount numEyes; + osvr_ClientGetNumEyesForViewer(m_display, 0, &numEyes); + + for (uint8_t eye = 0; eye < numEyes; eye++) { + double left, right, bottom, top; + // XXX for now there is only one surface per eye + osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes( + m_display, 0, eye, 0, &left, &right, &bottom, &top); + state.eyeFOV[eye] = SetFromTanRadians(-left, right, -bottom, top); + } + + // XXX Assuming there is only one display input for now + // however, it's possible to have more than one (dSight with 2 HDMI inputs) + OSVR_DisplayDimension width, height; + osvr_ClientGetDisplayDimensions(m_display, 0, &width, &height); + + for (uint8_t eye = 0; eye < numEyes; eye++) { + OSVR_ViewportDimension l, b, w, h; + osvr_ClientGetRelativeViewportForViewerEyeSurface(m_display, 0, eye, 0, &l, + &b, &w, &h); + state.eyeResolution.width = w; + state.eyeResolution.height = h; + OSVR_Pose3 eyePose; + // Viewer eye pose may not be immediately available, update client context + // until we get it + OSVR_ReturnCode ret = + osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose); + while (ret != OSVR_RETURN_SUCCESS) { + osvr_ClientUpdate(m_ctx); + ret = osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose); + } + state.eyeTranslation[eye].x = eyePose.translation.data[0]; + state.eyeTranslation[eye].y = eyePose.translation.data[1]; + state.eyeTranslation[eye].z = eyePose.translation.data[2]; + + Matrix4x4 pose; + pose.SetRotationFromQuaternion(gfx::Quaternion( + osvrQuatGetX(&eyePose.rotation), osvrQuatGetY(&eyePose.rotation), + osvrQuatGetZ(&eyePose.rotation), osvrQuatGetW(&eyePose.rotation))); + pose.PreTranslate(eyePose.translation.data[0], eyePose.translation.data[1], + eyePose.translation.data[2]); + pose.Invert(); + mHeadToEye[eye] = pose; + } + + // default to an identity quaternion + 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 OSVRSession::Shutdown() { + if (!mRuntimeLoaded) { + return; + } + mOSVRInitialized = false; + // client context may not have been initialized + if (m_ctx) { + osvr_ClientFreeDisplay(m_display); + } + // osvr checks that m_ctx or m_iface are not null + osvr_ClientFreeInterface(m_ctx, m_iface); + osvr_ClientShutdown(m_ctx); +} + +void OSVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {} + +void OSVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { + UpdateHeadsetPose(aSystemState); +} + +void OSVRSession::UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState) { + // Update client context before anything + // this usually goes into app's mainloop + osvr_ClientUpdate(m_ctx); + + VRHMDSensorState result{}; + OSVR_TimeValue timestamp; + + OSVR_OrientationState orientation; + + OSVR_ReturnCode ret = + osvr_GetOrientationState(m_iface, ×tamp, &orientation); + + aState.sensorState.timestamp = timestamp.seconds; + + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Orientation; + result.pose.orientation[0] = orientation.data[1]; + result.pose.orientation[1] = orientation.data[2]; + result.pose.orientation[2] = orientation.data[3]; + result.pose.orientation[3] = orientation.data[0]; + } else { + // default to an identity quaternion + result.pose.orientation[3] = 1.0f; + } + + OSVR_PositionState position; + ret = osvr_GetPositionState(m_iface, ×tamp, &position); + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Position; + result.pose.position[0] = position.data[0]; + result.pose.position[1] = position.data[1]; + result.pose.position[2] = position.data[2]; + } + + result.CalcViewMatrices(mHeadToEye); +} + +bool OSVRSession::StartPresentation() { + return false; + // TODO Implement +} + +void OSVRSession::StopPresentation() { + // TODO Implement +} + +#if defined(XP_WIN) +bool OSVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) { + return false; + // TODO Implement +} +#elif defined(XP_MACOSX) +bool OSVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) { + return false; + // TODO Implement +} +#endif + +void OSVRSession::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) {} + +void OSVRSession::StopVibrateHaptic(uint32_t aControllerIdx) {} + +void OSVRSession::StopAllHaptics() {} |