summaryrefslogtreecommitdiffstats
path: root/gfx/vr/service/OculusSession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/vr/service/OculusSession.cpp')
-rw-r--r--gfx/vr/service/OculusSession.cpp1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/gfx/vr/service/OculusSession.cpp b/gfx/vr/service/OculusSession.cpp
new file mode 100644
index 0000000000..07b0f208e3
--- /dev/null
+++ b/gfx/vr/service/OculusSession.cpp
@@ -0,0 +1,1525 @@
+/* -*- 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 <math.h>
+#include <d3d11.h>
+
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/GamepadEventTypes.h"
+#include "mozilla/dom/GamepadBinding.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.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 = 7;
+static const uint32_t kNumOculusHaptcs = 1;
+static const uint32_t kNumOculusAxes = 4;
+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)0, (ovrButton)0, (ovrButton)0, ovrButton_LThumb, ovrButton_X,
+ ovrButton_Y, (ovrButton)0},
+ {(ovrButton)0, (ovrButton)0, (ovrButton)0, ovrButton_RThumb, ovrButton_A,
+ ovrButton_B, (ovrButton)0},
+};
+
+ovrTouch OculusControllerTouches[2][kNumOculusButtons] = {
+ {ovrTouch_LIndexTrigger, (ovrTouch)0, (ovrTouch)0, ovrTouch_LThumb,
+ ovrTouch_X, ovrTouch_Y, ovrTouch_LThumbRest},
+ {ovrTouch_RIndexTrigger, (ovrTouch)0, (ovrTouch)0, ovrTouch_RThumb,
+ 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);
+ aControllerState.triggerValue[aButtonIdx] = 1.0f;
+ } else {
+ aControllerState.triggerValue[aButtonIdx] = 0.0f;
+ }
+ 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_puppet_enabled()) {
+ // Ensure that tests using the VR Puppet do not find real hardware
+ return false;
+ }
+ if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_oculus_enabled()) {
+ 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,
+ &currentRenderTarget);
+ 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<ID3D11ShaderResourceView> 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<nsString> 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, u""_ns);
+
+ // 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 + u"\\"_ns + 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(&params, 0, sizeof(params));
+ params.Flags = aFlags;
+ params.RequestedMinorVersion = OVR_MINOR_VERSION;
+ params.LogCallback = nullptr;
+ params.ConnectionTimeoutMS = 0;
+
+ ovrResult orv = ovr_Initialize(&params);
+
+ 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<ID3D11Device> 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<ID3D11RenderTargetView> 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<ID3D11ShaderResourceView> 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);
+ state.nativeFramebufferScaleFactor = 1.0f;
+
+ // 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");
+ }
+
+ eyeRotation.Invert();
+ 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;
+ controllerState.flags |=
+ dom::GamepadCapabilityFlags::Cap_GripSpacePosition;
+ }
+
+ 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;
+ }
+ controllerState.targetRayPose = controllerState.pose;
+ }
+ }
+}
+
+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]) {
+ // Touch Controller detected
+ if (controllerState.controllerName[0] == '\0') {
+ // Controller has been just enumerated
+ strncpy(controllerState.controllerName, OculusControllerNames[handIdx],
+ kVRControllerNameMaxLen);
+ controllerState.hand = OculusControllerHand[handIdx];
+ controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer;
+ controllerState.numButtons = kNumOculusButtons;
+ controllerState.numAxes = kNumOculusAxes;
+ controllerState.numHaptics = kNumOculusHaptcs;
+ controllerState.type = VRControllerType::OculusTouch;
+ }
+ } else {
+ // 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;
+
+ // Button 0: Trigger
+ VRSession::UpdateTrigger(controllerState, buttonIdx,
+ aInputState.IndexTrigger[handIdx],
+ triggerThreshold);
+ ++buttonIdx;
+ // Button 1: Grip
+ VRSession::UpdateTrigger(controllerState, buttonIdx,
+ aInputState.HandTrigger[handIdx],
+ triggerThreshold);
+ ++buttonIdx;
+ // Button 2: a placeholder button for trackpad.
+ UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
+ ++buttonIdx;
+ // Button 3: Thumbstick
+ UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
+ ++buttonIdx;
+ // Button 4: A
+ UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
+ ++buttonIdx;
+ // Button 5: B
+ UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
+ ++buttonIdx;
+ // Button 6: ThumbRest
+ UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
+ ++buttonIdx;
+
+ MOZ_ASSERT(buttonIdx == kNumOculusButtons);
+
+ // Update Thumbstick axis
+ uint32_t axisIdx = 0;
+ // Axis 0, 1: placeholder axes for trackpad.
+ axisIdx += 2;
+
+ // Axis 2, 3: placeholder axes for thumbstick.
+ 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);
+ }
+ SetControllerSelectionAndSqueezeFrameId(
+ controllerState, aState.displayState.lastSubmittedFrameId);
+ }
+}
+
+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