Bug 1466699 - Implement VRService thread r=daoshengmu

- Refactored gfxVROpenVR to use gfxVRExternal interface from the
  VR Service.  Existing gfxVROpenVR left in place (for now) to
  allow VR service to be enabled or disabled by pref.
- The VR service, containing gfxVROpenVR, is to run in-process within
  its own thread first, then to be later moved to its own process.
- Fixed periodic immersive mode flicker that occured due to HMD pose and
  HMD state being separately sampled from the Shmem.  It was possible
  to advance a frame without also getting an updated pose if a dirty
  copy of the shmem was detected.
MozReview-Commit-ID: IvpJErmi5kF

--HG--
extra : rebase_source : 0e21d3414a13dc514c3035f2bd5f6adc365b465d
This commit is contained in:
Kearwood "Kip" Gilbert 2018-05-08 11:31:28 -07:00
Родитель aece1534ef
Коммит d1989d114c
13 изменённых файлов: 1176 добавлений и 22 удалений

Просмотреть файл

@ -390,6 +390,7 @@ private:
DECL_GFX_PREF(Live, "dom.vr.puppet.enabled", VRPuppetEnabled, bool, false);
DECL_GFX_PREF(Live, "dom.vr.puppet.submitframe", VRPuppetSubmitFrame, uint32_t, 0);
DECL_GFX_PREF(Live, "dom.vr.display.rafMaxDuration", VRDisplayRafMaxDuration, uint32_t, 50);
DECL_GFX_PREF(Once, "dom.vr.service.enabled", VRServiceEnabled, bool, false);
DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled", PointerEventsEnabled, bool, false);
DECL_GFX_PREF(Live, "general.smoothScroll", SmoothScrollEnabled, bool, true);

Просмотреть файл

@ -29,6 +29,9 @@
#include "gfxVRPuppet.h"
#include "ipc/VRLayerParent.h"
#if !defined(MOZ_WIDGET_ANDROID)
#include "service/VRService.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
@ -74,31 +77,50 @@ VRManager::VRManager()
* OSVR will be used if Oculus SDK and OpenVR don't detect any HMDS,
* to support everyone else.
*/
mExternalManager = VRSystemManagerExternal::Create();
#if !defined(MOZ_WIDGET_ANDROID)
// The VR Service accesses all hardware from a separate process
// and replaces the other VRSystemManager when enabled.
mVRService = VRService::Create();
if (mVRService) {
mExternalManager = VRSystemManagerExternal::Create(mVRService->GetAPIShmem());
}
if (mExternalManager) {
mManagers.AppendElement(mExternalManager);
}
#endif
if (!mExternalManager) {
mExternalManager = VRSystemManagerExternal::Create();
if (mExternalManager) {
mManagers.AppendElement(mExternalManager);
}
}
#if defined(XP_WIN)
// The Oculus runtime is supported only on Windows
mgr = VRSystemManagerOculus::Create();
if (mgr) {
mManagers.AppendElement(mgr);
if (!mVRService) {
// The Oculus runtime is supported only on Windows
mgr = VRSystemManagerOculus::Create();
if (mgr) {
mManagers.AppendElement(mgr);
}
}
#endif
#if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
// OpenVR is cross platform compatible
mgr = VRSystemManagerOpenVR::Create();
if (mgr) {
mManagers.AppendElement(mgr);
}
// OSVR is cross platform compatible
mgr = VRSystemManagerOSVR::Create();
if (mgr) {
if (!mVRService) {
// OpenVR is cross platform compatible
mgr = VRSystemManagerOpenVR::Create();
if (mgr) {
mManagers.AppendElement(mgr);
}
}
// OSVR is cross platform compatible
mgr = VRSystemManagerOSVR::Create();
if (mgr) {
mManagers.AppendElement(mgr);
}
} // !mVRService
#endif
// Enable gamepad extensions while VR is enabled.
@ -135,6 +157,11 @@ VRManager::Shutdown()
for (uint32_t i = 0; i < mManagers.Length(); ++i) {
mManagers[i]->Shutdown();
}
#if !defined(MOZ_WIDGET_ANDROID)
if (mVRService) {
mVRService->Stop();
}
#endif
}
void
@ -321,6 +348,11 @@ VRManager::RefreshVRDisplays(bool aMustDispatch)
* or interrupt other VR activities.
*/
if (mVRDisplaysRequested || aMustDispatch) {
#if !defined(MOZ_WIDGET_ANDROID)
if (mVRService) {
mVRService->Start();
}
#endif
EnumerateVRDisplays();
}

Просмотреть файл

