diff options
Diffstat (limited to 'gfx/vr/gfxVROSVR.cpp')
-rw-r--r-- | gfx/vr/gfxVROSVR.cpp | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/gfx/vr/gfxVROSVR.cpp b/gfx/vr/gfxVROSVR.cpp new file mode 100644 index 000000000..8b275e923 --- /dev/null +++ b/gfx/vr/gfxVROSVR.cpp @@ -0,0 +1,529 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <math.h> + +#include "prlink.h" +#include "prmem.h" +#include "prenv.h" +#include "gfxPrefs.h" +#include "nsString.h" +#include "mozilla/Preferences.h" + +#include "mozilla/gfx/Quaternion.h" + +#ifdef XP_WIN +#include "../layers/d3d11/CompositorD3D11.h" +#include "../layers/d3d11/TextureD3D11.h" +#endif + +#include "gfxVROSVR.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::gfx::impl; + +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 + nsAdoptingCString osvrUtilPath = + mozilla::Preferences::GetCString("gfx.vr.osvr.utilLibPath"); + nsAdoptingCString osvrCommonPath = + mozilla::Preferences::GetCString("gfx.vr.osvr.commonLibPath"); + nsAdoptingCString osvrClientPath = + mozilla::Preferences::GetCString("gfx.vr.osvr.clientLibPath"); + nsAdoptingCString osvrClientKitPath = + mozilla::Preferences::GetCString("gfx.vr.osvr.clientKitLibPath"); + + //we need all the libs to be valid + if ((!osvrUtilPath) || (!osvrCommonPath) || (!osvrClientPath) || + (!osvrClientKitPath)) { + return false; + } + + osvrUtilLib = PR_LoadLibrary(osvrUtilPath.BeginReading()); + osvrCommonLib = PR_LoadLibrary(osvrCommonPath.BeginReading()); + osvrClientLib = PR_LoadLibrary(osvrClientPath.BeginReading()); + osvrClientKitLib = PR_LoadLibrary(osvrClientKitPath.BeginReading()); + + 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; +} + +VRDisplayOSVR::VRDisplayOSVR(OSVR_ClientContext* context, + OSVR_ClientInterface* iface, + OSVR_DisplayConfig* display) + : VRDisplayHost(VRDeviceType::OSVR) + , m_ctx(context) + , m_iface(iface) + , m_display(display) +{ + + MOZ_COUNT_CTOR_INHERITED(VRDisplayOSVR, VRDisplayHost); + + mDisplayInfo.mIsConnected = true; + mDisplayInfo.mDisplayName.AssignLiteral("OSVR HMD"); + mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None; + mDisplayInfo.mCapabilityFlags = + VRDisplayCapabilityFlags::Cap_Orientation | VRDisplayCapabilityFlags::Cap_Position; + + mDisplayInfo.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_External; + mDisplayInfo.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_Present; + + // 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); + mDisplayInfo.mEyeFOV[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); + mDisplayInfo.mEyeResolution.width = w; + mDisplayInfo.mEyeResolution.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); + } + mDisplayInfo.mEyeTranslation[eye].x = eyePose.translation.data[0]; + mDisplayInfo.mEyeTranslation[eye].y = eyePose.translation.data[1]; + mDisplayInfo.mEyeTranslation[eye].z = eyePose.translation.data[2]; + } +} + +void +VRDisplayOSVR::Destroy() +{ + // destroy non-owning pointers + m_ctx = nullptr; + m_iface = nullptr; + m_display = nullptr; +} + +void +VRDisplayOSVR::ZeroSensor() +{ + // recenter pose aka reset yaw + osvr_ClientSetRoomRotationUsingHead(*m_ctx); +} + +VRHMDSensorState +VRDisplayOSVR::GetSensorState() +{ + + //update client context before anything + //this usually goes into app's mainloop + osvr_ClientUpdate(*m_ctx); + + VRHMDSensorState result; + OSVR_TimeValue timestamp; + result.Clear(); + + OSVR_OrientationState orientation; + + OSVR_ReturnCode ret = + osvr_GetOrientationState(*m_iface, ×tamp, &orientation); + + result.timestamp = timestamp.seconds; + + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Orientation; + result.orientation[0] = orientation.data[1]; + result.orientation[1] = orientation.data[2]; + result.orientation[2] = orientation.data[3]; + result.orientation[3] = orientation.data[0]; + } + + OSVR_PositionState position; + ret = osvr_GetPositionState(*m_iface, ×tamp, &position); + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Position; + result.position[0] = position.data[0]; + result.position[1] = position.data[1]; + result.position[2] = position.data[2]; + } + + return result; +} + +VRHMDSensorState +VRDisplayOSVR::GetImmediateSensorState() +{ + return GetSensorState(); +} + +#if defined(XP_WIN) + +void +VRDisplayOSVR::SubmitFrame(TextureSourceD3D11* aSource, + const IntSize& aSize, + const VRHMDSensorState& aSensorState, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect) +{ + // XXX Add code to submit frame +} + +#endif + +void +VRDisplayOSVR::StartPresentation() +{ + // XXX Add code to start VR Presentation +} + +void +VRDisplayOSVR::StopPresentation() +{ + // XXX Add code to end VR Presentation +} + +already_AddRefed<VRDisplayManagerOSVR> +VRDisplayManagerOSVR::Create() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!gfxPrefs::VREnabled() || !gfxPrefs::VROSVREnabled()) { + return nullptr; + } + if (!LoadOSVRRuntime()) { + return nullptr; + } + RefPtr<VRDisplayManagerOSVR> manager = new VRDisplayManagerOSVR(); + return manager.forget(); +} + +void +VRDisplayManagerOSVR::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 +VRDisplayManagerOSVR::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 +VRDisplayManagerOSVR::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 +VRDisplayManagerOSVR::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 +VRDisplayManagerOSVR::Init() +{ + + // OSVR server should be running in the background + // It would load plugins and take care of detecting HMDs + if (!mOSVRInitialized) { + nsIThread* thread = nullptr; + NS_GetCurrentThread(&thread); + mOSVRThread = already_AddRefed<nsIThread>(thread); + + // initialize client context + InitializeClientContext(); + // try to initialize interface + InitializeInterface(); + // try to initialize display object + InitializeDisplay(); + // verify all components are initialized + CheckOSVRStatus(); + } + + return mOSVRInitialized; +} + +void +VRDisplayManagerOSVR::Destroy() +{ + if (mOSVRInitialized) { + MOZ_ASSERT(NS_GetCurrentThread() == mOSVRThread); + mOSVRThread = nullptr; + mHMDInfo = nullptr; + 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 +VRDisplayManagerOSVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) +{ + // make sure context, interface and display are initialized + CheckOSVRStatus(); + + if (!mOSVRInitialized) { + return; + } + + mHMDInfo = new VRDisplayOSVR(&m_ctx, &m_iface, &m_display); + + if (mHMDInfo) { + aHMDResult.AppendElement(mHMDInfo); + } +} |