зеркало из https://github.com/mozilla/gecko-dev.git
1496 строки
52 KiB
C++
1496 строки
52 KiB
C++
/* -*- 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/SharedLibrary.h"
|
|
#include "OculusSession.h"
|
|
|
|
/** XXX The DX11 objects and quad blitting could be encapsulated
|
|
* into a separate object if either Oculus starts supporting
|
|
* non-Windows platforms or the blit is needed by other HMD\
|
|
* drivers.
|
|
* Alternately, we could remove the extra blit for
|
|
* Oculus as well with some more refactoring.
|
|
*/
|
|
|
|
// See CompositorD3D11Shaders.h
|
|
namespace mozilla {
|
|
namespace layers {
|
|
struct ShaderBytes {
|
|
const void* mData;
|
|
size_t mLength;
|
|
};
|
|
extern ShaderBytes sRGBShader;
|
|
extern ShaderBytes sLayerQuadVS;
|
|
} // namespace layers
|
|
} // namespace mozilla
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::layers;
|
|
|
|
namespace {
|
|
|
|
static pfn_ovr_Initialize ovr_Initialize = nullptr;
|
|
static pfn_ovr_Shutdown ovr_Shutdown = nullptr;
|
|
static pfn_ovr_GetLastErrorInfo ovr_GetLastErrorInfo = nullptr;
|
|
static pfn_ovr_GetVersionString ovr_GetVersionString = nullptr;
|
|
static pfn_ovr_TraceMessage ovr_TraceMessage = nullptr;
|
|
static pfn_ovr_IdentifyClient ovr_IdentifyClient = nullptr;
|
|
static pfn_ovr_GetHmdDesc ovr_GetHmdDesc = nullptr;
|
|
static pfn_ovr_GetTrackerCount ovr_GetTrackerCount = nullptr;
|
|
static pfn_ovr_GetTrackerDesc ovr_GetTrackerDesc = nullptr;
|
|
static pfn_ovr_Create ovr_Create = nullptr;
|
|
static pfn_ovr_Destroy ovr_Destroy = nullptr;
|
|
static pfn_ovr_GetSessionStatus ovr_GetSessionStatus = nullptr;
|
|
static pfn_ovr_IsExtensionSupported ovr_IsExtensionSupported = nullptr;
|
|
static pfn_ovr_EnableExtension ovr_EnableExtension = nullptr;
|
|
static pfn_ovr_SetTrackingOriginType ovr_SetTrackingOriginType = nullptr;
|
|
static pfn_ovr_GetTrackingOriginType ovr_GetTrackingOriginType = nullptr;
|
|
static pfn_ovr_RecenterTrackingOrigin ovr_RecenterTrackingOrigin = nullptr;
|
|
static pfn_ovr_SpecifyTrackingOrigin ovr_SpecifyTrackingOrigin = nullptr;
|
|
static pfn_ovr_ClearShouldRecenterFlag ovr_ClearShouldRecenterFlag = nullptr;
|
|
static pfn_ovr_GetTrackingState ovr_GetTrackingState = nullptr;
|
|
static pfn_ovr_GetDevicePoses ovr_GetDevicePoses = nullptr;
|
|
static pfn_ovr_GetTrackerPose ovr_GetTrackerPose = nullptr;
|
|
static pfn_ovr_GetInputState ovr_GetInputState = nullptr;
|
|
static pfn_ovr_GetConnectedControllerTypes ovr_GetConnectedControllerTypes =
|
|
nullptr;
|
|
static pfn_ovr_GetTouchHapticsDesc ovr_GetTouchHapticsDesc = nullptr;
|
|
static pfn_ovr_SetControllerVibration ovr_SetControllerVibration = nullptr;
|
|
static pfn_ovr_SubmitControllerVibration ovr_SubmitControllerVibration =
|
|
nullptr;
|
|
static pfn_ovr_GetControllerVibrationState ovr_GetControllerVibrationState =
|
|
nullptr;
|
|
static pfn_ovr_TestBoundary ovr_TestBoundary = nullptr;
|
|
static pfn_ovr_TestBoundaryPoint ovr_TestBoundaryPoint = nullptr;
|
|
static pfn_ovr_SetBoundaryLookAndFeel ovr_SetBoundaryLookAndFeel = nullptr;
|
|
static pfn_ovr_ResetBoundaryLookAndFeel ovr_ResetBoundaryLookAndFeel = nullptr;
|
|
static pfn_ovr_GetBoundaryGeometry ovr_GetBoundaryGeometry = nullptr;
|
|
static pfn_ovr_GetBoundaryDimensions ovr_GetBoundaryDimensions = nullptr;
|
|
static pfn_ovr_GetBoundaryVisible ovr_GetBoundaryVisible = nullptr;
|
|
static pfn_ovr_RequestBoundaryVisible ovr_RequestBoundaryVisible = nullptr;
|
|
static pfn_ovr_GetTextureSwapChainLength ovr_GetTextureSwapChainLength =
|
|
nullptr;
|
|
static pfn_ovr_GetTextureSwapChainCurrentIndex
|
|
ovr_GetTextureSwapChainCurrentIndex = nullptr;
|
|
static pfn_ovr_GetTextureSwapChainDesc ovr_GetTextureSwapChainDesc = nullptr;
|
|
static pfn_ovr_CommitTextureSwapChain ovr_CommitTextureSwapChain = nullptr;
|
|
static pfn_ovr_DestroyTextureSwapChain ovr_DestroyTextureSwapChain = nullptr;
|
|
static pfn_ovr_DestroyMirrorTexture ovr_DestroyMirrorTexture = nullptr;
|
|
static pfn_ovr_GetFovTextureSize ovr_GetFovTextureSize = nullptr;
|
|
static pfn_ovr_GetRenderDesc2 ovr_GetRenderDesc2 = nullptr;
|
|
static pfn_ovr_WaitToBeginFrame ovr_WaitToBeginFrame = nullptr;
|
|
static pfn_ovr_BeginFrame ovr_BeginFrame = nullptr;
|
|
static pfn_ovr_EndFrame ovr_EndFrame = nullptr;
|
|
static pfn_ovr_SubmitFrame ovr_SubmitFrame = nullptr;
|
|
static pfn_ovr_GetPerfStats ovr_GetPerfStats = nullptr;
|
|
static pfn_ovr_ResetPerfStats ovr_ResetPerfStats = nullptr;
|
|
static pfn_ovr_GetPredictedDisplayTime ovr_GetPredictedDisplayTime = nullptr;
|
|
static pfn_ovr_GetTimeInSeconds ovr_GetTimeInSeconds = nullptr;
|
|
static pfn_ovr_GetBool ovr_GetBool = nullptr;
|
|
static pfn_ovr_SetBool ovr_SetBool = nullptr;
|
|
static pfn_ovr_GetInt ovr_GetInt = nullptr;
|
|
static pfn_ovr_SetInt ovr_SetInt = nullptr;
|
|
static pfn_ovr_GetFloat ovr_GetFloat = nullptr;
|
|
static pfn_ovr_SetFloat ovr_SetFloat = nullptr;
|
|
static pfn_ovr_GetFloatArray ovr_GetFloatArray = nullptr;
|
|
static pfn_ovr_SetFloatArray ovr_SetFloatArray = nullptr;
|
|
static pfn_ovr_GetString ovr_GetString = nullptr;
|
|
static pfn_ovr_SetString ovr_SetString = nullptr;
|
|
static pfn_ovr_GetExternalCameras ovr_GetExternalCameras = nullptr;
|
|
static pfn_ovr_SetExternalCameraProperties ovr_SetExternalCameraProperties =
|
|
nullptr;
|
|
|
|
#ifdef XP_WIN
|
|
static pfn_ovr_CreateTextureSwapChainDX ovr_CreateTextureSwapChainDX = nullptr;
|
|
static pfn_ovr_GetTextureSwapChainBufferDX ovr_GetTextureSwapChainBufferDX =
|
|
nullptr;
|
|
static pfn_ovr_CreateMirrorTextureDX ovr_CreateMirrorTextureDX = nullptr;
|
|
static pfn_ovr_GetMirrorTextureBufferDX ovr_GetMirrorTextureBufferDX = nullptr;
|
|
#endif
|
|
|
|
static pfn_ovr_CreateTextureSwapChainGL ovr_CreateTextureSwapChainGL = nullptr;
|
|
static pfn_ovr_GetTextureSwapChainBufferGL ovr_GetTextureSwapChainBufferGL =
|
|
nullptr;
|
|
static pfn_ovr_CreateMirrorTextureGL ovr_CreateMirrorTextureGL = nullptr;
|
|
static pfn_ovr_GetMirrorTextureBufferGL ovr_GetMirrorTextureBufferGL = nullptr;
|
|
|
|
#ifdef HAVE_64BIT_BUILD
|
|
# define BUILD_BITS 64
|
|
#else
|
|
# define BUILD_BITS 32
|
|
#endif
|
|
|
|
#define OVR_PRODUCT_VERSION 1
|
|
#define OVR_MAJOR_VERSION 1
|
|
#define OVR_MINOR_VERSION 19
|
|
|
|
static const uint32_t kNumOculusButtons = 6;
|
|
static const uint32_t kNumOculusHaptcs = 1;
|
|
static const uint32_t kNumOculusAxes = 2;
|
|
ovrControllerType OculusControllerTypes[2] = {ovrControllerType_LTouch,
|
|
ovrControllerType_RTouch};
|
|
const char* OculusControllerNames[2] = {"Oculus Touch (Left)",
|
|
"Oculus Touch (Right)"};
|
|
dom::GamepadHand OculusControllerHand[2] = {dom::GamepadHand::Left,
|
|
dom::GamepadHand::Right};
|
|
ovrButton OculusControllerButtons[2][kNumOculusButtons] = {
|
|
{ovrButton_LThumb, (ovrButton)0, (ovrButton)0, ovrButton_X, ovrButton_Y,
|
|
(ovrButton)0},
|
|
{ovrButton_RThumb, (ovrButton)0, (ovrButton)0, ovrButton_A, ovrButton_B,
|
|
(ovrButton)0},
|
|
};
|
|
|
|
ovrTouch OculusControllerTouches[2][kNumOculusButtons] = {
|
|
{ovrTouch_LThumb, ovrTouch_LIndexTrigger, (ovrTouch)0, ovrTouch_X,
|
|
ovrTouch_Y, ovrTouch_LThumbRest},
|
|
{ovrTouch_RThumb, ovrTouch_RIndexTrigger, (ovrTouch)0, ovrTouch_A,
|
|
ovrTouch_B, ovrTouch_RThumbRest},
|
|
};
|
|
|
|
void UpdateButton(const ovrInputState& aInputState, uint32_t aHandIdx,
|
|
uint32_t aButtonIdx, VRControllerState& aControllerState) {
|
|
if (aInputState.Buttons & OculusControllerButtons[aHandIdx][aButtonIdx]) {
|
|
aControllerState.buttonPressed |= ((uint64_t)1 << aButtonIdx);
|
|
}
|
|
if (aInputState.Touches & OculusControllerTouches[aHandIdx][aButtonIdx]) {
|
|
aControllerState.buttonTouched |= ((uint64_t)1 << aButtonIdx);
|
|
}
|
|
}
|
|
|
|
VRFieldOfView FromFovPort(const ovrFovPort& aFOV) {
|
|
VRFieldOfView fovInfo;
|
|
fovInfo.leftDegrees = atan(aFOV.LeftTan) * 180.0 / M_PI;
|
|
fovInfo.rightDegrees = atan(aFOV.RightTan) * 180.0 / M_PI;
|
|
fovInfo.upDegrees = atan(aFOV.UpTan) * 180.0 / M_PI;
|
|
fovInfo.downDegrees = atan(aFOV.DownTan) * 180.0 / M_PI;
|
|
return fovInfo;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
OculusSession::OculusSession()
|
|
: VRSession(),
|
|
mOvrLib(nullptr),
|
|
mSession(nullptr),
|
|
mInitFlags((ovrInitFlags)0),
|
|
mTextureSet(nullptr),
|
|
mQuadVS(nullptr),
|
|
mQuadPS(nullptr),
|
|
mLinearSamplerState(nullptr),
|
|
mVSConstantBuffer(nullptr),
|
|
mPSConstantBuffer(nullptr),
|
|
mVertexBuffer(nullptr),
|
|
mInputLayout(nullptr),
|
|
mRemainingVibrateTime{},
|
|
mHapticPulseIntensity{},
|
|
mIsPresenting(false) {}
|
|
|
|
OculusSession::~OculusSession() { Shutdown(); }
|
|
|
|
bool OculusSession::Initialize(mozilla::gfx::VRSystemState& aSystemState,
|
|
bool aDetectRuntimesOnly) {
|
|
if (!StaticPrefs::dom_vr_enabled() ||
|
|
!StaticPrefs::dom_vr_oculus_enabled_AtStartup()) {
|
|
return false;
|
|
}
|
|
|
|
if (aDetectRuntimesOnly) {
|
|
if (LoadOvrLib()) {
|
|
aSystemState.displayState.capabilityFlags |=
|
|
VRDisplayCapabilityFlags::Cap_ImmersiveVR;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!CreateD3DObjects()) {
|
|
return false;
|
|
}
|
|
if (!CreateShaders()) {
|
|
return false;
|
|
}
|
|
// Ideally, we should move LoadOvrLib() up to the first line to avoid
|
|
// unnecessary D3D objects creation. But it will cause a WPT fail in Win 7
|
|
// debug.
|
|
if (!LoadOvrLib()) {
|
|
return false;
|
|
}
|
|
// We start off with an invisible session, then re-initialize
|
|
// with visible session once WebVR content starts rendering.
|
|
if (!ChangeVisibility(false)) {
|
|
return false;
|
|
}
|
|
if (!InitState(aSystemState)) {
|
|
return false;
|
|
}
|
|
|
|
mPresentationSize = IntSize(aSystemState.displayState.eyeResolution.width * 2,
|
|
aSystemState.displayState.eyeResolution.height);
|
|
return true;
|
|
}
|
|
|
|
void OculusSession::UpdateVisibility() {
|
|
// Do not immediately re-initialize with an invisible session after
|
|
// the end of a VR presentation. Waiting for the configured duraction
|
|
// ensures that the user will not drop to Oculus Home during VR link
|
|
// traversal.
|
|
if (mIsPresenting) {
|
|
// We are currently rendering immersive content.
|
|
// Avoid interrupting the session
|
|
return;
|
|
}
|
|
if (mInitFlags & ovrInit_Invisible) {
|
|
// We are already invisible
|
|
return;
|
|
}
|
|
if (mLastPresentationEnd.IsNull()) {
|
|
// There has been no presentation yet
|
|
return;
|
|
}
|
|
|
|
TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd;
|
|
TimeDuration timeout = TimeDuration::FromMilliseconds(
|
|
StaticPrefs::dom_vr_oculus_present_timeout());
|
|
if (timeout <= TimeDuration(0) || duration >= timeout) {
|
|
if (!ChangeVisibility(false)) {
|
|
gfxWarning() << "OculusSession::ChangeVisibility(false) failed";
|
|
}
|
|
}
|
|
}
|
|
|
|
void OculusSession::CoverTransitions() {
|
|
// While content is loading or during immersive-mode link
|
|
// traversal, we need to prevent the user from seeing the
|
|
// last rendered frame.
|
|
// We render black frames to cover up the transition.
|
|
MOZ_ASSERT(mSession);
|
|
if (mIsPresenting) {
|
|
// We are currently rendering immersive content.
|
|
// Avoid interrupting the session
|
|
return;
|
|
}
|
|
|
|
if (mInitFlags & ovrInit_Invisible) {
|
|
// We are invisible, nothing to cover up
|
|
return;
|
|
}
|
|
|
|
// Render a black frame
|
|
ovrLayerEyeFov layer;
|
|
memset(&layer, 0, sizeof(layer));
|
|
layer.Header.Type = ovrLayerType_Disabled;
|
|
ovrLayerHeader* layers = &layer.Header;
|
|
ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1);
|
|
}
|
|
|
|
bool OculusSession::ChangeVisibility(bool bVisible) {
|
|
ovrInitFlags flags =
|
|
(ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering);
|
|
if (StaticPrefs::dom_vr_oculus_invisible_enabled() && !bVisible) {
|
|
flags = (ovrInitFlags)(flags | ovrInit_Invisible);
|
|
}
|
|
if (mInitFlags == flags) {
|
|
// The new state is the same, nothing to do
|
|
return true;
|
|
}
|
|
|
|
// Tear everything down
|
|
StopRendering();
|
|
StopSession();
|
|
StopLib();
|
|
|
|
// Start it back up
|
|
if (!StartLib(flags)) {
|
|
return false;
|
|
}
|
|
if (!StartSession()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OculusSession::Shutdown() {
|
|
StopRendering();
|
|
StopSession();
|
|
StopLib();
|
|
UnloadOvrLib();
|
|
DestroyShaders();
|
|
}
|
|
|
|
void OculusSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
|
|
ovrSessionStatus status;
|
|
if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) {
|
|
aSystemState.displayState.isConnected = status.HmdPresent;
|
|
aSystemState.displayState.isMounted = status.HmdMounted;
|
|
mShouldQuit = status.ShouldQuit;
|
|
|
|
} else {
|
|
aSystemState.displayState.isConnected = false;
|
|
aSystemState.displayState.isMounted = false;
|
|
}
|
|
UpdateHaptics();
|
|
UpdateVisibility();
|
|
CoverTransitions();
|
|
}
|
|
|
|
void OculusSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) {
|
|
UpdateHeadsetPose(aSystemState);
|
|
UpdateEyeParameters(aSystemState);
|
|
UpdateControllers(aSystemState);
|
|
UpdateTelemetry(aSystemState);
|
|
aSystemState.sensorState.inputFrameID++;
|
|
}
|
|
|
|
bool OculusSession::StartPresentation() {
|
|
/**
|
|
* XXX - We should resolve fail the promise returned by
|
|
* VRDisplay.requestPresent() when the DX11 resources fail allocation
|
|
* in VRDisplayOculus::StartPresentation().
|
|
* Bailing out here prevents the crash but content should be aware
|
|
* that frames are not being presented.
|
|
* See Bug 1299309.
|
|
**/
|
|
if (!ChangeVisibility(true)) {
|
|
return false;
|
|
}
|
|
if (!StartRendering()) {
|
|
StopRendering();
|
|
return false;
|
|
}
|
|
mIsPresenting = true;
|
|
return true;
|
|
}
|
|
|
|
void OculusSession::StopPresentation() {
|
|
mLastPresentationEnd = TimeStamp::Now();
|
|
mIsPresenting = false;
|
|
}
|
|
|
|
bool OculusSession::SubmitFrame(
|
|
const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
|
|
ID3D11Texture2D* aTexture) {
|
|
if (!IsPresentationReady()) {
|
|
return false;
|
|
}
|
|
|
|
D3D11_TEXTURE2D_DESC textureDesc = {0};
|
|
aTexture->GetDesc(&textureDesc);
|
|
|
|
int currentRenderTarget = 0;
|
|
ovrResult orv = ovr_GetTextureSwapChainCurrentIndex(mSession, mTextureSet,
|
|
¤tRenderTarget);
|
|
if (orv != ovrSuccess) {
|
|
NS_WARNING("ovr_GetTextureSwapChainCurrentIndex failed.");
|
|
return false;
|
|
}
|
|
|
|
ID3D11RenderTargetView* view = mRTView[currentRenderTarget];
|
|
|
|
float clear[] = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
mContext->ClearRenderTargetView(view, clear);
|
|
mContext->OMSetRenderTargets(1, &view, nullptr);
|
|
|
|
Matrix viewMatrix = Matrix::Translation(-1.0, 1.0);
|
|
viewMatrix.PreScale(2.0f / float(textureDesc.Width),
|
|
2.0f / float(textureDesc.Height));
|
|
viewMatrix.PreScale(1.0f, -1.0f);
|
|
Matrix4x4 projection = Matrix4x4::From2D(viewMatrix);
|
|
projection._33 = 0.0f;
|
|
|
|
Matrix transform2d;
|
|
gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d);
|
|
|
|
D3D11_VIEWPORT viewport;
|
|
viewport.MinDepth = 0.0f;
|
|
viewport.MaxDepth = 1.0f;
|
|
viewport.Width = textureDesc.Width;
|
|
viewport.Height = textureDesc.Height;
|
|
viewport.TopLeftX = 0;
|
|
viewport.TopLeftY = 0;
|
|
|
|
D3D11_RECT scissor;
|
|
scissor.left = 0;
|
|
scissor.right = textureDesc.Width;
|
|
scissor.top = 0;
|
|
scissor.bottom = textureDesc.Height;
|
|
|
|
memcpy(&mVSConstants.layerTransform, &transform._11,
|
|
sizeof(mVSConstants.layerTransform));
|
|
memcpy(&mVSConstants.projection, &projection._11,
|
|
sizeof(mVSConstants.projection));
|
|
mVSConstants.renderTargetOffset[0] = 0.0f;
|
|
mVSConstants.renderTargetOffset[1] = 0.0f;
|
|
mVSConstants.layerQuad =
|
|
Rect(0.0f, 0.0f, textureDesc.Width, textureDesc.Height);
|
|
mVSConstants.textureCoords = Rect(0.0f, 1.0f, 1.0f, -1.0f);
|
|
|
|
mPSConstants.layerOpacity[0] = 1.0f;
|
|
|
|
ID3D11Buffer* vbuffer = mVertexBuffer;
|
|
UINT vsize = sizeof(Vertex);
|
|
UINT voffset = 0;
|
|
mContext->IASetVertexBuffers(0, 1, &vbuffer, &vsize, &voffset);
|
|
mContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
|
|
mContext->IASetInputLayout(mInputLayout);
|
|
mContext->RSSetViewports(1, &viewport);
|
|
mContext->RSSetScissorRects(1, &scissor);
|
|
mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
|
mContext->VSSetShader(mQuadVS, nullptr, 0);
|
|
mContext->PSSetShader(mQuadPS, nullptr, 0);
|
|
|
|
RefPtr<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, EmptyString());
|
|
|
|
// If the env var is present, we override libName
|
|
if (_wgetenv(L"OVR_LIB_PATH")) {
|
|
searchPath = _wgetenv(L"OVR_LIB_PATH");
|
|
libSearchPaths.InsertElementsAt(0, 1, searchPath);
|
|
}
|
|
|
|
if (_wgetenv(L"OVR_LIB_NAME")) {
|
|
libName = _wgetenv(L"OVR_LIB_NAME");
|
|
}
|
|
|
|
if (libName.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) {
|
|
nsString& libPath = libSearchPaths[i];
|
|
nsString fullName;
|
|
if (libPath.Length() == 0) {
|
|
fullName.Assign(libName);
|
|
} else {
|
|
fullName.Assign(libPath + NS_LITERAL_STRING(u"\\") + libName);
|
|
}
|
|
|
|
mOvrLib = LoadLibraryWithFlags(fullName.get());
|
|
if (mOvrLib) {
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
# error "Unsupported platform!"
|
|
#endif
|
|
|
|
if (!mOvrLib) {
|
|
return false;
|
|
}
|
|
|
|
#define REQUIRE_FUNCTION(_x) \
|
|
do { \
|
|
*(void**)&_x = (void*)PR_FindSymbol(mOvrLib, #_x); \
|
|
if (!_x) { \
|
|
printf_stderr(#_x " symbol missing\n"); \
|
|
goto fail; \
|
|
} \
|
|
} while (0)
|
|
|
|
REQUIRE_FUNCTION(ovr_Initialize);
|
|
REQUIRE_FUNCTION(ovr_Shutdown);
|
|
REQUIRE_FUNCTION(ovr_GetLastErrorInfo);
|
|
REQUIRE_FUNCTION(ovr_GetVersionString);
|
|
REQUIRE_FUNCTION(ovr_TraceMessage);
|
|
REQUIRE_FUNCTION(ovr_IdentifyClient);
|
|
REQUIRE_FUNCTION(ovr_GetHmdDesc);
|
|
REQUIRE_FUNCTION(ovr_GetTrackerCount);
|
|
REQUIRE_FUNCTION(ovr_GetTrackerDesc);
|
|
REQUIRE_FUNCTION(ovr_Create);
|
|
REQUIRE_FUNCTION(ovr_Destroy);
|
|
REQUIRE_FUNCTION(ovr_GetSessionStatus);
|
|
REQUIRE_FUNCTION(ovr_IsExtensionSupported);
|
|
REQUIRE_FUNCTION(ovr_EnableExtension);
|
|
REQUIRE_FUNCTION(ovr_SetTrackingOriginType);
|
|
REQUIRE_FUNCTION(ovr_GetTrackingOriginType);
|
|
REQUIRE_FUNCTION(ovr_RecenterTrackingOrigin);
|
|
REQUIRE_FUNCTION(ovr_SpecifyTrackingOrigin);
|
|
REQUIRE_FUNCTION(ovr_ClearShouldRecenterFlag);
|
|
REQUIRE_FUNCTION(ovr_GetTrackingState);
|
|
REQUIRE_FUNCTION(ovr_GetDevicePoses);
|
|
REQUIRE_FUNCTION(ovr_GetTrackerPose);
|
|
REQUIRE_FUNCTION(ovr_GetInputState);
|
|
REQUIRE_FUNCTION(ovr_GetConnectedControllerTypes);
|
|
REQUIRE_FUNCTION(ovr_GetTouchHapticsDesc);
|
|
REQUIRE_FUNCTION(ovr_SetControllerVibration);
|
|
REQUIRE_FUNCTION(ovr_SubmitControllerVibration);
|
|
REQUIRE_FUNCTION(ovr_GetControllerVibrationState);
|
|
REQUIRE_FUNCTION(ovr_TestBoundary);
|
|
REQUIRE_FUNCTION(ovr_TestBoundaryPoint);
|
|
REQUIRE_FUNCTION(ovr_SetBoundaryLookAndFeel);
|
|
REQUIRE_FUNCTION(ovr_ResetBoundaryLookAndFeel);
|
|
REQUIRE_FUNCTION(ovr_GetBoundaryGeometry);
|
|
REQUIRE_FUNCTION(ovr_GetBoundaryDimensions);
|
|
REQUIRE_FUNCTION(ovr_GetBoundaryVisible);
|
|
REQUIRE_FUNCTION(ovr_RequestBoundaryVisible);
|
|
REQUIRE_FUNCTION(ovr_GetTextureSwapChainLength);
|
|
REQUIRE_FUNCTION(ovr_GetTextureSwapChainCurrentIndex);
|
|
REQUIRE_FUNCTION(ovr_GetTextureSwapChainDesc);
|
|
REQUIRE_FUNCTION(ovr_CommitTextureSwapChain);
|
|
REQUIRE_FUNCTION(ovr_DestroyTextureSwapChain);
|
|
REQUIRE_FUNCTION(ovr_DestroyMirrorTexture);
|
|
REQUIRE_FUNCTION(ovr_GetFovTextureSize);
|
|
REQUIRE_FUNCTION(ovr_GetRenderDesc2);
|
|
REQUIRE_FUNCTION(ovr_WaitToBeginFrame);
|
|
REQUIRE_FUNCTION(ovr_BeginFrame);
|
|
REQUIRE_FUNCTION(ovr_EndFrame);
|
|
REQUIRE_FUNCTION(ovr_SubmitFrame);
|
|
REQUIRE_FUNCTION(ovr_GetPerfStats);
|
|
REQUIRE_FUNCTION(ovr_ResetPerfStats);
|
|
REQUIRE_FUNCTION(ovr_GetPredictedDisplayTime);
|
|
REQUIRE_FUNCTION(ovr_GetTimeInSeconds);
|
|
REQUIRE_FUNCTION(ovr_GetBool);
|
|
REQUIRE_FUNCTION(ovr_SetBool);
|
|
REQUIRE_FUNCTION(ovr_GetInt);
|
|
REQUIRE_FUNCTION(ovr_SetInt);
|
|
REQUIRE_FUNCTION(ovr_GetFloat);
|
|
REQUIRE_FUNCTION(ovr_SetFloat);
|
|
REQUIRE_FUNCTION(ovr_GetFloatArray);
|
|
REQUIRE_FUNCTION(ovr_SetFloatArray);
|
|
REQUIRE_FUNCTION(ovr_GetString);
|
|
REQUIRE_FUNCTION(ovr_SetString);
|
|
REQUIRE_FUNCTION(ovr_GetExternalCameras);
|
|
REQUIRE_FUNCTION(ovr_SetExternalCameraProperties);
|
|
|
|
#ifdef XP_WIN
|
|
|
|
REQUIRE_FUNCTION(ovr_CreateTextureSwapChainDX);
|
|
REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferDX);
|
|
REQUIRE_FUNCTION(ovr_CreateMirrorTextureDX);
|
|
REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferDX);
|
|
|
|
#endif
|
|
|
|
REQUIRE_FUNCTION(ovr_CreateTextureSwapChainGL);
|
|
REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferGL);
|
|
REQUIRE_FUNCTION(ovr_CreateMirrorTextureGL);
|
|
REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferGL);
|
|
|
|
#undef REQUIRE_FUNCTION
|
|
|
|
return true;
|
|
|
|
fail:
|
|
ovr_Initialize = nullptr;
|
|
PR_UnloadLibrary(mOvrLib);
|
|
mOvrLib = nullptr;
|
|
return false;
|
|
}
|
|
|
|
void OculusSession::UnloadOvrLib() {
|
|
if (mOvrLib) {
|
|
PR_UnloadLibrary(mOvrLib);
|
|
mOvrLib = nullptr;
|
|
}
|
|
}
|
|
|
|
bool OculusSession::StartLib(ovrInitFlags aFlags) {
|
|
if (mInitFlags == 0) {
|
|
ovrInitParams params;
|
|
memset(¶ms, 0, sizeof(params));
|
|
params.Flags = aFlags;
|
|
params.RequestedMinorVersion = OVR_MINOR_VERSION;
|
|
params.LogCallback = nullptr;
|
|
params.ConnectionTimeoutMS = 0;
|
|
|
|
ovrResult orv = ovr_Initialize(¶ms);
|
|
|
|
if (orv == ovrSuccess) {
|
|
mInitFlags = aFlags;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
MOZ_ASSERT(mInitFlags == aFlags);
|
|
return true;
|
|
}
|
|
|
|
void OculusSession::StopLib() {
|
|
if (mInitFlags) {
|
|
ovr_Shutdown();
|
|
mInitFlags = (ovrInitFlags)0;
|
|
}
|
|
}
|
|
|
|
bool OculusSession::StartSession() {
|
|
// ovr_Create can be slow when no HMD is present and we wish
|
|
// to keep the same oculus session when possible, so we detect
|
|
// presence of an HMD with ovr_GetHmdDesc before calling ovr_Create
|
|
ovrHmdDesc desc = ovr_GetHmdDesc(NULL);
|
|
if (desc.Type == ovrHmd_None) {
|
|
// No HMD connected, destroy any existing session
|
|
if (mSession) {
|
|
ovr_Destroy(mSession);
|
|
mSession = nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
if (mSession != nullptr) {
|
|
// HMD Detected and we already have a session, let's keep using it.
|
|
return true;
|
|
}
|
|
|
|
// HMD Detected and we don't have a session yet,
|
|
// try to create a new session
|
|
ovrSession session;
|
|
ovrGraphicsLuid luid;
|
|
ovrResult orv = ovr_Create(&session, &luid);
|
|
if (orv == ovrSuccess) {
|
|
orv = ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel);
|
|
if (orv != ovrSuccess) {
|
|
NS_WARNING("ovr_SetTrackingOriginType failed.\n");
|
|
}
|
|
mSession = session;
|
|
return true;
|
|
}
|
|
|
|
// Failed to create a session for the HMD
|
|
return false;
|
|
}
|
|
|
|
void OculusSession::StopSession() {
|
|
if (mSession) {
|
|
ovr_Destroy(mSession);
|
|
mSession = nullptr;
|
|
}
|
|
}
|
|
|
|
bool OculusSession::CreateD3DObjects() {
|
|
RefPtr<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);
|
|
|
|
// default to an identity quaternion
|
|
aSystemState.sensorState.pose.orientation[3] = 1.0f;
|
|
|
|
UpdateStageParameters(state);
|
|
UpdateEyeParameters(aSystemState);
|
|
|
|
VRHMDSensorState& sensorState = aSystemState.sensorState;
|
|
sensorState.flags = (VRDisplayCapabilityFlags)(
|
|
(int)VRDisplayCapabilityFlags::Cap_Orientation |
|
|
(int)VRDisplayCapabilityFlags::Cap_Position);
|
|
sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion
|
|
|
|
return true;
|
|
}
|
|
|
|
void OculusSession::UpdateStageParameters(VRDisplayState& aState) {
|
|
ovrVector3f playArea;
|
|
ovrResult res =
|
|
ovr_GetBoundaryDimensions(mSession, ovrBoundary_PlayArea, &playArea);
|
|
if (res == ovrSuccess) {
|
|
aState.stageSize.width = playArea.x;
|
|
aState.stageSize.height = playArea.z;
|
|
} else {
|
|
// If we fail, fall back to reasonable defaults.
|
|
// 1m x 1m space
|
|
aState.stageSize.width = 1.0f;
|
|
aState.stageSize.height = 1.0f;
|
|
}
|
|
|
|
float eyeHeight =
|
|
ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT);
|
|
|
|
aState.sittingToStandingTransform[0] = 1.0f;
|
|
aState.sittingToStandingTransform[1] = 0.0f;
|
|
aState.sittingToStandingTransform[2] = 0.0f;
|
|
aState.sittingToStandingTransform[3] = 0.0f;
|
|
|
|
aState.sittingToStandingTransform[4] = 0.0f;
|
|
aState.sittingToStandingTransform[5] = 1.0f;
|
|
aState.sittingToStandingTransform[6] = 0.0f;
|
|
aState.sittingToStandingTransform[7] = 0.0f;
|
|
|
|
aState.sittingToStandingTransform[8] = 0.0f;
|
|
aState.sittingToStandingTransform[9] = 0.0f;
|
|
aState.sittingToStandingTransform[10] = 1.0f;
|
|
aState.sittingToStandingTransform[11] = 0.0f;
|
|
|
|
aState.sittingToStandingTransform[12] = 0.0f;
|
|
aState.sittingToStandingTransform[13] = eyeHeight;
|
|
aState.sittingToStandingTransform[14] = 0.0f;
|
|
aState.sittingToStandingTransform[15] = 1.0f;
|
|
}
|
|
|
|
void OculusSession::UpdateEyeParameters(VRSystemState& aState) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
// This must be called every frame in order to
|
|
// account for continuous adjustments to ipd.
|
|
gfx::Matrix4x4 headToEyeTransforms[2];
|
|
for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) {
|
|
// As of Oculus 1.17 SDK, we must use the ovr_GetRenderDesc2 function to
|
|
// return the updated version of ovrEyeRenderDesc. This is normally done by
|
|
// the Oculus static lib shim, but we need to do this explicitly as we are
|
|
// loading the Oculus runtime dll directly.
|
|
ovrEyeRenderDesc renderDesc =
|
|
ovr_GetRenderDesc2(mSession, (ovrEyeType)eye, mFOVPort[eye]);
|
|
aState.displayState.eyeTranslation[eye].x =
|
|
renderDesc.HmdToEyePose.Position.x;
|
|
aState.displayState.eyeTranslation[eye].y =
|
|
renderDesc.HmdToEyePose.Position.y;
|
|
aState.displayState.eyeTranslation[eye].z =
|
|
renderDesc.HmdToEyePose.Position.z;
|
|
|
|
Matrix4x4 pose;
|
|
pose.SetRotationFromQuaternion(
|
|
gfx::Quaternion(renderDesc.HmdToEyePose.Orientation.x,
|
|
renderDesc.HmdToEyePose.Orientation.y,
|
|
renderDesc.HmdToEyePose.Orientation.z,
|
|
renderDesc.HmdToEyePose.Orientation.w));
|
|
pose.PreTranslate(renderDesc.HmdToEyePose.Position.x,
|
|
renderDesc.HmdToEyePose.Position.y,
|
|
renderDesc.HmdToEyePose.Position.z);
|
|
pose.Invert();
|
|
headToEyeTransforms[eye] = pose;
|
|
}
|
|
aState.sensorState.CalcViewMatrices(headToEyeTransforms);
|
|
|
|
Matrix4x4 matView[2];
|
|
memcpy(matView[0].components, aState.sensorState.leftViewMatrix,
|
|
sizeof(float) * 16);
|
|
memcpy(matView[1].components, aState.sensorState.rightViewMatrix,
|
|
sizeof(float) * 16);
|
|
|
|
for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) {
|
|
Point3D eyeTranslation;
|
|
Quaternion eyeRotation;
|
|
Point3D eyeScale;
|
|
if (!matView[eye].Decompose(eyeTranslation, eyeRotation, eyeScale)) {
|
|
NS_WARNING("Failed to decompose eye pose matrix for Oculus");
|
|
}
|
|
|
|
mFrameStartPose[eye].Orientation.x = eyeRotation.x;
|
|
mFrameStartPose[eye].Orientation.y = eyeRotation.y;
|
|
mFrameStartPose[eye].Orientation.z = eyeRotation.z;
|
|
mFrameStartPose[eye].Orientation.w = eyeRotation.w;
|
|
mFrameStartPose[eye].Position.x = eyeTranslation.x;
|
|
mFrameStartPose[eye].Position.y = eyeTranslation.y;
|
|
mFrameStartPose[eye].Position.z = eyeTranslation.z;
|
|
}
|
|
}
|
|
|
|
void OculusSession::UpdateHeadsetPose(VRSystemState& aState) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
double predictedFrameTime = 0.0f;
|
|
if (StaticPrefs::dom_vr_poseprediction_enabled()) {
|
|
// XXX We might need to call ovr_GetPredictedDisplayTime even if we don't
|
|
// use the result. If we don't call it, the Oculus driver will spew out many
|
|
// warnings...
|
|
predictedFrameTime = ovr_GetPredictedDisplayTime(mSession, 0);
|
|
}
|
|
ovrTrackingState trackingState =
|
|
ovr_GetTrackingState(mSession, predictedFrameTime, true);
|
|
ovrPoseStatef& pose(trackingState.HeadPose);
|
|
|
|
aState.sensorState.timestamp = pose.TimeInSeconds;
|
|
|
|
if (trackingState.StatusFlags & ovrStatus_OrientationTracked) {
|
|
aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
|
|
|
|
aState.sensorState.pose.orientation[0] = pose.ThePose.Orientation.x;
|
|
aState.sensorState.pose.orientation[1] = pose.ThePose.Orientation.y;
|
|
aState.sensorState.pose.orientation[2] = pose.ThePose.Orientation.z;
|
|
aState.sensorState.pose.orientation[3] = pose.ThePose.Orientation.w;
|
|
|
|
aState.sensorState.pose.angularVelocity[0] = pose.AngularVelocity.x;
|
|
aState.sensorState.pose.angularVelocity[1] = pose.AngularVelocity.y;
|
|
aState.sensorState.pose.angularVelocity[2] = pose.AngularVelocity.z;
|
|
|
|
aState.sensorState.flags |=
|
|
VRDisplayCapabilityFlags::Cap_AngularAcceleration;
|
|
|
|
aState.sensorState.pose.angularAcceleration[0] = pose.AngularAcceleration.x;
|
|
aState.sensorState.pose.angularAcceleration[1] = pose.AngularAcceleration.y;
|
|
aState.sensorState.pose.angularAcceleration[2] = pose.AngularAcceleration.z;
|
|
} else {
|
|
// default to an identity quaternion
|
|
aState.sensorState.pose.orientation[3] = 1.0f;
|
|
}
|
|
|
|
if (trackingState.StatusFlags & ovrStatus_PositionTracked) {
|
|
float eyeHeight =
|
|
ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT);
|
|
aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
|
|
|
|
aState.sensorState.pose.position[0] = pose.ThePose.Position.x;
|
|
aState.sensorState.pose.position[1] = pose.ThePose.Position.y - eyeHeight;
|
|
aState.sensorState.pose.position[2] = pose.ThePose.Position.z;
|
|
|
|
aState.sensorState.pose.linearVelocity[0] = pose.LinearVelocity.x;
|
|
aState.sensorState.pose.linearVelocity[1] = pose.LinearVelocity.y;
|
|
aState.sensorState.pose.linearVelocity[2] = pose.LinearVelocity.z;
|
|
|
|
aState.sensorState.flags |=
|
|
VRDisplayCapabilityFlags::Cap_LinearAcceleration;
|
|
|
|
aState.sensorState.pose.linearAcceleration[0] = pose.LinearAcceleration.x;
|
|
aState.sensorState.pose.linearAcceleration[1] = pose.LinearAcceleration.y;
|
|
aState.sensorState.pose.linearAcceleration[2] = pose.LinearAcceleration.z;
|
|
}
|
|
aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_External;
|
|
aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_MountDetection;
|
|
aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Present;
|
|
}
|
|
|
|
void OculusSession::UpdateControllers(VRSystemState& aState) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
|
|
ovrInputState inputState;
|
|
bool hasInputState = ovr_GetInputState(mSession, ovrControllerType_Touch,
|
|
&inputState) == ovrSuccess;
|
|
|
|
if (!hasInputState) {
|
|
return;
|
|
}
|
|
|
|
EnumerateControllers(aState, inputState);
|
|
UpdateControllerInputs(aState, inputState);
|
|
UpdateControllerPose(aState, inputState);
|
|
}
|
|
|
|
void OculusSession::UpdateControllerPose(VRSystemState& aState,
|
|
const ovrInputState& aInputState) {
|
|
ovrTrackingState trackingState = ovr_GetTrackingState(mSession, 0.0, false);
|
|
for (uint32_t handIdx = 0; handIdx < 2; handIdx++) {
|
|
// Left Touch Controller will always be at index 0 and
|
|
// and Right Touch Controller will always be at index 1
|
|
VRControllerState& controllerState = aState.controllerState[handIdx];
|
|
if (aInputState.ControllerType & OculusControllerTypes[handIdx]) {
|
|
ovrPoseStatef& pose = trackingState.HandPoses[handIdx];
|
|
bool bNewController = !(controllerState.flags &
|
|
dom::GamepadCapabilityFlags::Cap_Orientation);
|
|
if (bNewController) {
|
|
controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Orientation;
|
|
controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Position;
|
|
controllerState.flags |=
|
|
dom::GamepadCapabilityFlags::Cap_AngularAcceleration;
|
|
controllerState.flags |=
|
|
dom::GamepadCapabilityFlags::Cap_LinearAcceleration;
|
|
}
|
|
|
|
if (bNewController || trackingState.HandStatusFlags[handIdx] &
|
|
ovrStatus_OrientationTracked) {
|
|
controllerState.pose.orientation[0] = pose.ThePose.Orientation.x;
|
|
controllerState.pose.orientation[1] = pose.ThePose.Orientation.y;
|
|
controllerState.pose.orientation[2] = pose.ThePose.Orientation.z;
|
|
controllerState.pose.orientation[3] = pose.ThePose.Orientation.w;
|
|
controllerState.pose.angularVelocity[0] = pose.AngularVelocity.x;
|
|
controllerState.pose.angularVelocity[1] = pose.AngularVelocity.y;
|
|
controllerState.pose.angularVelocity[2] = pose.AngularVelocity.z;
|
|
controllerState.pose.angularAcceleration[0] =
|
|
pose.AngularAcceleration.x;
|
|
controllerState.pose.angularAcceleration[1] =
|
|
pose.AngularAcceleration.y;
|
|
controllerState.pose.angularAcceleration[2] =
|
|
pose.AngularAcceleration.z;
|
|
controllerState.isOrientationValid = true;
|
|
} else {
|
|
controllerState.isOrientationValid = false;
|
|
}
|
|
if (bNewController ||
|
|
trackingState.HandStatusFlags[handIdx] & ovrStatus_PositionTracked) {
|
|
controllerState.pose.position[0] = pose.ThePose.Position.x;
|
|
controllerState.pose.position[1] = pose.ThePose.Position.y;
|
|
controllerState.pose.position[2] = pose.ThePose.Position.z;
|
|
controllerState.pose.linearVelocity[0] = pose.LinearVelocity.x;
|
|
controllerState.pose.linearVelocity[1] = pose.LinearVelocity.y;
|
|
controllerState.pose.linearVelocity[2] = pose.LinearVelocity.z;
|
|
controllerState.pose.linearAcceleration[0] = pose.LinearAcceleration.x;
|
|
controllerState.pose.linearAcceleration[1] = pose.LinearAcceleration.y;
|
|
controllerState.pose.linearAcceleration[2] = pose.LinearAcceleration.z;
|
|
|
|
float eyeHeight =
|
|
ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT);
|
|
controllerState.pose.position[1] -= eyeHeight;
|
|
controllerState.isPositionValid = true;
|
|
} else {
|
|
controllerState.isPositionValid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OculusSession::EnumerateControllers(VRSystemState& aState,
|
|
const ovrInputState& aInputState) {
|
|
for (uint32_t handIdx = 0; handIdx < 2; handIdx++) {
|
|
// Left Touch Controller will always be at index 0 and
|
|
// and Right Touch Controller will always be at index 1
|
|
VRControllerState& controllerState = aState.controllerState[handIdx];
|
|
if (aInputState.ControllerType & OculusControllerTypes[handIdx]) {
|
|
bool bNewController = false;
|
|
// Left Touch Controller detected
|
|
if (controllerState.controllerName[0] == '\0') {
|
|
// Controller has been just enumerated
|
|
strncpy(controllerState.controllerName, OculusControllerNames[handIdx],
|
|
kVRControllerNameMaxLen);
|
|
controllerState.hand = OculusControllerHand[handIdx];
|
|
controllerState.numButtons = kNumOculusButtons;
|
|
controllerState.numAxes = kNumOculusAxes;
|
|
controllerState.numHaptics = kNumOculusHaptcs;
|
|
bNewController = true;
|
|
}
|
|
} else {
|
|
// Left Touch Controller not detected
|
|
if (controllerState.controllerName[0] != '\0') {
|
|
// Clear any newly disconnected ontrollers
|
|
memset(&controllerState, 0, sizeof(VRControllerState));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OculusSession::UpdateControllerInputs(VRSystemState& aState,
|
|
const ovrInputState& aInputState) {
|
|
const float triggerThreshold =
|
|
StaticPrefs::dom_vr_controller_trigger_threshold();
|
|
|
|
for (uint32_t handIdx = 0; handIdx < 2; handIdx++) {
|
|
// Left Touch Controller will always be at index 0 and
|
|
// and Right Touch Controller will always be at index 1
|
|
VRControllerState& controllerState = aState.controllerState[handIdx];
|
|
if (aInputState.ControllerType & OculusControllerTypes[handIdx]) {
|
|
// Update Button States
|
|
controllerState.buttonPressed = 0;
|
|
controllerState.buttonTouched = 0;
|
|
uint32_t buttonIdx = 0;
|
|
|
|
UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
|
|
buttonIdx++;
|
|
UpdateTrigger(controllerState, buttonIdx,
|
|
aInputState.IndexTrigger[handIdx], triggerThreshold);
|
|
UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
|
|
buttonIdx++;
|
|
UpdateTrigger(controllerState, buttonIdx,
|
|
aInputState.HandTrigger[handIdx], triggerThreshold);
|
|
buttonIdx++;
|
|
UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
|
|
buttonIdx++;
|
|
UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
|
|
buttonIdx++;
|
|
UpdateButton(aInputState, handIdx, buttonIdx, controllerState);
|
|
buttonIdx++;
|
|
|
|
MOZ_ASSERT(buttonIdx == kNumOculusButtons);
|
|
|
|
// Update Thumbstick axis
|
|
uint32_t axisIdx = 0;
|
|
float axisValue = aInputState.Thumbstick[handIdx].x;
|
|
if (abs(axisValue) < 0.0000009f) {
|
|
axisValue = 0.0f; // Clear noise signal
|
|
}
|
|
controllerState.axisValue[axisIdx] = axisValue;
|
|
axisIdx++;
|
|
|
|
// Note that y axis is intentionally inverted!
|
|
axisValue = -aInputState.Thumbstick[handIdx].y;
|
|
if (abs(axisValue) < 0.0000009f) {
|
|
axisValue = 0.0f; // Clear noise signal
|
|
}
|
|
controllerState.axisValue[axisIdx] = axisValue;
|
|
axisIdx++;
|
|
|
|
MOZ_ASSERT(axisIdx == kNumOculusAxes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OculusSession::UpdateTelemetry(VRSystemState& aSystemState) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
ovrPerfStats perfStats;
|
|
if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) {
|
|
if (perfStats.FrameStatsCount) {
|
|
aSystemState.displayState.droppedFrameCount =
|
|
perfStats.FrameStats[0].AppDroppedFrameCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OculusSession::VibrateHaptic(uint32_t aControllerIdx,
|
|
uint32_t aHapticIndex, float aIntensity,
|
|
float aDuration) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
|
|
if (aDuration <= 0.0f) {
|
|
StopVibrateHaptic(aControllerIdx);
|
|
return;
|
|
}
|
|
|
|
// Vibration amplitude in the [0.0, 1.0] range
|
|
MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1);
|
|
mHapticPulseIntensity[aControllerIdx] = aIntensity > 1.0 ? 1.0 : aIntensity;
|
|
mRemainingVibrateTime[aControllerIdx] = aDuration;
|
|
ovrControllerType hand = OculusControllerTypes[aControllerIdx];
|
|
|
|
// The gamepad extensions API does not yet have independent control
|
|
// of frequency and amplitude. We are always sending 0.0f (160hz)
|
|
// to the frequency argument.
|
|
ovrResult result = ovr_SetControllerVibration(
|
|
mSession, hand, 0.0f, mHapticPulseIntensity[aControllerIdx]);
|
|
if (result != ovrSuccess) {
|
|
// This may happen if called when not presenting.
|
|
gfxWarning() << "ovr_SetControllerVibration failed.";
|
|
}
|
|
}
|
|
|
|
void OculusSession::StopVibrateHaptic(uint32_t aControllerIdx) {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1);
|
|
ovrControllerType hand = OculusControllerTypes[aControllerIdx];
|
|
mRemainingVibrateTime[aControllerIdx] = 0.0f;
|
|
mHapticPulseIntensity[aControllerIdx] = 0.0f;
|
|
|
|
ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f, 0.0f);
|
|
if (result != ovrSuccess) {
|
|
// This may happen if called when not presenting.
|
|
gfxWarning() << "ovr_SetControllerVibration failed.";
|
|
}
|
|
}
|
|
|
|
void OculusSession::StopAllHaptics() {
|
|
// Left Oculus Touch
|
|
StopVibrateHaptic(0);
|
|
// Right Oculus Touch
|
|
StopVibrateHaptic(1);
|
|
}
|
|
|
|
void OculusSession::UpdateHaptics() {
|
|
if (!mSession) {
|
|
return;
|
|
}
|
|
// The Oculus API and hardware takes at least 33ms to respond
|
|
// to haptic state changes, so it is not beneficial to create
|
|
// a dedicated haptic feedback thread and update multiple
|
|
// times per frame.
|
|
// If we wish to support more accurate effects with sub-frame timing,
|
|
// we should use the buffered haptic feedback API's.
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
if (mLastHapticUpdate.IsNull()) {
|
|
mLastHapticUpdate = now;
|
|
return;
|
|
}
|
|
float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds();
|
|
mLastHapticUpdate = now;
|
|
for (int i = 0; i < 2; i++) {
|
|
if (mRemainingVibrateTime[i] <= 0.0f) {
|
|
continue;
|
|
}
|
|
mRemainingVibrateTime[i] -= deltaTime;
|
|
ovrControllerType hand = OculusControllerTypes[i];
|
|
if (mRemainingVibrateTime[i] > 0.0f) {
|
|
ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f,
|
|
mHapticPulseIntensity[i]);
|
|
if (result != ovrSuccess) {
|
|
// This may happen if called when not presenting.
|
|
gfxWarning() << "ovr_SetControllerVibration failed.";
|
|
}
|
|
} else {
|
|
StopVibrateHaptic(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|