@ -23,6 +23,9 @@ namespace gfx {
class VRLayerParent;
class VRManagerParent;
class VRDisplayHost;
#if !defined(MOZ_WIDGET_ANDROID)
class VRService;
#endif
class VRSystemManagerPuppet;
class VRSystemManagerExternal;
@ -92,6 +95,9 @@ private:
TimeStamp mLastActiveTime;
RefPtr<VRSystemManagerPuppet> mPuppetManager;
RefPtr<VRSystemManagerExternal> mExternalManager;
#if !defined(MOZ_WIDGET_ANDROID)
RefPtr<VRService> mVRService;
#endif
bool mVRDisplaysRequested;
bool mVRControllersRequested;
};

Просмотреть файл

@ -91,17 +91,12 @@ VRDisplayExternal::Refresh()
VRManager *vm = VRManager::Get();
VRSystemManagerExternal* manager = vm->GetExternalManager();
manager->PullState(&mDisplayInfo.mDisplayState);
manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState);
}
VRHMDSensorState
VRDisplayExternal::GetSensorState()
{
VRManager *vm = VRManager::Get();
VRSystemManagerExternal* manager = vm->GetExternalManager();
manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState);
return mLastSensorState;
}
@ -226,7 +221,7 @@ VRDisplayExternal::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
VRDisplayState displayState;
memset(&displayState, 0, sizeof(VRDisplayState));
while (displayState.mLastSubmittedFrameId < aFrameId) {
if (manager->PullState(&displayState)) {
if (manager->PullState(&displayState, &mLastSensorState)) {
if (!displayState.mIsConnected) {
// Service has shut down or hardware has been disconnected
return false;

Просмотреть файл

@ -54,6 +54,7 @@ SOURCES += [
if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
DIRS += [
'openvr',
'service',
]
SOURCES += [
'gfxVROpenVR.cpp',

Просмотреть файл

@ -0,0 +1,487 @@
#include "OpenVRSession.h"
#if defined(XP_WIN)
#include <d3d11.h>
#include "mozilla/gfx/DeviceManagerDx.h"
#endif // defined(XP_WIN)
#if defined(MOZILLA_INTERNAL_API)
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/dom/GamepadBinding.h"
#endif
#if !defined(M_PI)
#define M_PI 3.14159265358979323846264338327950288
#endif
#define BTN_MASK_FROM_ID(_id) \
::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
using namespace mozilla::gfx;
namespace mozilla {
namespace gfx {
OpenVRSession::OpenVRSession()
: VRSession()
, mVRSystem(nullptr)
, mVRChaperone(nullptr)
, mVRCompositor(nullptr)
, mShouldQuit(false)
{
}
OpenVRSession::~OpenVRSession()
{
Shutdown();
}
bool
OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState)
{
if (mVRSystem != nullptr) {
// Already initialized
return true;
}
if (!::vr::VR_IsHmdPresent()) {
fprintf(stderr, "No HMD detected, VR_IsHmdPresent returned false.\n");
return false;
}
::vr::HmdError err;
::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
if (err) {
return false;
}
mVRSystem = (::vr::IVRSystem *)::vr::VR_GetGenericInterface(::vr::IVRSystem_Version, &err);
if (err || !mVRSystem) {
Shutdown();
return false;
}
mVRChaperone = (::vr::IVRChaperone *)::vr::VR_GetGenericInterface(::vr::IVRChaperone_Version, &err);
if (err || !mVRChaperone) {
Shutdown();
return false;
}
mVRCompositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(::vr::IVRCompositor_Version, &err);
if (err || !mVRCompositor) {
Shutdown();
return false;
}
#if defined(XP_WIN)
if (!CreateD3DObjects()) {
Shutdown();
return false;
}
#endif
// Configure coordinate system
mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated);
if (!InitState(aSystemState)) {
Shutdown();
return false;
}
// Succeeded
return true;
}
#if defined(XP_WIN)
bool
OpenVRSession::CreateD3DObjects()
{
RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
if (!device) {
return false;
}
if (!CreateD3DContext(device)) {
return false;
}
return true;
}
#endif
void
OpenVRSession::Shutdown()
{
if (mVRSystem || mVRCompositor || mVRSystem) {
::vr::VR_Shutdown();
mVRCompositor = nullptr;
mVRChaperone = nullptr;
mVRSystem = nullptr;
}
}
bool
OpenVRSession::InitState(VRSystemState& aSystemState)
{
VRDisplayState& state = aSystemState.displayState;
strncpy(state.mDisplayName, "OpenVR HMD", kVRDisplayNameMaxLen);
state.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
state.mIsMounted = false;
state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
(int)VRDisplayCapabilityFlags::Cap_Orientation |
(int)VRDisplayCapabilityFlags::Cap_Position |
(int)VRDisplayCapabilityFlags::Cap_External |
(int)VRDisplayCapabilityFlags::Cap_Present |
(int)VRDisplayCapabilityFlags::Cap_StageParameters);
::vr::ETrackedPropertyError err;
bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err);
if (err == ::vr::TrackedProp_Success && bHasProximitySensor) {
state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)state.mCapabilityFlags | (int)VRDisplayCapabilityFlags::Cap_MountDetection);
}
uint32_t w, h;
mVRSystem->GetRecommendedRenderTargetSize(&w, &h);
state.mEyeResolution.width = w;
state.mEyeResolution.height = h;
// default to an identity quaternion
aSystemState.sensorState.orientation[3] = 1.0f;
UpdateStageParameters(state);
UpdateEyeParameters(state);
VRHMDSensorState& sensorState = aSystemState.sensorState;
sensorState.flags = (VRDisplayCapabilityFlags)(
(int)VRDisplayCapabilityFlags::Cap_Orientation |
(int)VRDisplayCapabilityFlags::Cap_Position);
sensorState.orientation[3] = 1.0f; // Default to an identity quaternion
return true;
}
void
OpenVRSession::UpdateStageParameters(VRDisplayState& state)
{
float sizeX = 0.0f;
float sizeZ = 0.0f;
if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) {
::vr::HmdMatrix34_t t = mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
state.mStageSize.width = sizeX;
state.mStageSize.height = sizeZ;
state.mSittingToStandingTransform[0] = t.m[0][0];
state.mSittingToStandingTransform[1] = t.m[1][0];
state.mSittingToStandingTransform[2] = t.m[2][0];
state.mSittingToStandingTransform[3] = 0.0f;
state.mSittingToStandingTransform[4] = t.m[0][1];
state.mSittingToStandingTransform[5] = t.m[1][1];
state.mSittingToStandingTransform[6] = t.m[2][1];
state.mSittingToStandingTransform[7] = 0.0f;
state.mSittingToStandingTransform[8] = t.m[0][2];
state.mSittingToStandingTransform[9] = t.m[1][2];
state.mSittingToStandingTransform[10] = t.m[2][2];
state.mSittingToStandingTransform[11] = 0.0f;
state.mSittingToStandingTransform[12] = t.m[0][3];
state.mSittingToStandingTransform[13] = t.m[1][3];
state.mSittingToStandingTransform[14] = t.m[2][3];
state.mSittingToStandingTransform[15] = 1.0f;
} else {
// If we fail, fall back to reasonable defaults.
// 1m x 1m space, 0.75m high in seated position
state.mStageSize.width = 1.0f;
state.mStageSize.height = 1.0f;
state.mSittingToStandingTransform[0] = 1.0f;
state.mSittingToStandingTransform[1] = 0.0f;
state.mSittingToStandingTransform[2] = 0.0f;
state.mSittingToStandingTransform[3] = 0.0f;
state.mSittingToStandingTransform[4] = 0.0f;
state.mSittingToStandingTransform[5] = 1.0f;
state.mSittingToStandingTransform[6] = 0.0f;
state.mSittingToStandingTransform[7] = 0.0f;
state.mSittingToStandingTransform[8] = 0.0f;
state.mSittingToStandingTransform[9] = 0.0f;
state.mSittingToStandingTransform[10] = 1.0f;
state.mSittingToStandingTransform[11] = 0.0f;
state.mSittingToStandingTransform[12] = 0.0f;
state.mSittingToStandingTransform[13] = 0.75f;
state.mSittingToStandingTransform[14] = 0.0f;
state.mSittingToStandingTransform[15] = 1.0f;
}
}
void
OpenVRSession::UpdateEyeParameters(VRDisplayState& state, gfx::Matrix4x4* headToEyeTransforms /* = nullptr */)
{
for (uint32_t eye = 0; eye < 2; ++eye) {
::vr::HmdMatrix34_t eyeToHead = mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye));
state.mEyeTranslation[eye].x = eyeToHead.m[0][3];
state.mEyeTranslation[eye].y = eyeToHead.m[1][3];
state.mEyeTranslation[eye].z = eyeToHead.m[2][3];
float left, right, up, down;
mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &left, &right, &up, &down);
state.mEyeFOV[eye].upDegrees = atan(-up) * 180.0 / M_PI;
state.mEyeFOV[eye].rightDegrees = atan(right) * 180.0 / M_PI;
state.mEyeFOV[eye].downDegrees = atan(down) * 180.0 / M_PI;
state.mEyeFOV[eye].leftDegrees = atan(-left) * 180.0 / M_PI;
if (headToEyeTransforms) {
Matrix4x4 pose;
// NOTE! eyeToHead.m is a 3x4 matrix, not 4x4. But
// because of its arrangement, we can copy the 12 elements in and
// then transpose them to the right place.
memcpy(&pose._11, &eyeToHead.m, sizeof(eyeToHead.m));
pose.Transpose();
pose.Invert();
headToEyeTransforms[eye] = pose;
}
}
}
void
OpenVRSession::GetSensorState(VRSystemState& state)
{
const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1;
::vr::TrackedDevicePose_t poses[posesSize];
// Note: We *must* call WaitGetPoses in order for any rendering to happen at all.
mVRCompositor->WaitGetPoses(nullptr, 0, poses, posesSize);
gfx::Matrix4x4 headToEyeTransforms[2];
UpdateEyeParameters(state.displayState, headToEyeTransforms);
::vr::Compositor_FrameTiming timing;
timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
if (mVRCompositor->GetFrameTiming(&timing)) {
state.sensorState.timestamp = timing.m_flSystemTimeInSeconds;
} else {
// This should not happen, but log it just in case
fprintf(stderr, "OpenVR - IVRCompositor::GetFrameTiming failed");
}
if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == ::vr::TrackingResult_Running_OK)
{
const ::vr::TrackedDevicePose_t& pose = poses[::vr::k_unTrackedDeviceIndex_Hmd];
gfx::Matrix4x4 m;
// NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
// because of its arrangement, we can copy the 12 elements in and
// then transpose them to the right place. We do this so we can
// pull out a Quaternion.
memcpy(&m._11, &pose.mDeviceToAbsoluteTracking, sizeof(pose.mDeviceToAbsoluteTracking));
m.Transpose();
gfx::Quaternion rot;
rot.SetFromRotationMatrix(m);
rot.Invert();
state.sensorState.flags = (VRDisplayCapabilityFlags)((int)state.sensorState.flags | (int)VRDisplayCapabilityFlags::Cap_Orientation);
state.sensorState.orientation[0] = rot.x;
state.sensorState.orientation[1] = rot.y;
state.sensorState.orientation[2] = rot.z;
state.sensorState.orientation[3] = rot.w;
state.sensorState.angularVelocity[0] = pose.vAngularVelocity.v[0];
state.sensorState.angularVelocity[1] = pose.vAngularVelocity.v[1];
state.sensorState.angularVelocity[2] = pose.vAngularVelocity.v[2];
state.sensorState.flags =(VRDisplayCapabilityFlags)((int)state.sensorState.flags | (int)VRDisplayCapabilityFlags::Cap_Position);
state.sensorState.position[0] = m._41;
state.sensorState.position[1] = m._42;
state.sensorState.position[2] = m._43;
state.sensorState.linearVelocity[0] = pose.vVelocity.v[0];
state.sensorState.linearVelocity[1] = pose.vVelocity.v[1];
state.sensorState.linearVelocity[2] = pose.vVelocity.v[2];
}
state.sensorState.CalcViewMatrices(headToEyeTransforms);
state.sensorState.inputFrameID++;
}
void
OpenVRSession::GetControllerState(VRSystemState &state)
{
// TODO - Implement
}
void
OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState)
{
GetSensorState(aSystemState);
GetControllerState(aSystemState);
}
bool
OpenVRSession::ShouldQuit() const
{
return mShouldQuit;
}
void
OpenVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState)
{
bool isHmdPresent = ::vr::VR_IsHmdPresent();
if (!isHmdPresent) {
mShouldQuit = true;
}
::vr::VREvent_t event;
while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
switch (event.eventType) {
case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
aSystemState.displayState.mIsMounted = true;
}
break;
case ::vr::VREvent_TrackedDeviceUserInteractionEnded:
if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
aSystemState.displayState.mIsMounted = false;
}
break;
case ::vr::EVREventType::VREvent_TrackedDeviceActivated:
if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
aSystemState.displayState.mIsConnected = true;
}
break;
case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
aSystemState.displayState.mIsConnected = false;
}
break;
case ::vr::EVREventType::VREvent_DriverRequestedQuit:
case ::vr::EVREventType::VREvent_Quit:
case ::vr::EVREventType::VREvent_ProcessQuit:
case ::vr::EVREventType::VREvent_QuitAcknowledged:
case ::vr::EVREventType::VREvent_QuitAborted_UserPrompt:
mShouldQuit = true;
break;
default:
// ignore
break;
}
}
}
bool
OpenVRSession::SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer)
{
#if defined(XP_WIN)
if (aLayer.mTextureType == VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor) {
RefPtr<ID3D11Texture2D> dxTexture;
HRESULT hr = mDevice->OpenSharedResource((HANDLE)aLayer.mTextureHandle,
__uuidof(ID3D11Texture2D),
(void**)(ID3D11Texture2D**)getter_AddRefs(dxTexture));
if (FAILED(hr) || !dxTexture) {
NS_WARNING("Failed to open shared texture");
return false;
}
// Similar to LockD3DTexture in TextureD3D11.cpp
RefPtr<IDXGIKeyedMutex> mutex;
dxTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
if (mutex) {
HRESULT hr = mutex->AcquireSync(0, 1000);
if (hr == WAIT_TIMEOUT) {
gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
}
else if (hr == WAIT_ABANDONED) {
gfxCriticalNote << "GFX: D3D11 lock mutex abandoned";
}
if (FAILED(hr)) {
NS_WARNING("Failed to lock the texture");
return false;
}
}
bool success = SubmitFrame((void *)dxTexture,
::vr::ETextureType::TextureType_DirectX,
aLayer.mLeftEyeRect, aLayer.mRightEyeRect);
if (mutex) {
HRESULT hr = mutex->ReleaseSync(0);
if (FAILED(hr)) {
NS_WARNING("Failed to unlock the texture");
}
}
if (!success) {
return false;
}
return true;
}
#elif defined(XP_MACOSX)
if (aLayer.mTextureType == VRLayerTextureType::LayerTextureType_MacIOSurface) {
return SubmitFrame(aLayer.mTextureHandle,
::vr::ETextureType::TextureType_IOSurface,
aLayer.mLeftEyeRect, aLayer.mRightEyeRect);
}
#endif
return false;
}
bool
OpenVRSession::SubmitFrame(void* aTextureHandle,
::vr::ETextureType aTextureType,
const VRLayerEyeRect& aLeftEyeRect,
const VRLayerEyeRect& aRightEyeRect)
{
::vr::Texture_t tex;
tex.handle = aTextureHandle;
tex.eType = aTextureType;
tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
::vr::VRTextureBounds_t bounds;
bounds.uMin = aLeftEyeRect.x;
bounds.vMin = 1.0 - aLeftEyeRect.y;
bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width;
bounds.vMax = 1.0 - (aLeftEyeRect.y + aLeftEyeRect.height);
::vr::EVRCompositorError err;
err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds);
if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
printf_stderr("OpenVR Compositor Submit() failed.\n");
}
bounds.uMin = aRightEyeRect.x;
bounds.vMin = 1.0 - aRightEyeRect.y;
bounds.uMax = aRightEyeRect.x + aRightEyeRect.width;
bounds.vMax = 1.0 - (aRightEyeRect.y + aRightEyeRect.height);
err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds);
if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
printf_stderr("OpenVR Compositor Submit() failed.\n");
}
mVRCompositor->PostPresentHandoff();
return true;
}
void
OpenVRSession::StopPresentation()
{
mVRCompositor->ClearLastSubmittedFrame();
::vr::Compositor_CumulativeStats stats;
mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats));
// TODO - Need to send telemetry back to browser.
// Bug 1473398 will refactor this original gfxVROpenVR code:
// const uint32_t droppedFramesPerSec = (stats.m_nNumReprojectedFrames -
// mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds();
// Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR, droppedFramesPerSec);
}
bool
OpenVRSession::StartPresentation()
{
return true;
}
} // namespace mozilla
} // namespace gfx

