1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "OSVRSession.h"
8 #include "prenv.h"
9 #include "nsString.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_dom.h"
12 #include "mozilla/SharedLibrary.h"
13 #include "mozilla/gfx/Quaternion.h"
14
15 #if defined(XP_WIN)
16 # include <d3d11.h>
17 # include "mozilla/gfx/DeviceManagerDx.h"
18 #endif // defined(XP_WIN)
19
20 #ifndef M_PI
21 # define M_PI 3.14159265358979323846
22 #endif
23
24 using namespace mozilla;
25 using namespace mozilla::gfx;
26
27 namespace {
28 // need to typedef functions that will be used in the code below
29 extern "C" {
30 typedef OSVR_ClientContext (*pfn_osvrClientInit)(
31 const char applicationIdentifier[], uint32_t flags);
32 typedef OSVR_ReturnCode (*pfn_osvrClientShutdown)(OSVR_ClientContext ctx);
33 typedef OSVR_ReturnCode (*pfn_osvrClientUpdate)(OSVR_ClientContext ctx);
34 typedef OSVR_ReturnCode (*pfn_osvrClientCheckStatus)(OSVR_ClientContext ctx);
35 typedef OSVR_ReturnCode (*pfn_osvrClientGetInterface)(
36 OSVR_ClientContext ctx, const char path[], OSVR_ClientInterface* iface);
37 typedef OSVR_ReturnCode (*pfn_osvrClientFreeInterface)(
38 OSVR_ClientContext ctx, OSVR_ClientInterface iface);
39 typedef OSVR_ReturnCode (*pfn_osvrGetOrientationState)(
40 OSVR_ClientInterface iface, OSVR_TimeValue* timestamp,
41 OSVR_OrientationState* state);
42 typedef OSVR_ReturnCode (*pfn_osvrGetPositionState)(OSVR_ClientInterface iface,
43 OSVR_TimeValue* timestamp,
44 OSVR_PositionState* state);
45 typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplay)(OSVR_ClientContext ctx,
46 OSVR_DisplayConfig* disp);
47 typedef OSVR_ReturnCode (*pfn_osvrClientFreeDisplay)(OSVR_DisplayConfig disp);
48 typedef OSVR_ReturnCode (*pfn_osvrClientGetNumEyesForViewer)(
49 OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount* eyes);
50 typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyePose)(
51 OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye,
52 OSVR_Pose3* pose);
53 typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplayDimensions)(
54 OSVR_DisplayConfig disp, OSVR_DisplayInputCount displayInputIndex,
55 OSVR_DisplayDimension* width, OSVR_DisplayDimension* height);
56 typedef OSVR_ReturnCode (
57 *pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes)(
58 OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye,
59 OSVR_SurfaceCount surface, double* left, double* right, double* bottom,
60 double* top);
61 typedef OSVR_ReturnCode (*pfn_osvrClientGetRelativeViewportForViewerEyeSurface)(
62 OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye,
63 OSVR_SurfaceCount surface, OSVR_ViewportDimension* left,
64 OSVR_ViewportDimension* bottom, OSVR_ViewportDimension* width,
65 OSVR_ViewportDimension* height);
66 typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf)(
67 OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye,
68 OSVR_SurfaceCount surface, float near, float far,
69 OSVR_MatrixConventions flags, float* matrix);
70 typedef OSVR_ReturnCode (*pfn_osvrClientCheckDisplayStartup)(
71 OSVR_DisplayConfig disp);
72 typedef OSVR_ReturnCode (*pfn_osvrClientSetRoomRotationUsingHead)(
73 OSVR_ClientContext ctx);
74 }
75
76 static pfn_osvrClientInit osvr_ClientInit = nullptr;
77 static pfn_osvrClientShutdown osvr_ClientShutdown = nullptr;
78 static pfn_osvrClientUpdate osvr_ClientUpdate = nullptr;
79 static pfn_osvrClientCheckStatus osvr_ClientCheckStatus = nullptr;
80 static pfn_osvrClientGetInterface osvr_ClientGetInterface = nullptr;
81 static pfn_osvrClientFreeInterface osvr_ClientFreeInterface = nullptr;
82 static pfn_osvrGetOrientationState osvr_GetOrientationState = nullptr;
83 static pfn_osvrGetPositionState osvr_GetPositionState = nullptr;
84 static pfn_osvrClientGetDisplay osvr_ClientGetDisplay = nullptr;
85 static pfn_osvrClientFreeDisplay osvr_ClientFreeDisplay = nullptr;
86 static pfn_osvrClientGetNumEyesForViewer osvr_ClientGetNumEyesForViewer =
87 nullptr;
88 static pfn_osvrClientGetViewerEyePose osvr_ClientGetViewerEyePose = nullptr;
89 static pfn_osvrClientGetDisplayDimensions osvr_ClientGetDisplayDimensions =
90 nullptr;
91 static pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes
92 osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes = nullptr;
93 static pfn_osvrClientGetRelativeViewportForViewerEyeSurface
94 osvr_ClientGetRelativeViewportForViewerEyeSurface = nullptr;
95 static pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf
96 osvr_ClientGetViewerEyeSurfaceProjectionMatrixf = nullptr;
97 static pfn_osvrClientCheckDisplayStartup osvr_ClientCheckDisplayStartup =
98 nullptr;
99 static pfn_osvrClientSetRoomRotationUsingHead
100 osvr_ClientSetRoomRotationUsingHead = nullptr;
101
LoadOSVRRuntime()102 bool LoadOSVRRuntime() {
103 static PRLibrary* osvrUtilLib = nullptr;
104 static PRLibrary* osvrCommonLib = nullptr;
105 static PRLibrary* osvrClientLib = nullptr;
106 static PRLibrary* osvrClientKitLib = nullptr;
107 // this looks up the path in the about:config setting, from greprefs.js or
108 // modules\libpref\init\all.js we need all the libs to be valid
109 #ifdef XP_WIN
110 constexpr static auto* pfnGetPathStringPref = mozilla::Preferences::GetString;
111 nsAutoString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath;
112 #else
113 constexpr static auto* pfnGetPathStringPref =
114 mozilla::Preferences::GetCString;
115 nsAutoCString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath;
116 #endif
117 if (NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.utilLibPath", osvrUtilPath,
118 PrefValueKind::User)) ||
119 NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.commonLibPath",
120 osvrCommonPath, PrefValueKind::User)) ||
121 NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientLibPath",
122 osvrClientPath, PrefValueKind::User)) ||
123 NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientKitLibPath",
124 osvrClientKitPath, PrefValueKind::User))) {
125 return false;
126 }
127
128 osvrUtilLib = LoadLibraryWithFlags(osvrUtilPath.get());
129 osvrCommonLib = LoadLibraryWithFlags(osvrCommonPath.get());
130 osvrClientLib = LoadLibraryWithFlags(osvrClientPath.get());
131 osvrClientKitLib = LoadLibraryWithFlags(osvrClientKitPath.get());
132
133 if (!osvrUtilLib) {
134 printf_stderr("[OSVR] Failed to load OSVR Util library!\n");
135 return false;
136 }
137 if (!osvrCommonLib) {
138 printf_stderr("[OSVR] Failed to load OSVR Common library!\n");
139 return false;
140 }
141 if (!osvrClientLib) {
142 printf_stderr("[OSVR] Failed to load OSVR Client library!\n");
143 return false;
144 }
145 if (!osvrClientKitLib) {
146 printf_stderr("[OSVR] Failed to load OSVR ClientKit library!\n");
147 return false;
148 }
149
150 // make sure all functions that we'll be using are available
151 #define REQUIRE_FUNCTION(_x) \
152 do { \
153 *(void**)&osvr_##_x = (void*)PR_FindSymbol(osvrClientKitLib, "osvr" #_x); \
154 if (!osvr_##_x) { \
155 printf_stderr("osvr" #_x " symbol missing\n"); \
156 goto fail; \
157 } \
158 } while (0)
159
160 REQUIRE_FUNCTION(ClientInit);
161 REQUIRE_FUNCTION(ClientShutdown);
162 REQUIRE_FUNCTION(ClientUpdate);
163 REQUIRE_FUNCTION(ClientCheckStatus);
164 REQUIRE_FUNCTION(ClientGetInterface);
165 REQUIRE_FUNCTION(ClientFreeInterface);
166 REQUIRE_FUNCTION(GetOrientationState);
167 REQUIRE_FUNCTION(GetPositionState);
168 REQUIRE_FUNCTION(ClientGetDisplay);
169 REQUIRE_FUNCTION(ClientFreeDisplay);
170 REQUIRE_FUNCTION(ClientGetNumEyesForViewer);
171 REQUIRE_FUNCTION(ClientGetViewerEyePose);
172 REQUIRE_FUNCTION(ClientGetDisplayDimensions);
173 REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionClippingPlanes);
174 REQUIRE_FUNCTION(ClientGetRelativeViewportForViewerEyeSurface);
175 REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionMatrixf);
176 REQUIRE_FUNCTION(ClientCheckDisplayStartup);
177 REQUIRE_FUNCTION(ClientSetRoomRotationUsingHead);
178
179 #undef REQUIRE_FUNCTION
180
181 return true;
182
183 fail:
184 return false;
185 }
186
187 } // namespace
188
SetFromTanRadians(double left,double right,double bottom,double top)189 mozilla::gfx::VRFieldOfView SetFromTanRadians(double left, double right,
190 double bottom, double top) {
191 mozilla::gfx::VRFieldOfView fovInfo;
192 fovInfo.leftDegrees = atan(left) * 180.0 / M_PI;
193 fovInfo.rightDegrees = atan(right) * 180.0 / M_PI;
194 fovInfo.upDegrees = atan(top) * 180.0 / M_PI;
195 fovInfo.downDegrees = atan(bottom) * 180.0 / M_PI;
196 return fovInfo;
197 }
198
OSVRSession()199 OSVRSession::OSVRSession()
200 : VRSession(),
201 mRuntimeLoaded(false),
202 mOSVRInitialized(false),
203 mClientContextInitialized(false),
204 mDisplayConfigInitialized(false),
205 mInterfaceInitialized(false),
206 m_ctx(nullptr),
207 m_iface(nullptr),
208 m_display(nullptr) {}
~OSVRSession()209 OSVRSession::~OSVRSession() { Shutdown(); }
210
Initialize(mozilla::gfx::VRSystemState & aSystemState,bool aDetectRuntimesOnly)211 bool OSVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState,
212 bool aDetectRuntimesOnly) {
213 if (StaticPrefs::dom_vr_puppet_enabled()) {
214 // Ensure that tests using the VR Puppet do not find real hardware
215 return false;
216 }
217 if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_osvr_enabled()) {
218 return false;
219 }
220 if (mOSVRInitialized) {
221 return true;
222 }
223 if (!LoadOSVRRuntime()) {
224 return false;
225 }
226 mRuntimeLoaded = true;
227
228 if (aDetectRuntimesOnly) {
229 aSystemState.displayState.capabilityFlags |=
230 VRDisplayCapabilityFlags::Cap_ImmersiveVR;
231 return false;
232 }
233
234 // initialize client context
235 InitializeClientContext();
236 // try to initialize interface
237 InitializeInterface();
238 // try to initialize display object
239 InitializeDisplay();
240 // verify all components are initialized
241 CheckOSVRStatus();
242
243 if (!mOSVRInitialized) {
244 return false;
245 }
246
247 if (!InitState(aSystemState)) {
248 return false;
249 }
250
251 return true;
252 }
253
CheckOSVRStatus()254 void OSVRSession::CheckOSVRStatus() {
255 if (mOSVRInitialized) {
256 return;
257 }
258
259 // client context must be initialized first
260 InitializeClientContext();
261
262 // update client context
263 osvr_ClientUpdate(m_ctx);
264
265 // initialize interface and display if they are not initialized yet
266 InitializeInterface();
267 InitializeDisplay();
268
269 // OSVR is fully initialized now
270 if (mClientContextInitialized && mDisplayConfigInitialized &&
271 mInterfaceInitialized) {
272 mOSVRInitialized = true;
273 }
274 }
275
InitializeClientContext()276 void OSVRSession::InitializeClientContext() {
277 // already initialized
278 if (mClientContextInitialized) {
279 return;
280 }
281
282 // first time creating
283 if (!m_ctx) {
284 // get client context
285 m_ctx = osvr_ClientInit("com.osvr.webvr", 0);
286 // update context
287 osvr_ClientUpdate(m_ctx);
288 // verify we are connected
289 if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) {
290 mClientContextInitialized = true;
291 }
292 }
293 // client context exists but not up and running yet
294 else {
295 // update context
296 osvr_ClientUpdate(m_ctx);
297 if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) {
298 mClientContextInitialized = true;
299 }
300 }
301 }
302
InitializeInterface()303 void OSVRSession::InitializeInterface() {
304 // already initialized
305 if (mInterfaceInitialized) {
306 return;
307 }
308 // Client context must be initialized before getting interface
309 if (mClientContextInitialized) {
310 // m_iface will remain nullptr if no interface is returned
311 if (OSVR_RETURN_SUCCESS ==
312 osvr_ClientGetInterface(m_ctx, "/me/head", &m_iface)) {
313 mInterfaceInitialized = true;
314 }
315 }
316 }
317
InitializeDisplay()318 void OSVRSession::InitializeDisplay() {
319 // display is fully configured
320 if (mDisplayConfigInitialized) {
321 return;
322 }
323
324 // Client context must be initialized before getting interface
325 if (mClientContextInitialized) {
326 // first time creating display object
327 if (m_display == nullptr) {
328 OSVR_ReturnCode ret = osvr_ClientGetDisplay(m_ctx, &m_display);
329
330 if (ret == OSVR_RETURN_SUCCESS) {
331 osvr_ClientUpdate(m_ctx);
332 // display object may have been created but not fully startup
333 if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) {
334 mDisplayConfigInitialized = true;
335 }
336 }
337
338 // Typically once we get Display object, pose data is available after
339 // clientUpdate but sometimes it takes ~ 200 ms to get
340 // a succesfull connection, so we might have to run a few update cycles
341 } else {
342 if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) {
343 mDisplayConfigInitialized = true;
344 }
345 }
346 }
347 }
348
InitState(mozilla::gfx::VRSystemState & aSystemState)349 bool OSVRSession::InitState(mozilla::gfx::VRSystemState& aSystemState) {
350 VRDisplayState& state = aSystemState.displayState;
351 strncpy(state.displayName, "OSVR HMD", kVRDisplayNameMaxLen);
352 state.eightCC = GFX_VR_EIGHTCC('O', 'S', 'V', 'R', ' ', ' ', ' ', ' ');
353 state.isConnected = true;
354 state.isMounted = false;
355 state.capabilityFlags =
356 (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
357 (int)
358 VRDisplayCapabilityFlags::Cap_Orientation |
359 (int)VRDisplayCapabilityFlags::Cap_Position |
360 (int)VRDisplayCapabilityFlags::Cap_External |
361 (int)VRDisplayCapabilityFlags::Cap_Present |
362 (int)
363 VRDisplayCapabilityFlags::Cap_ImmersiveVR);
364 state.blendMode = VRDisplayBlendMode::Opaque;
365 state.reportsDroppedFrames = false;
366
367 // XXX OSVR display topology allows for more than one viewer
368 // will assume only one viewer for now (most likely stay that way)
369
370 OSVR_EyeCount numEyes;
371 osvr_ClientGetNumEyesForViewer(m_display, 0, &numEyes);
372
373 for (uint8_t eye = 0; eye < numEyes; eye++) {
374 double left, right, bottom, top;
375 // XXX for now there is only one surface per eye
376 osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes(
377 m_display, 0, eye, 0, &left, &right, &bottom, &top);
378 state.eyeFOV[eye] = SetFromTanRadians(-left, right, -bottom, top);
379 }
380
381 // XXX Assuming there is only one display input for now
382 // however, it's possible to have more than one (dSight with 2 HDMI inputs)
383 OSVR_DisplayDimension width, height;
384 osvr_ClientGetDisplayDimensions(m_display, 0, &width, &height);
385
386 for (uint8_t eye = 0; eye < numEyes; eye++) {
387 OSVR_ViewportDimension l, b, w, h;
388 osvr_ClientGetRelativeViewportForViewerEyeSurface(m_display, 0, eye, 0, &l,
389 &b, &w, &h);
390 state.eyeResolution.width = w;
391 state.eyeResolution.height = h;
392 OSVR_Pose3 eyePose;
393 // Viewer eye pose may not be immediately available, update client context
394 // until we get it
395 OSVR_ReturnCode ret =
396 osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose);
397 while (ret != OSVR_RETURN_SUCCESS) {
398 osvr_ClientUpdate(m_ctx);
399 ret = osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose);
400 }
401 state.eyeTranslation[eye].x = eyePose.translation.data[0];
402 state.eyeTranslation[eye].y = eyePose.translation.data[1];
403 state.eyeTranslation[eye].z = eyePose.translation.data[2];
404
405 Matrix4x4 pose;
406 pose.SetRotationFromQuaternion(gfx::Quaternion(
407 osvrQuatGetX(&eyePose.rotation), osvrQuatGetY(&eyePose.rotation),
408 osvrQuatGetZ(&eyePose.rotation), osvrQuatGetW(&eyePose.rotation)));
409 pose.PreTranslate(eyePose.translation.data[0], eyePose.translation.data[1],
410 eyePose.translation.data[2]);
411 pose.Invert();
412 mHeadToEye[eye] = pose;
413 }
414
415 // default to an identity quaternion
416 VRHMDSensorState& sensorState = aSystemState.sensorState;
417 sensorState.flags =
418 (VRDisplayCapabilityFlags)((int)
419 VRDisplayCapabilityFlags::Cap_Orientation |
420 (int)VRDisplayCapabilityFlags::Cap_Position);
421 sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion
422
423 return true;
424 }
425
Shutdown()426 void OSVRSession::Shutdown() {
427 if (!mRuntimeLoaded) {
428 return;
429 }
430 mOSVRInitialized = false;
431 // client context may not have been initialized
432 if (m_ctx) {
433 osvr_ClientFreeDisplay(m_display);
434 }
435 // osvr checks that m_ctx or m_iface are not null
436 osvr_ClientFreeInterface(m_ctx, m_iface);
437 osvr_ClientShutdown(m_ctx);
438 }
439
ProcessEvents(mozilla::gfx::VRSystemState & aSystemState)440 void OSVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {}
441
StartFrame(mozilla::gfx::VRSystemState & aSystemState)442 void OSVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) {
443 UpdateHeadsetPose(aSystemState);
444 }
445
UpdateHeadsetPose(mozilla::gfx::VRSystemState & aState)446 void OSVRSession::UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState) {
447 // Update client context before anything
448 // this usually goes into app's mainloop
449 osvr_ClientUpdate(m_ctx);
450
451 VRHMDSensorState result{};
452 OSVR_TimeValue timestamp;
453
454 OSVR_OrientationState orientation;
455
456 OSVR_ReturnCode ret =
457 osvr_GetOrientationState(m_iface, ×tamp, &orientation);
458
459 aState.sensorState.timestamp = timestamp.seconds;
460
461 if (ret == OSVR_RETURN_SUCCESS) {
462 result.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
463 result.pose.orientation[0] = orientation.data[1];
464 result.pose.orientation[1] = orientation.data[2];
465 result.pose.orientation[2] = orientation.data[3];
466 result.pose.orientation[3] = orientation.data[0];
467 } else {
468 // default to an identity quaternion
469 result.pose.orientation[3] = 1.0f;
470 }
471
472 OSVR_PositionState position;
473 ret = osvr_GetPositionState(m_iface, ×tamp, &position);
474 if (ret == OSVR_RETURN_SUCCESS) {
475 result.flags |= VRDisplayCapabilityFlags::Cap_Position;
476 result.pose.position[0] = position.data[0];
477 result.pose.position[1] = position.data[1];
478 result.pose.position[2] = position.data[2];
479 }
480
481 result.CalcViewMatrices(mHeadToEye);
482 }
483
StartPresentation()484 bool OSVRSession::StartPresentation() {
485 return false;
486 // TODO Implement
487 }
488
StopPresentation()489 void OSVRSession::StopPresentation() {
490 // TODO Implement
491 }
492
493 #if defined(XP_WIN)
SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive & aLayer,ID3D11Texture2D * aTexture)494 bool OSVRSession::SubmitFrame(
495 const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
496 ID3D11Texture2D* aTexture) {
497 return false;
498 // TODO Implement
499 }
500 #elif defined(XP_MACOSX)
SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive & aLayer,const VRLayerTextureHandle & aTexture)501 bool OSVRSession::SubmitFrame(
502 const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
503 const VRLayerTextureHandle& aTexture) {
504 return false;
505 // TODO Implement
506 }
507 #endif
508
VibrateHaptic(uint32_t aControllerIdx,uint32_t aHapticIndex,float aIntensity,float aDuration)509 void OSVRSession::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
510 float aIntensity, float aDuration) {}
511
StopVibrateHaptic(uint32_t aControllerIdx)512 void OSVRSession::StopVibrateHaptic(uint32_t aControllerIdx) {}
513
StopAllHaptics()514 void OSVRSession::StopAllHaptics() {}
515