Просмотреть файл

@ -0,0 +1,65 @@
/* -*- 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 GFX_VR_SERVICE_OPENVRSESSION_H
#define GFX_VR_SERVICE_OPENVRSESSION_H
#include "VRSession.h"
#include "openvr.h"
#include "mozilla/gfx/2D.h"
#include "moz_external_vr.h"
#if defined(XP_WIN)
#include <d3d11_1.h>
#elif defined(XP_MACOSX)
class MacIOSurface;
#endif
namespace mozilla {
namespace gfx {
class OpenVRSession : public VRSession
{
public:
OpenVRSession();
virtual ~OpenVRSession();
bool Initialize(mozilla::gfx::VRSystemState& aSystemState) override;
void Shutdown() override;
void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override;
void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override;
bool ShouldQuit() const override;
bool StartPresentation() override;
void StopPresentation() override;
bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) override;
private:
// OpenVR State
::vr::IVRSystem* mVRSystem = nullptr;
::vr::IVRChaperone* mVRChaperone = nullptr;
::vr::IVRCompositor* mVRCompositor = nullptr;
bool mShouldQuit;
bool InitState(mozilla::gfx::VRSystemState& aSystemState);
void UpdateStageParameters(mozilla::gfx::VRDisplayState& state);
void UpdateEyeParameters(mozilla::gfx::VRDisplayState& state, gfx::Matrix4x4* headToEyeTransforms = nullptr);
void GetSensorState(mozilla::gfx::VRSystemState& state);
void GetControllerState(VRSystemState &state);
bool SubmitFrame(void* aTextureHandle,
::vr::ETextureType aTextureType,
const VRLayerEyeRect& aLeftEyeRect,
const VRLayerEyeRect& aRightEyeRect);
#if defined(XP_WIN)
bool CreateD3DObjects();
#endif
};
} // namespace mozilla
} // namespace gfx
#endif // GFX_VR_SERVICE_OPENVRSESSION_H

Просмотреть файл

@ -0,0 +1,332 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VRService.h"
#include "OpenVRSession.h"
#include "gfxPrefs.h"
#include "base/thread.h" // for Thread
using namespace mozilla;
using namespace mozilla::gfx;
using namespace std;
namespace {
int64_t
FrameIDFromBrowserState(const mozilla::gfx::VRBrowserState& aState)
{
for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
const VRLayerState& layer = aState.layerState[iLayer];
if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
return layer.layer_stereo_immersive.mFrameId;
}
}
return 0;
}
bool
IsImmersiveContentActive(const mozilla::gfx::VRBrowserState& aState)
{
for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
const VRLayerState& layer = aState.layerState[iLayer];
if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
return true;
}
}
return false;
}
} // anonymous namespace
/*static*/ already_AddRefed<VRService>
VRService::Create()
{
MOZ_ASSERT(NS_IsMainThread());
if (!gfxPrefs::VRServiceEnabled()) {
return nullptr;
}
RefPtr<VRService> service = new VRService();
return service.forget();
}
VRService::VRService()
: mSystemState{}
, mBrowserState{}
, mServiceThread(nullptr)
, mShutdownRequested(false)
{
memset(&mAPIShmem, 0, sizeof(mAPIShmem));
}
VRService::~VRService()
{
Stop();
}
void
VRService::Start()
{
if (!mServiceThread) {
/**
* We must ensure that any time the service is re-started, that
* the VRSystemState is reset, including mSystemState.enumerationCompleted
* This must happen before VRService::Start returns to the caller, in order
* to prevent the WebVR/WebXR promises from being resolved before the
* enumeration has been completed.
*/
memset(&mSystemState, 0, sizeof(mSystemState));
PushState(mSystemState);
mServiceThread = new base::Thread("VRService");
base::Thread::Options options;
/* Timeout values are powers-of-two to enable us get better data.
128ms is chosen for transient hangs because 8Hz should be the minimally
acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
options.transient_hang_timeout = 128; // milliseconds
/* 2048ms is chosen for permanent hangs because it's longer than most
* Compositor hangs seen in the wild, but is short enough to not miss getting
* native hang stacks. */
options.permanent_hang_timeout = 2048; // milliseconds
if (!mServiceThread->StartWithOptions(options)) {
delete mServiceThread;
mServiceThread = nullptr;
return;
}
mServiceThread->message_loop()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceInitialize",
this, &VRService::ServiceInitialize
));
}
}
void
VRService::Stop()
{
if (mServiceThread) {
mServiceThread->message_loop()->PostTask(NewRunnableMethod(
"gfx::VRService::RequestShutdown",
this, &VRService::RequestShutdown
));
delete mServiceThread;
mServiceThread = nullptr;
}
}
bool
VRService::IsInServiceThread()
{
return mServiceThread && mServiceThread->thread_id() == PlatformThread::CurrentId();
}
void
VRService::RequestShutdown()
{
MOZ_ASSERT(IsInServiceThread());
mShutdownRequested = true;
}
void
VRService::ServiceInitialize()
{
MOZ_ASSERT(IsInServiceThread());
mShutdownRequested = false;
memset(&mBrowserState, 0, sizeof(mBrowserState));
// Try to start a VRSession
unique_ptr<VRSession> session;
// Try OpenVR
session = make_unique<OpenVRSession>();
if (!session->Initialize(mSystemState)) {
session = nullptr;
}
if (session) {
mSession = std::move(session);
// Setting enumerationCompleted to true indicates to the browser
// that it should resolve any promises in the WebVR/WebXR API
// waiting for hardware detection.
mSystemState.enumerationCompleted = true;
PushState(mSystemState);
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceWaitForImmersive",
this, &VRService::ServiceWaitForImmersive
));
} else {
// VR hardware was not detected.
// We must inform the browser of the failure so it may try again
// later and resolve WebVR promises. A failure or shutdown is
// indicated by enumerationCompleted being set to true, with all
// other fields remaining zeroed out.
memset(&mSystemState, 0, sizeof(mSystemState));
mSystemState.enumerationCompleted = true;
PushState(mSystemState);
}
}
void
VRService::ServiceShutdown()
{
MOZ_ASSERT(IsInServiceThread());
mSession = nullptr;
// Notify the browser that we have shut down.
// This is indicated by enumerationCompleted being set
// to true, with all other fields remaining zeroed out.
memset(&mSystemState, 0, sizeof(mSystemState));
mSystemState.enumerationCompleted = true;
PushState(mSystemState);
}
void
VRService::ServiceWaitForImmersive()
{
MOZ_ASSERT(IsInServiceThread());
MOZ_ASSERT(mSession);
mSession->ProcessEvents(mSystemState);
PushState(mSystemState);
PullState(mBrowserState);
if (mSession->ShouldQuit() || mShutdownRequested) {
// Shut down
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceShutdown",
this, &VRService::ServiceShutdown
));
} else if (IsImmersiveContentActive(mBrowserState)) {
// Enter Immersive Mode
mSession->StartPresentation();
mSession->StartFrame(mSystemState);
PushState(mSystemState);
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceImmersiveMode",
this, &VRService::ServiceImmersiveMode
));
} else {
// Continue waiting for immersive mode
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceWaitForImmersive",
this, &VRService::ServiceWaitForImmersive
));
}
}
void
VRService::ServiceImmersiveMode()
{
MOZ_ASSERT(IsInServiceThread());
MOZ_ASSERT(mSession);
mSession->ProcessEvents(mSystemState);
PushState(mSystemState);
PullState(mBrowserState);
if (mSession->ShouldQuit() || mShutdownRequested) {
// Shut down
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceShutdown",
this, &VRService::ServiceShutdown
));
return;
} else if (!IsImmersiveContentActive(mBrowserState)) {
// Exit immersive mode
mSession->StopPresentation();
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceWaitForImmersive",
this, &VRService::ServiceWaitForImmersive
));
return;
}
uint64_t newFrameId = FrameIDFromBrowserState(mBrowserState);
if (newFrameId != mSystemState.displayState.mLastSubmittedFrameId) {
// A new immersive frame has been received.
// Submit the textures to the VR system compositor.
bool success = false;
for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
const VRLayerState& layer = mBrowserState.layerState[iLayer];
if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
success = mSession->SubmitFrame(layer.layer_stereo_immersive);
break;
}
}
// Changing mLastSubmittedFrameId triggers a new frame to start
// rendering. Changes to mLastSubmittedFrameId and the values
// used for rendering, such as headset pose, must be pushed
// atomically to the browser.
mSystemState.displayState.mLastSubmittedFrameId = newFrameId;
mSystemState.displayState.mLastSubmittedFrameSuccessful = success;
mSession->StartFrame(mSystemState);
PushState(mSystemState);
}
// Continue immersive mode
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceImmersiveMode",
this, &VRService::ServiceImmersiveMode
));
}
void
VRService::PushState(const mozilla::gfx::VRSystemState& aState)
{
// Copying the VR service state to the shmem is atomic, infallable,
// and non-blocking on x86/x64 architectures. Arm requires a mutex
// that is locked for the duration of the memcpy to and from shmem on
// both sides.
#if defined(MOZ_WIDGET_ANDROID)
if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == 0) {
memcpy((void *)&mAPIShmem.state, &aState, sizeof(VRSystemState));
pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
}
#else
mAPIShmem.generationA++;
memcpy((void *)&mAPIShmem.state, &aState, sizeof(VRSystemState));
mAPIShmem.generationB++;
#endif
}
void
VRService::PullState(mozilla::gfx::VRBrowserState& aState)
{
// Copying the browser state from the shmem is non-blocking
// on x86/x64 architectures. Arm requires a mutex that is
// locked for the duration of the memcpy to and from shmem on
// both sides.
// On x86/x64 It is fallable -- If a dirty copy is detected by
// a mismatch of browserGenerationA and browserGenerationB,
// the copy is discarded and will not replace the last known
// browser state.
#if defined(MOZ_WIDGET_ANDROID)
if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->browserMutex)) == 0) {
memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->browserMutex));
}
#else
VRExternalShmem tmp;
memcpy(&tmp, &mAPIShmem, sizeof(VRExternalShmem));
if (tmp.browserGenerationA == tmp.browserGenerationB && tmp.browserGenerationA != 0 && tmp.browserGenerationA != -1) {
memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
}
#endif
}
VRExternalShmem*
VRService::GetAPIShmem()
{
return &mAPIShmem;
}

Просмотреть файл

@ -0,0 +1,79 @@
/* -*- 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 GFX_VR_SERVICE_VRSERVICE_H
#define GFX_VR_SERVICE_VRSERVICE_H
#include "mozilla/Atomics.h"
#include "moz_external_vr.h"
#include <thread>
namespace base {
class Thread;
} // namespace base
namespace mozilla {
namespace gfx {
class VRSession;
class VRService
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRService)
static already_AddRefed<VRService> Create();
void Start();
void Stop();
VRExternalShmem* GetAPIShmem();
private:
VRService();
~VRService();
void PushState(const mozilla::gfx::VRSystemState& aState);
void PullState(mozilla::gfx::VRBrowserState& aState);
/**
* VRSystemState contains the most recent state of the VR
* system, to be shared with the browser by Shmem.
* mSystemState is the VR Service copy of this data, which
* is memcpy'ed atomically to the Shmem.
* VRSystemState is written by the VR Service, but read-only
* by the browser.
*/
VRSystemState mSystemState;
/**
* VRBrowserState contains the most recent state of the browser.
* mBrowserState is memcpy'ed from the Shmem atomically
*/
VRBrowserState mBrowserState;
std::unique_ptr<VRSession> mSession;
base::Thread* mServiceThread;
bool mShutdownRequested;
VRExternalShmem mAPIShmem;
bool IsInServiceThread();
void RequestShutdown();
/**
* The VR Service thread is a state machine that always has one
* task queued depending on the state.
*
* VR Service thread state task functions:
*/
void ServiceInitialize();
void ServiceShutdown();
void ServiceWaitForImmersive();
void ServiceImmersiveMode();
};
} // namespace gfx
} // namespace mozilla
#endif // GFX_VR_SERVICE_VRSERVICE_H

Просмотреть файл

@ -0,0 +1,80 @@
#include "VRSession.h"
#include "moz_external_vr.h"
#if defined(XP_WIN)
#include <d3d11.h>
#endif // defined(XP_WIN)
using namespace mozilla::gfx;
VRSession::VRSession()
{
}
VRSession::~VRSession()
{
}
#if defined(XP_WIN)
bool
VRSession::CreateD3DContext(RefPtr<ID3D11Device> aDevice)
{
if (!mDevice) {
if (!aDevice) {
NS_WARNING("OpenVRSession::CreateD3DObjects failed to get a D3D11Device");
return false;
}
if (FAILED(aDevice->QueryInterface(__uuidof(ID3D11Device1), getter_AddRefs(mDevice)))) {
NS_WARNING("OpenVRSession::CreateD3DObjects failed to get a D3D11Device1");
return false;
}
}
if (!mContext) {
mDevice->GetImmediateContext1(getter_AddRefs(mContext));
if (!mContext) {
NS_WARNING("OpenVRSession::CreateD3DObjects failed to get an immediate context");
return false;
}
}
if (!mDeviceContextState) {
D3D_FEATURE_LEVEL featureLevels[] {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0
};
mDevice->CreateDeviceContextState(0,
featureLevels,
2,
D3D11_SDK_VERSION,
__uuidof(ID3D11Device1),
nullptr,
getter_AddRefs(mDeviceContextState));
}
if (!mDeviceContextState) {
NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11DeviceContextState");
return false;
}
return true;
}
ID3D11Device1*
VRSession::GetD3DDevice()
{
return mDevice;
}
ID3D11DeviceContext1*
VRSession::GetD3DDeviceContext()
{
return mContext;
}
ID3DDeviceContextState*
VRSession::GetD3DDeviceContextState()
{
return mDeviceContextState;
}
#endif // defined(XP_WIN)

Просмотреть файл

@ -0,0 +1,53 @@
/* -*- 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 GFX_VR_SERVICE_VRSESSION_H
#define GFX_VR_SERVICE_VRSESSION_H
#include "VRSession.h"
#include "moz_external_vr.h"
#if defined(XP_WIN)
#include <d3d11_1.h>
#elif defined(XP_MACOSX)
class MacIOSurface;
#endif
namespace mozilla {
namespace gfx {
class VRSession
{
public:
VRSession();
virtual ~VRSession();
virtual bool Initialize(mozilla::gfx::VRSystemState& aSystemState) = 0;
virtual void Shutdown() = 0;
virtual void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) = 0;
virtual void StartFrame(mozilla::gfx::VRSystemState& aSystemState) = 0;
virtual bool ShouldQuit() const = 0;
virtual bool StartPresentation() = 0;
virtual void StopPresentation() = 0;
virtual bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) = 0;
#if defined(XP_WIN)
protected:
bool CreateD3DContext(RefPtr<ID3D11Device> aDevice);
RefPtr<ID3D11Device1> mDevice;
RefPtr<ID3D11DeviceContext1> mContext;
ID3D11Device1* GetD3DDevice();
ID3D11DeviceContext1* GetD3DDeviceContext();
ID3DDeviceContextState* GetD3DDeviceContextState();
RefPtr<ID3DDeviceContextState> mDeviceContextState;
#endif
};
} // namespace mozilla
} // namespace gfx
#endif // GFX_VR_SERVICE_VRSESSION_H

21
gfx/vr/service/moz.build Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
# Build OpenVR on Windows, Linux, and macOS desktop targets
if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
UNIFIED_SOURCES += [
'OpenVRSession.cpp',
'VRService.cpp',
'VRSession.cpp',
]
LOCAL_INCLUDES += [
'/dom/base',
'/gfx/layers/d3d11',
'/gfx/thebes',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -5283,6 +5283,8 @@ pref("dom.vr.puppet.submitframe", 0);
pref("dom.vr.display.rafMaxDuration", 50);
// VR test system.
pref("dom.vr.test.enabled", false);
// Enable the VR Service, which interfaces with VR hardware in a separate thread
pref("dom.vr.service.enabled", false);
// If the user puts a finger down on an element and we think the user
// might be executing a pan gesture, how long do we wait before