Bug 1473397 - Implement haptic feedback support for gfxVRExternal and OpenVRSession r=daoshengmu

- Implemented 1ms, 10ms, and 100ms VR tasks, dispatched from
  VRManager
- Removed Android-specific code that compensated for
  tasks that did not run when the...

...compositor was paused.

Differential Revision: https://phabricator.services.mozilla.com/D3378

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Kearwood "Kip" Gilbert 2018-09-25 22:56:10 +00:00
Родитель 2611229c08
Коммит f65dbeca74
21 изменённых файлов: 847 добавлений и 121 удалений

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

@ -8,14 +8,22 @@ support-files =
[test_vrController_displayId.html]
# Enable Linux after Bug 1310655 # TIMED_OUT for Android.
skip-if = (os != "win" && release_or_beta) || (os == "android")
# skip-if = (os != "win" && release_or_beta) || (os == "android")
# Re-enable this once Bug 1466702 has landed
skip-if = true
[test_vrDisplay_canvas2d.html]
skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
# skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
# Re-enable this once Bug 1466702 has landed
skip-if = true
[test_vrDisplay_exitPresent.html]
skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
# skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
# Re-enable this once Bug 1466702 has landed
skip-if = true
[test_vrDisplay_getFrameData.html]
# Enable Linux after Bug 1310655, enable Android after Bug 1348246
skip-if = (os != "win" && release_or_beta) || (os == "android")
# skip-if = (os != "win" && release_or_beta) || (os == "android")
# Re-enable this once Bug 1466702 has landed
skip-if = true
[test_vrDisplay_onvrdisplayconnect.html]
skip-if = true
[test_vrDisplay_onvrdisplaydeactivate_crosscontent.html]

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

@ -208,9 +208,11 @@ VRDisplayHost::StartFrame()
{
AUTO_PROFILER_TRACING("VR", "GetSensorState");
TimeStamp now = TimeStamp::Now();
#if defined(MOZ_WIDGET_ANDROID)
const TimeStamp lastFrameStart = mDisplayInfo.mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
double duration = mLastFrameStart.IsNull() ? 0.0 : (TimeStamp::Now() - mLastFrameStart).ToMilliseconds();
double duration = lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
/**
* Do not start more VR frames until the last submitted frame is already processed.
*/
@ -219,9 +221,10 @@ VRDisplayHost::StartFrame()
}
#endif // !defined(MOZ_WIDGET_ANDROID)
mLastFrameStart = TimeStamp::Now();
++mDisplayInfo.mFrameId;
mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames] = GetSensorState();
size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
mDisplayInfo.mLastSensorState[bufferIndex] = GetSensorState();
mDisplayInfo.mLastFrameStart[bufferIndex] = now;
mFrameStarted = true;
#if defined(MOZ_WIDGET_ANDROID)
mLastStartedFrame = mDisplayInfo.mFrameId;
@ -230,6 +233,20 @@ VRDisplayHost::StartFrame()
void
VRDisplayHost::NotifyVSync()
{
/**
* If this display isn't presenting, refresh the sensors and trigger
* VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
*/
if (mDisplayInfo.mPresentingGroups == 0) {
VRManager *vm = VRManager::Get();
MOZ_ASSERT(vm);
vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
}
}
void
VRDisplayHost::CheckWatchDog()
{
/**
* We will trigger a new frame immediately after a successful frame texture
@ -260,20 +277,15 @@ VRDisplayHost::NotifyVSync()
*/
bool bShouldStartFrame = false;
if (mDisplayInfo.mPresentingGroups == 0) {
// If this display isn't presenting, refresh the sensors and trigger
// VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
// If content fails to call VRDisplay.submitFrame, we must eventually
// time-out and trigger a new frame.
TimeStamp lastFrameStart = mDisplayInfo.mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
if (lastFrameStart.IsNull()) {
bShouldStartFrame = true;
} else {
// If content fails to call VRDisplay.submitFrame, we must eventually
// time-out and trigger a new frame.
if (mLastFrameStart.IsNull()) {
TimeDuration duration = TimeStamp::Now() - lastFrameStart;
if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) {
bShouldStartFrame = true;
} else {
TimeDuration duration = TimeStamp::Now() - mLastFrameStart;
if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) {
bShouldStartFrame = true;
}
}
}
@ -284,6 +296,24 @@ VRDisplayHost::NotifyVSync()
}
}
void
VRDisplayHost::Run1msTasks(double aDeltaTime)
{
// To override in children
}
void
VRDisplayHost::Run10msTasks()
{
CheckWatchDog();
}
void
VRDisplayHost::Run100msTasks()
{
// to override in children
}
void
VRDisplayHost::SubmitFrameInternal(const layers::SurfaceDescriptor &aTexture,
uint64_t aFrameId,

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

@ -45,6 +45,9 @@ public:
virtual void StartVRNavigation();
virtual void StopVRNavigation(const TimeDuration& aTimeout);
void NotifyVSync();
virtual void Run1msTasks(double aDeltaTime);
virtual void Run10msTasks();
virtual void Run100msTasks();
void StartFrame();
void SubmitFrame(VRLayerParent* aLayer,
@ -97,9 +100,9 @@ private:
uint64_t aFrameId,
const gfx::Rect& aLeftEyeRect,
const gfx::Rect& aRightEyeRect);
void CheckWatchDog();
VRDisplayInfo mLastUpdateDisplayInfo;
TimeStamp mLastFrameStart;
bool mFrameStarted;
#if defined(MOZ_WIDGET_ANDROID)
protected:

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

@ -45,6 +45,21 @@ namespace gfx {
static StaticRefPtr<VRManager> sVRManagerSingleton;
/**
* When VR content is active, we run the tasks at 1ms
* intervals, enabling multiple events to be processed
* per frame, such as haptic feedback pulses.
*/
const uint32_t kVRActiveTaskInterval = 1; // milliseconds
/**
* When VR content is inactive, we run the tasks at 100ms
* intervals, enabling VR display enumeration and
* presentation startup to be relatively responsive
* while not consuming unnecessary resources.
*/
const uint32_t kVRIdleTaskInterval = 100; // milliseconds
/*static*/ void
VRManager::ManagerInit()
{
@ -60,9 +75,11 @@ VRManager::ManagerInit()
VRManager::VRManager()
: mInitialized(false)
, mAccumulator100ms(0.0f)
, mVRDisplaysRequested(false)
, mVRControllersRequested(false)
, mVRServiceStarted(false)
, mTaskInterval(0)
{
MOZ_COUNT_CTOR(VRManager);
MOZ_ASSERT(sVRManagerSingleton == nullptr);
@ -151,6 +168,7 @@ VRManager::~VRManager()
void
VRManager::Destroy()
{
StopTasks();
mVRDisplays.Clear();
mVRControllers.Clear();
for (uint32_t i = 0; i < mManagers.Length(); ++i) {
@ -195,6 +213,7 @@ VRManager::Shutdown()
void
VRManager::Init()
{
StartTasks();
mInitialized = true;
}
@ -252,11 +271,181 @@ void
VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
{
MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
UpdateRequestedDevices();
for (const auto& manager : mManagers) {
manager->NotifyVSync();
}
}
void
VRManager::StartTasks()
{
MOZ_ASSERT(VRListenerThread());
if (!mTaskTimer) {
mTaskInterval = GetOptimalTaskInterval();
mTaskTimer = NS_NewTimer();
mTaskTimer->SetTarget(VRListenerThreadHolder::Loop()->SerialEventTarget());
mTaskTimer->InitWithNamedFuncCallback(
TaskTimerCallback,
this,
mTaskInterval,
nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
"VRManager::TaskTimerCallback");
}
}
void
VRManager::StopTasks()
{
if (mTaskTimer) {
MOZ_ASSERT(VRListenerThread());
mTaskTimer->Cancel();
mTaskTimer = nullptr;
}
}
/*static*/ void
VRManager::StopVRListenerThreadTasks()
{
if (sVRManagerSingleton) {
sVRManagerSingleton->StopTasks();
}
}
/*static*/ void
VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure)
{
/**
* It is safe to use the pointer passed in aClosure to reference the
* VRManager object as the timer is canceled in VRManager::Destroy.
* VRManager::Destroy set mInitialized to false, which is asserted
* in the VRManager destructor, guaranteeing that this functions
* runs if and only if the VRManager object is valid.
*/
VRManager* self = static_cast<VRManager*>(aClosure);
self->RunTasks();
}
void
VRManager::RunTasks()
{
MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
// Will be called once every 1ms when a VR presentation
// is active or once per vsync when a VR presentation is
// not active.
TimeStamp now = TimeStamp::Now();
double lastTickMs = mAccumulator100ms;
double deltaTime = 0.0f;
if (!mLastTickTime.IsNull()) {
deltaTime = (now - mLastTickTime).ToMilliseconds();
}
mAccumulator100ms += deltaTime;
mLastTickTime = now;
if (deltaTime > 0.0f && floor(mAccumulator100ms) != floor(lastTickMs)) {
// Even if more than 1 ms has passed, we will only
// execute Run1msTasks() once.
Run1msTasks(deltaTime);
}
if (floor(mAccumulator100ms * 0.1f) != floor(lastTickMs * 0.1f)) {
// Even if more than 10 ms has passed, we will only
// execute Run10msTasks() once.
Run10msTasks();
}
if (mAccumulator100ms >= 100.0f) {
// Even if more than 100 ms has passed, we will only
// execute Run100msTasks() once.
Run100msTasks();
mAccumulator100ms = fmod(mAccumulator100ms, 100.0f);
}
uint32_t optimalTaskInterval = GetOptimalTaskInterval();
if (mTaskTimer && optimalTaskInterval != mTaskInterval) {
mTaskTimer->SetDelay(optimalTaskInterval);
mTaskInterval = optimalTaskInterval;
}
}
uint32_t
VRManager::GetOptimalTaskInterval()
{
/**
* When either VR content is detected or VR hardware
* has already been activated, we schedule tasks more
* frequently.
*/
bool wantGranularTasks = mVRDisplaysRequested ||
mVRControllersRequested ||
mVRDisplays.Count() ||
mVRControllers.Count();
if (wantGranularTasks) {
return kVRActiveTaskInterval;
}
return kVRIdleTaskInterval;
}
/**
* Run1msTasks() is guaranteed not to be
* called more than once within 1ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 1ms.
*/
void
VRManager::Run1msTasks(double aDeltaTime)
{
MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
for (const auto& manager : mManagers) {
manager->Run1msTasks(aDeltaTime);
}
for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
gfx::VRDisplayHost* display = iter.UserData();
display->Run1msTasks(aDeltaTime);
}
}
/**
* Run10msTasks() is guaranteed not to be
* called more than once within 10ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 10ms.
*/
void
VRManager::Run10msTasks()
{
MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
UpdateRequestedDevices();
for (const auto& manager : mManagers) {
manager->Run10msTasks();
}
for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
gfx::VRDisplayHost* display = iter.UserData();
display->Run10msTasks();
}
}
/**
* Run100msTasks() is guaranteed not to be
* called more than once within 100ms.
* When VR is not active, this will be
* called once per VSync if it wasn't
* called within the last 100ms.
*/
void
VRManager::Run100msTasks()
{
MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
// We must continually refresh the VR display enumeration to check
// for events that we must fire such as Window.onvrdisplayconnect
@ -269,6 +458,15 @@ VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
RefreshVRControllers();
CheckForInactiveTimeout();
for (const auto& manager : mManagers) {
manager->Run100msTasks();
}
for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
gfx::VRDisplayHost* display = iter.UserData();
display->Run100msTasks();
}
}
void
@ -311,7 +509,7 @@ VRManager::NotifyVRVsync(const uint32_t& aDisplayID)
display->StartFrame();
}
RefreshVRDisplays();
DispatchVRDisplayInfoUpdate();
}
void

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

@ -14,6 +14,7 @@
#include "mozilla/TimeStamp.h"
#include "gfxVR.h"
class nsITimer;
namespace mozilla {
namespace layers {
class TextureHost;
@ -62,6 +63,7 @@ public:
void DispatchSubmitFrameResult(uint32_t aDisplayID, const VRSubmitFrameResultInfo& aResult);
void StartVRNavigation(const uint32_t& aDisplayID);
void StopVRNavigation(const uint32_t& aDisplayID, const TimeDuration& aTimeout);
static void StopVRListenerThreadTasks();
protected:
VRManager();
@ -72,6 +74,14 @@ private:
void Init();
void Destroy();
void Shutdown();
void StartTasks();
void StopTasks();
static void TaskTimerCallback(nsITimer* aTimer, void* aClosure);
void RunTasks();
void Run1msTasks(double aDeltaTime);
void Run10msTasks();
void Run100msTasks();
uint32_t GetOptimalTaskInterval();
void DispatchVRDisplayInfoUpdate();
void UpdateRequestedDevices();
@ -95,6 +105,8 @@ private:
TimeStamp mLastControllerEnumerationTime;
TimeStamp mLastDisplayEnumerationTime;
TimeStamp mLastActiveTime;
TimeStamp mLastTickTime;
double mAccumulator100ms;
RefPtr<VRSystemManagerPuppet> mPuppetManager;
RefPtr<VRSystemManagerExternal> mExternalManager;
#if defined(XP_WIN) || defined(XP_MACOSX) || (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
@ -103,6 +115,8 @@ private:
bool mVRDisplaysRequested;
bool mVRControllersRequested;
bool mVRServiceStarted;
uint32_t mTaskInterval;
RefPtr<nsITimer> mTaskTimer;
};
} // namespace gfx

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

@ -6,13 +6,14 @@
#include "VRThread.h"
#include "nsThreadUtils.h"
#include "VRManager.h"
namespace mozilla {
namespace gfx {
static StaticRefPtr<VRListenerThreadHolder> sVRListenerThreadHolder;
static bool sFinishedVRListenerShutDown = false;
static bool sFinishedVRListenerShutDown = true;
static const uint32_t kDefaultThreadLifeTime = 60; // in 60 seconds.
static const uint32_t kDelayPostTaskTime = 20000; // in 20000 ms.
@ -93,7 +94,7 @@ VRListenerThreadHolder::Start()
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
MOZ_ASSERT(!sVRListenerThreadHolder, "The VR listener thread has already been started!");
sFinishedVRListenerShutDown = false;
sVRListenerThreadHolder = new VRListenerThreadHolder();
}
@ -102,7 +103,7 @@ VRListenerThreadHolder::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
MOZ_ASSERT(sVRListenerThreadHolder, "The VR listener thread has already been shut down!");
VRManager::StopVRListenerThreadTasks();
sVRListenerThreadHolder = nullptr;
SpinEventLoopUntil([&]() { return sFinishedVRListenerShutDown; });

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

@ -29,7 +29,7 @@ namespace dom {
#endif // MOZILLA_INTERNAL_API
namespace gfx {
static const int32_t kVRExternalVersion = 2;
static const int32_t kVRExternalVersion = 3;
// We assign VR presentations to groups with a bitmask.
// Currently, we will only display either content or chrome.
@ -48,6 +48,7 @@ static const int kVRControllerMaxCount = 16;
static const int kVRControllerMaxButtons = 64;
static const int kVRControllerMaxAxis = 16;
static const int kVRLayerMaxCount = 8;
static const int kVRHapticsMaxCount = 32;
#if defined(__ANDROID__)
typedef uint64_t VRLayerTextureHandle;
@ -354,6 +355,25 @@ struct VRLayerState
};
};
struct VRHapticState
{
// Reference frame for timing.
// When 0, this does not represent an active haptic pulse.
uint64_t inputFrameID;
// Index within VRSystemState.controllerState identifying the controller
// to emit the haptic pulse
uint32_t controllerIndex;
// 0-based index indicating which haptic actuator within the controller
uint32_t hapticIndex;
// Start time of the haptic feedback pulse, relative to the start of
// inputFrameID, in seconds
float pulseStart;
// Duration of the haptic feedback pulse, in seconds
float pulseDuration;
// Intensity of the haptic feedback pulse, from 0.0f to 1.0f
float pulseIntensity;
};
struct VRBrowserState
{
#if defined(__ANDROID__)
@ -362,6 +382,7 @@ struct VRBrowserState
bool presentationActive;
bool navigationTransitionActive;
VRLayerState layerState[kVRLayerMaxCount];
VRHapticState hapticState[kVRHapticsMaxCount];
};
struct VRSystemState

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

@ -58,6 +58,24 @@ VRSystemManager::NotifyVSync()
}
}
void
VRSystemManager::Run1msTasks(double aDeltaTime)
{
// To be overridden by children
}
void
VRSystemManager::Run10msTasks()
{
// To be overridden by children
}
void
VRSystemManager::Run100msTasks()
{
// To be overridden by children
}
/**
* VRSystemManager::GetHMDs must not be called unless
* VRSystemManager::ShouldInhibitEnumeration is called

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

@ -63,6 +63,7 @@ struct VRDisplayInfo
VRControllerState mControllerState[kVRControllerMaxCount];
VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
TimeStamp mLastFrameStart[kVRMaxLatencyFrames];
const VRHMDSensorState& GetSensorState() const
{
return mLastSensorState[mFrameId % kVRMaxLatencyFrames];
@ -187,6 +188,9 @@ public:
virtual void Shutdown() = 0;
virtual void Enumerate() = 0;
virtual void NotifyVSync();
virtual void Run1msTasks(double aDeltaTime);
virtual void Run10msTasks();
virtual void Run100msTasks();
virtual bool ShouldInhibitEnumeration();
virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
virtual bool GetIsPresenting() = 0;

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

@ -54,6 +54,7 @@ using namespace mozilla::dom;
VRDisplayExternal::VRDisplayExternal(const VRDisplayState& aDisplayState)
: VRDisplayHost(VRDeviceType::External)
, mHapticPulseRemaining{}
, mBrowserState{}
, mLastSensorState{}
{
@ -73,6 +74,7 @@ VRDisplayExternal::~VRDisplayExternal()
void
VRDisplayExternal::Destroy()
{
StopAllHaptics();
StopPresentation();
}
@ -82,16 +84,32 @@ VRDisplayExternal::ZeroSensor()
}
void
VRDisplayExternal::Refresh()
VRDisplayExternal::Run1msTasks(double aDeltaTime)
{
VRDisplayHost::Run1msTasks(aDeltaTime);
UpdateHaptics(aDeltaTime);
}
void
VRDisplayExternal::Run10msTasks()
{
VRDisplayHost::Run10msTasks();
ExpireNavigationTransition();
PullState();
PushState();
// 1ms tasks will always be run before
// the 10ms tasks, so no need to include
// them here as well.
}
void
VRDisplayExternal::ExpireNavigationTransition()
{
if (!mVRNavigationTransitionEnd.IsNull() &&
TimeStamp::Now() > mVRNavigationTransitionEnd) {
mBrowserState.navigationTransitionActive = false;
}
PullState();
PushState();
}
VRHMDSensorState
@ -114,47 +132,10 @@ VRDisplayExternal::StartPresentation()
mBrowserState.layerState[0].type = VRLayerType::LayerType_Stereo_Immersive;
PushState();
#if defined(MOZ_WIDGET_ANDROID)
mLastSubmittedFrameId = 0;
mLastStartedFrame = 0;
/**
* Android compositor is paused when presentation starts. That causes VRManager::NotifyVsync() not to be called.
* We post a VRTask to call VRManager::NotifyVsync() while the compositor is paused on Android.
* VRManager::NotifyVsync() should be called constinuosly while the compositor is paused because Gecko WebVR Architecture
* relies on that to act as a "watchdog" in order to avoid render loop stalls and recover from SubmitFrame call timeouts.
*/
PostVRTask();
#endif
// TODO - Implement telemetry:
mDisplayInfo.mDisplayState.mLastSubmittedFrameId = 0;
// mTelemetry.mLastDroppedFrameCount = stats.m_nNumReprojectedFrames;
}
#if defined(MOZ_WIDGET_ANDROID)
void
VRDisplayExternal::PostVRTask() {
MessageLoop * vrLoop = VRListenerThreadHolder::Loop();
if (!vrLoop || !mBrowserState.presentationActive) {
return;
}
RefPtr<Runnable> task = NewRunnableMethod(
"VRDisplayExternal::RunVRTask",
this,
&VRDisplayExternal::RunVRTask);
VRListenerThreadHolder::Loop()->PostDelayedTask(task.forget(), 50);
}
void
VRDisplayExternal::RunVRTask() {
if (mBrowserState.presentationActive) {
VRManager *vm = VRManager::Get();
vm->NotifyVsync(TimeStamp::Now());
PostVRTask();
}
}
#endif // defined(MOZ_WIDGET_ANDROID)
void
VRDisplayExternal::StopPresentation()
{
@ -305,6 +286,125 @@ VRDisplayExternal::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
return mDisplayInfo.mDisplayState.mLastSubmittedFrameSuccessful;
}
void
VRDisplayExternal::VibrateHaptic(uint32_t aControllerIdx,
uint32_t aHapticIndex,
double aIntensity,
double aDuration,
const VRManagerPromise& aPromise)
{
TimeStamp now = TimeStamp::Now();
size_t bestSlotIndex = 0;
// Default to an empty slot, or the slot holding the oldest haptic pulse
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// Unused slot, use it
bestSlotIndex = i;
break;
}
if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) {
// If no empty slots are available, fall back to overriding
// the pulse which is ending soonest.
bestSlotIndex = i;
}
}
// Override the last pulse on the same actuator if present.
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// This is an empty slot -- no match
continue;
}
if (state.controllerIndex == aControllerIdx &&
state.hapticIndex == aHapticIndex) {
// Found pulse on same actuator -- let's override it.
bestSlotIndex = i;
}
}
ClearHapticSlot(bestSlotIndex);
// Populate the selected slot with new haptic state
size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
bestSlot.inputFrameID =
mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
bestSlot.controllerIndex = aControllerIdx;
bestSlot.hapticIndex = aHapticIndex;
bestSlot.pulseStart =
(now - mDisplayInfo.mLastFrameStart[bufferIndex]).ToSeconds();
bestSlot.pulseDuration = aDuration;
bestSlot.pulseIntensity = aIntensity;
// Convert from seconds to ms
mHapticPulseRemaining[bestSlotIndex] = aDuration * 1000.0f;
MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
if (bestSlotIndex == mHapticPromises.Length()) {
mHapticPromises.AppendElement(
UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
} else {
mHapticPromises[bestSlotIndex] =
UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise));
}
PushState();
}
void
VRDisplayExternal::ClearHapticSlot(size_t aSlot)
{
MOZ_ASSERT(aSlot < mozilla::ArrayLength(mBrowserState.hapticState));
memset(&mBrowserState.hapticState[aSlot], 0, sizeof(VRHapticState));
mHapticPulseRemaining[aSlot] = 0.0f;
if (aSlot < mHapticPromises.Length() && mHapticPromises[aSlot]) {
VRManager* vm = VRManager::Get();
vm->NotifyVibrateHapticCompleted(*mHapticPromises[aSlot]);
mHapticPromises[aSlot] = nullptr;
}
}
void
VRDisplayExternal::UpdateHaptics(double aDeltaTime)
{
bool bNeedPush = false;
// Check for any haptic pulses that have ended and clear them
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
const VRHapticState& state = mBrowserState.hapticState[i];
if (state.inputFrameID == 0) {
// Nothing in this slot
continue;
}
mHapticPulseRemaining[i] -= aDeltaTime;
if (mHapticPulseRemaining[i] <= 0.0f) {
// The pulse has finished
ClearHapticSlot(i);
bNeedPush = true;
}
}
if (bNeedPush) {
PushState();
}
}
void
VRDisplayExternal::StopVibrateHaptic(uint32_t aControllerIdx)
{
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
VRHapticState& state = mBrowserState.hapticState[i];
if (state.controllerIndex == aControllerIdx) {
memset(&state, 0, sizeof(VRHapticState));
}
}
PushState();
}
void
VRDisplayExternal::StopAllHaptics()
{
for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
ClearHapticSlot(i);
}
PushState();
}
void
VRDisplayExternal::PushState(bool aNotifyCond)
{
@ -543,15 +643,14 @@ VRSystemManagerExternal::Shutdown()
}
void
VRSystemManagerExternal::NotifyVSync()
VRSystemManagerExternal::Run100msTasks()
{
VRSystemManager::NotifyVSync();
VRSystemManager::Run100msTasks();
// 1ms and 10ms tasks will always be run before
// the 100ms tasks, so no need to run them
// redundantly here.
CheckForShutdown();
if (mDisplay) {
mDisplay->Refresh();
}
}
void
@ -623,54 +722,73 @@ VRSystemManagerExternal::GetIsPresenting()
void
VRSystemManagerExternal::VibrateHaptic(uint32_t aControllerIdx,
uint32_t aHapticIndex,
double aIntensity,
double aDuration,
const VRManagerPromise& aPromise)
uint32_t aHapticIndex,
double aIntensity,
double aDuration,
const VRManagerPromise& aPromise)
{
// TODO - Implement this
if (mDisplay) {
// VRDisplayClient::FireGamepadEvents() assigns a controller ID with ranges
// based on displayID. We must translate this to the indexes understood by
// VRDisplayExternal.
uint32_t controllerBaseIndex =
kVRControllerMaxCount * mDisplay->GetDisplayInfo().mDisplayID;
uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
double aDurationSeconds = aDuration * 0.001f;
mDisplay->VibrateHaptic(
controllerIndex, aHapticIndex, aIntensity, aDurationSeconds, aPromise);
}
}
void
VRSystemManagerExternal::StopVibrateHaptic(uint32_t aControllerIdx)
{
// TODO - Implement this
if (mDisplay) {
// VRDisplayClient::FireGamepadEvents() assigns a controller ID with ranges
// based on displayID. We must translate this to the indexes understood by
// VRDisplayExternal.
uint32_t controllerBaseIndex =
kVRControllerMaxCount * mDisplay->GetDisplayInfo().mDisplayID;
uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
mDisplay->StopVibrateHaptic(controllerIndex);
}
}
void
VRSystemManagerExternal::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
VRSystemManagerExternal::GetControllers(
nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
{
// Controller updates are handled in VRDisplayClient for VRSystemManagerExternal
// Controller updates are handled in VRDisplayClient for
// VRSystemManagerExternal
aControllerResult.Clear();
}
void
VRSystemManagerExternal::ScanForControllers()
{
// Controller updates are handled in VRDisplayClient for VRSystemManagerExternal
if (mDisplay) {
mDisplay->Refresh();
}
// Controller updates are handled in VRDisplayClient for
// VRSystemManagerExternal
return;
}
void
VRSystemManagerExternal::HandleInput()
{
// Controller updates are handled in VRDisplayClient for VRSystemManagerExternal
if (mDisplay) {
mDisplay->Refresh();
}
// Controller updates are handled in VRDisplayClient for
// VRSystemManagerExternal
return;
}
void
VRSystemManagerExternal::RemoveControllers()
{
// Controller updates are handled in VRDisplayClient for VRSystemManagerExternal
if (mDisplay) {
mDisplay->StopAllHaptics();
}
// Controller updates are handled in VRDisplayClient for
// VRSystemManagerExternal
}
#if defined(MOZ_WIDGET_ANDROID)
bool
VRSystemManagerExternal::PullState(VRDisplayState* aDisplayState,

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

@ -14,6 +14,7 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/EnumeratedArray.h"
#include "mozilla/UniquePtr.h"
#include "gfxVR.h"
#include "VRDisplayHost.h"
@ -23,8 +24,6 @@ class MacIOSurface;
#endif
namespace mozilla {
namespace gfx {
class VRThread;
namespace impl {
class VRDisplayExternal : public VRDisplayHost
@ -48,6 +47,15 @@ public:
explicit VRDisplayExternal(const VRDisplayState& aDisplayState);
void Refresh();
const VRControllerState& GetLastControllerState(uint32_t aStateIndex) const;
void VibrateHaptic(uint32_t aControllerIdx,
uint32_t aHapticIndex,
double aIntensity,
double aDuration,
const VRManagerPromise& aPromise);
void StopVibrateHaptic(uint32_t aControllerIdx);
void StopAllHaptics();
void Run1msTasks(double aDeltaTime) override;
void Run10msTasks() override;
protected:
virtual ~VRDisplayExternal();
void Destroy();
@ -59,12 +67,15 @@ private:
void PushState(bool aNotifyCond = false);
#if defined(MOZ_WIDGET_ANDROID)
bool PullState(const std::function<bool()>& aWaitCondition = nullptr);
void PostVRTask();
void RunVRTask();
#else
bool PullState();
#endif
void ClearHapticSlot(size_t aSlot);
void ExpireNavigationTransition();
void UpdateHaptics(double aDeltaTime);
nsTArray<UniquePtr<VRManagerPromise>> mHapticPromises;
// Duration of haptic pulse time remaining (milliseconds)
double mHapticPulseRemaining[kVRHapticsMaxCount];
VRTelemetry mTelemetry;
TimeStamp mVRNavigationTransitionEnd;
VRBrowserState mBrowserState;
@ -80,7 +91,7 @@ public:
virtual void Destroy() override;
virtual void Shutdown() override;
virtual void NotifyVSync() override;
virtual void Run100msTasks() override;
virtual void Enumerate() override;
virtual bool ShouldInhibitEnumeration() override;
virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;

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

@ -711,6 +711,21 @@ VRSystemManagerPuppet::Shutdown()
mPuppetHMDs.Clear();
}
void
VRSystemManagerPuppet::Run10msTasks()
{
VRSystemManager::Run10msTasks();
/**
* When running headless mochitests on some of our automated test
* infrastructure, 2d display vsyncs are not always generated.
* To workaround, we produce a vsync manually.
*/
VRManager *vm = VRManager::Get();
MOZ_ASSERT(vm);
vm->NotifyVsync(TimeStamp::Now());
}
void
VRSystemManagerPuppet::NotifyVSync()
{

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

@ -139,6 +139,7 @@ public:
const VRManagerPromise& aPromise) override;
virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
virtual void NotifyVSync() override;
virtual void Run10msTasks() override;
protected:
VRSystemManagerPuppet();

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

@ -133,6 +133,7 @@ VRManagerParent::CreateForGPUProcess(Endpoint<PVRManagerParent>&& aEndpoint)
RefPtr<VRManagerParent> vmp = new VRManagerParent(aEndpoint.OtherPid(), false);
vmp->mVRListenerThreadHolder = VRListenerThreadHolder::GetSingleton();
vmp->mSelfRef = vmp;
loop->PostTask(NewRunnableMethod<Endpoint<PVRManagerParent>&&>(
"gfx::VRManagerParent::Bind",
vmp,
@ -226,7 +227,6 @@ mozilla::ipc::IPCResult
VRManagerParent::RecvControllerListenerAdded()
{
// Force update the available controllers for GamepadManager,
// remove the existing controllers and sync them by NotifyVsync().
VRManager* vm = VRManager::Get();
vm->RemoveControllers();
mHaveControllerListener = true;
@ -276,25 +276,47 @@ VRManagerParent::RecvCreateVRServiceTestController(const nsCString& aID, const u
VRManager* vm = VRManager::Get();
/**
* When running headless mochitests on some of our automated test
* infrastructure, 2d display vsyncs are not always generated.
* In this case, the test controllers can't be created immediately
* after the VR display was created as the state of the VR displays
* are updated during vsync.
* To workaround, we produce a vsync manually.
* The controller is created asynchronously in the VRListener thread.
* We will wait up to kMaxControllerCreationTime milliseconds before
* assuming that the controller will never be created.
*/
const int kMaxControllerCreationTime = 1000;
/**
* min(100ms, kVRIdleTaskInterval) * 10 as a very
* pessimistic estimation of the maximum duration possible.
* It's possible that the IPC message queues could be so busy
* that this time is elapsed while still succeeding to create
* the controllers; however, in this case the browser would be
* locking up for more than a second at a time and something else
* has gone horribly wrong.
*/
const int kTestInterval = 10;
/**
* We will keep checking every kTestInterval milliseconds until
* we see the controllers or kMaxControllerCreationTime milliseconds
* have elapsed and we will give up.
*/
vm->NotifyVsync(TimeStamp::Now());
// Get VRControllerPuppet from VRManager
vm->GetVRControllerInfo(controllerInfoArray);
for (auto& controllerInfo : controllerInfoArray) {
if (controllerInfo.GetType() == VRDeviceType::Puppet) {
if (controllerIdx == mControllerTestID) {
controllerPuppet = static_cast<impl::VRControllerPuppet*>(
vm->GetController(controllerInfo.GetControllerID()).get());
break;
int testDuration = 0;
while (!controllerPuppet && testDuration < kMaxControllerCreationTime) {
testDuration += kTestInterval;
#ifdef XP_WIN
Sleep(kTestInterval);
#else
sleep(kTestInterval);
#endif
// Get VRControllerPuppet from VRManager
vm->GetVRControllerInfo(controllerInfoArray);
for (auto& controllerInfo : controllerInfoArray) {
if (controllerInfo.GetType() == VRDeviceType::Puppet) {
if (controllerIdx == mControllerTestID) {
controllerPuppet = static_cast<impl::VRControllerPuppet*>(
vm->GetController(controllerInfo.GetControllerID()).get());
break;
}
++controllerIdx;
}
++controllerIdx;
}
}

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

@ -111,6 +111,10 @@ public:
~VRManagerPromise() {
mParent = nullptr;
}
bool operator==(const VRManagerPromise& aOther) const
{
return mParent == aOther.mParent && mPromiseID == aOther.mPromiseID;
}
private:
RefPtr<VRManagerParent> mParent;

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

@ -115,6 +115,9 @@ struct ParamTraits<mozilla::gfx::VRDisplayInfo>
for (size_t i = 0; i < mozilla::ArrayLength(aParam.mLastSensorState); i++) {
WriteParam(aMsg, aParam.mLastSensorState[i]);
}
for (size_t i = 0; i < mozilla::ArrayLength(aParam.mLastFrameStart); i++) {
WriteParam(aMsg, aParam.mLastFrameStart[i]);
}
for (size_t i = 0; i < mozilla::ArrayLength(aParam.mControllerState); i++) {
WriteParam(aMsg, aParam.mControllerState[i]);
}
@ -135,6 +138,11 @@ struct ParamTraits<mozilla::gfx::VRDisplayInfo>
return false;
}
}
for (size_t i = 0; i < mozilla::ArrayLength(aResult->mLastFrameStart); i++) {
if (!ReadParam(aMsg, aIter, &(aResult->mLastFrameStart[i]))) {
return false;
}
}
for (size_t i = 0; i < mozilla::ArrayLength(aResult->mControllerState); i++) {
if (!ReadParam(aMsg, aIter, &(aResult->mControllerState[i]))) {
return false;

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

@ -8,6 +8,7 @@
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/dom/GamepadBinding.h"
#include "VRThread.h"
#if !defined(M_PI)
#define M_PI 3.14159265358979323846264338327950288
@ -16,7 +17,12 @@
#define BTN_MASK_FROM_ID(_id) \
::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
static const uint32_t kNumOpenVRHaptcs = 1;
// Haptic feedback is updated every 5ms, as this is
// the minimum period between new haptic pulse requests.
// Effectively, this results in a pulse width modulation
// with an interval of 5ms. Through experimentation, the
// maximum duty cycle was found to be about 3.9ms
const uint32_t kVRHapticUpdateInterval = 5;
using namespace mozilla::gfx;
@ -98,9 +104,12 @@ OpenVRSession::OpenVRSession()
, mVRSystem(nullptr)
, mVRChaperone(nullptr)
, mVRCompositor(nullptr)
, mControllerDeviceIndex{0}
, mControllerDeviceIndex{}
, mHapticPulseRemaining{}
, mHapticPulseIntensity{}
, mShouldQuit(false)
, mIsWindowsMR(false)
, mControllerHapticStateMutex("OpenVRSession::mControllerHapticStateMutex")
{
}
@ -162,6 +171,9 @@ OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState)
return false;
}
StartHapticThread();
StartHapticTimer();
// Succeeded
return true;
}
@ -184,6 +196,8 @@ OpenVRSession::CreateD3DObjects()
void
OpenVRSession::Shutdown()
{
StopHapticTimer();
StopHapticThread();
if (mVRSystem || mVRCompositor || mVRSystem) {
::vr::VR_Shutdown();
mVRCompositor = nullptr;
@ -380,6 +394,8 @@ OpenVRSession::EnumerateControllers(VRSystemState& aState)
{
MOZ_ASSERT(mVRSystem);
MutexAutoLock lock(mControllerHapticStateMutex);
bool controllerPresent[kVRControllerMaxCount] = { false };
// Basically, we would have HMDs in the tracked devices,
@ -492,7 +508,7 @@ OpenVRSession::EnumerateControllers(VRSystemState& aState)
strncpy(controllerState.controllerName, deviceId.BeginReading(), kVRControllerNameMaxLen);
controllerState.numButtons = numButtons;
controllerState.numAxes = numAxes;
controllerState.numHaptics = kNumOpenVRHaptcs;
controllerState.numHaptics = kNumOpenVRHaptics;
// If the Windows MR controller doesn't has the amount
// of buttons or axes as our expectation, switching off
@ -760,7 +776,6 @@ OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState)
EnumerateControllers(aSystemState);
UpdateControllerButtons(aSystemState);
UpdateControllerPoses(aSystemState);
aSystemState.sensorState.inputFrameID++;
}
bool
@ -930,5 +945,153 @@ OpenVRSession::StartPresentation()
return true;
}
void
OpenVRSession::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
float aIntensity, float aDuration)
{
MutexAutoLock lock(mControllerHapticStateMutex);
if (aHapticIndex >= kNumOpenVRHaptics ||
aControllerIdx >= kVRControllerMaxCount) {
return;
}
::vr::TrackedDeviceIndex_t deviceIndex = mControllerDeviceIndex[aControllerIdx];
if (deviceIndex == 0) {
return;
}
mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
/**
* TODO - The haptic feedback pulses will have latency of one frame and we
* are simulating intensity with pulse-width modulation.
* We should use of the OpenVR Input API to correct this
* and replace the TriggerHapticPulse calls which have been
* deprecated.
*/
}
void
OpenVRSession::StartHapticThread()
{
if (!mHapticThread) {
mHapticThread = new VRThread(NS_LITERAL_CSTRING("VR_OpenVR_Haptics"));
}
mHapticThread->Start();
}
void
OpenVRSession::StopHapticThread()
{
if (mHapticThread) {
mHapticThread->Shutdown();
mHapticThread = nullptr;
}
}
void
OpenVRSession::StartHapticTimer()
{
if (!mHapticTimer && mHapticThread) {
mLastHapticUpdate = TimeStamp();
mHapticTimer = NS_NewTimer();
mHapticTimer->SetTarget(mHapticThread->GetThread()->EventTarget());
mHapticTimer->InitWithNamedFuncCallback(
HapticTimerCallback,
this,
kVRHapticUpdateInterval,
nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
"OpenVRSession::HapticTimerCallback");
}
}
void
OpenVRSession::StopHapticTimer()
{
if (mHapticTimer) {
mHapticTimer->Cancel();
mHapticTimer = nullptr;
}
}
/*static*/ void
OpenVRSession::HapticTimerCallback(nsITimer* aTimer, void* aClosure)
{
/**
* It is safe to use the pointer passed in aClosure to reference the
* OpenVRSession object as the timer is canceled in OpenVRSession::Shutdown,
* which is called by the OpenVRSession destructor, guaranteeing
* that this function runs if and only if the VRManager object is valid.
*/
OpenVRSession* self = static_cast<OpenVRSession*>(aClosure);
self->UpdateHaptics();
}
void
OpenVRSession::UpdateHaptics()
{
MOZ_ASSERT(mHapticThread->GetThread() == NS_GetCurrentThread());
MOZ_ASSERT(mVRSystem);
MutexAutoLock lock(mControllerHapticStateMutex);
TimeStamp now = TimeStamp::Now();
if (mLastHapticUpdate.IsNull()) {
mLastHapticUpdate = now;
return;
}
float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds();
mLastHapticUpdate = now;
for (int iController = 0; iController < kVRControllerMaxCount; iController++) {
for (int iHaptic = 0; iHaptic < kNumOpenVRHaptics; iHaptic++) {
::vr::TrackedDeviceIndex_t deviceIndex = mControllerDeviceIndex[iController];
if (deviceIndex == 0) {
continue;
}
float intensity = mHapticPulseIntensity[iController][iHaptic];
float duration = mHapticPulseRemaining[iController][iHaptic];
if (duration <= 0.0f || intensity <= 0.0f) {
continue;
}
// We expect OpenVR to vibrate for 5 ms, but we found it only response the
// commend ~ 3.9 ms. For duration time longer than 3.9 ms, we separate them
// to a loop of 3.9 ms for make users feel that is a continuous events.
const float microSec = (duration < 0.0039f ? duration : 0.0039f) * 1000000.0f * intensity;
mVRSystem->TriggerHapticPulse(deviceIndex, iHaptic, (uint32_t)microSec);
duration -= deltaTime;
if (duration < 0.0f) {
duration = 0.0f;
}
mHapticPulseRemaining[iController][iHaptic] = duration;
}
}
}
void
OpenVRSession::StopVibrateHaptic(uint32_t aControllerIdx)
{
MutexAutoLock lock(mControllerHapticStateMutex);
if (aControllerIdx >= kVRControllerMaxCount) {
return;
}
for (int iHaptic = 0; iHaptic < kNumOpenVRHaptics; iHaptic++) {
mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
}
}
void
OpenVRSession::StopAllHaptics()
{
MutexAutoLock lock(mControllerHapticStateMutex);
for (auto& controller : mHapticPulseRemaining) {
for (auto& haptic : controller) {
haptic = 0.0f;
}
}
}
} // namespace mozilla
} // namespace gfx

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

@ -11,6 +11,7 @@
#include "openvr.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/TimeStamp.h"
#include "moz_external_vr.h"
#if defined(XP_WIN)
@ -18,9 +19,13 @@
#elif defined(XP_MACOSX)
class MacIOSurface;
#endif
class nsITimer;
namespace mozilla {
namespace gfx {
class VRThread;
static const int kNumOpenVRHaptics = 1;
class OpenVRSession : public VRSession
{
@ -36,6 +41,10 @@ public:
bool StartPresentation() override;
void StopPresentation() override;
bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) override;
void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
float aIntensity, float aDuration) override;
void StopVibrateHaptic(uint32_t aControllerIdx) override;
void StopAllHaptics() override;
private:
// OpenVR State
@ -43,8 +52,11 @@ private:
::vr::IVRChaperone* mVRChaperone = nullptr;
::vr::IVRCompositor* mVRCompositor = nullptr;
::vr::TrackedDeviceIndex_t mControllerDeviceIndex[kVRControllerMaxCount];
float mHapticPulseRemaining[kVRControllerMaxCount][kNumOpenVRHaptics];
float mHapticPulseIntensity[kVRControllerMaxCount][kNumOpenVRHaptics];
bool mShouldQuit;
bool mIsWindowsMR;
TimeStamp mLastHapticUpdate;
bool InitState(mozilla::gfx::VRSystemState& aSystemState);
void UpdateStageParameters(mozilla::gfx::VRDisplayState& aState);
@ -64,6 +76,15 @@ private:
void GetControllerDeviceId(::vr::ETrackedDeviceClass aDeviceType,
::vr::TrackedDeviceIndex_t aDeviceIndex,
nsCString& aId);
void UpdateHaptics();
void StartHapticThread();
void StopHapticThread();
void StartHapticTimer();
void StopHapticTimer();
static void HapticTimerCallback(nsITimer* aTimer, void* aClosure);
RefPtr<nsITimer> mHapticTimer;
RefPtr<VRThread> mHapticThread;
mozilla::Mutex mControllerHapticStateMutex;
};
} // namespace mozilla

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

@ -8,6 +8,7 @@
#include "OpenVRSession.h"
#include "gfxPrefs.h"
#include "base/thread.h" // for Thread
#include <cstring> // for memcmp
using namespace mozilla;
using namespace mozilla::gfx;
@ -57,10 +58,13 @@ VRService::Create()
VRService::VRService()
: mSystemState{}
, mBrowserState{}
, mBrowserGeneration(0)
, mServiceThread(nullptr)
, mShutdownRequested(false)
, mAPIShmem(nullptr)
, mTargetShmemFile(0)
, mLastHapticState{}
, mFrameStartTime{}
{
// When we have the VR process, we map the memory
// of mAPIShmem from GPU process.
@ -290,6 +294,7 @@ VRService::ServiceImmersiveMode()
MOZ_ASSERT(mSession);
mSession->ProcessEvents(mSystemState);
UpdateHaptics();
PushState(mSystemState);
PullState(mBrowserState);
@ -302,6 +307,7 @@ VRService::ServiceImmersiveMode()
return;
} else if (!IsImmersiveContentActive(mBrowserState)) {
// Exit immersive mode
mSession->StopAllHaptics();
mSession->StopPresentation();
MessageLoop::current()->PostTask(NewRunnableMethod(
"gfx::VRService::ServiceWaitForImmersive",
@ -318,6 +324,8 @@ VRService::ServiceImmersiveMode()
for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
const VRLayerState& layer = mBrowserState.layerState[iLayer];
if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
// SubmitFrame may block in order to control the timing for
// the next frame start
success = mSession->SubmitFrame(layer.layer_stereo_immersive);
break;
}
@ -329,7 +337,12 @@ VRService::ServiceImmersiveMode()
// atomically to the browser.
mSystemState.displayState.mLastSubmittedFrameId = newFrameId;
mSystemState.displayState.mLastSubmittedFrameSuccessful = success;
// StartFrame may block to control the timing for the next frame start
mSession->StartFrame(mSystemState);
mSystemState.sensorState.inputFrameID++;
size_t historyIndex = mSystemState.sensorState.inputFrameID % ArrayLength(mFrameStartTime);
mFrameStartTime[historyIndex] = TimeStamp::Now();
PushState(mSystemState);
}
@ -340,6 +353,46 @@ VRService::ServiceImmersiveMode()
));
}
void
VRService::UpdateHaptics()
{
MOZ_ASSERT(IsInServiceThread());
MOZ_ASSERT(mSession);
for (size_t i = 0; i < ArrayLength(mBrowserState.hapticState); i++) {
VRHapticState& state = mBrowserState.hapticState[i];
VRHapticState& lastState = mLastHapticState[i];
// Note that VRHapticState is asserted to be a POD type, thus memcmp is safe
if (memcmp(&state, &lastState, sizeof(VRHapticState)) == 0) {
// No change since the last update
continue;
}
if (state.inputFrameID == 0) {
// The haptic feedback was stopped
mSession->StopVibrateHaptic(state.controllerIndex);
} else {
TimeStamp now;
if (now.IsNull()) {
// TimeStamp::Now() is expensive, so we
// must call it only when needed and save the
// output for further loop iterations.
now = TimeStamp::Now();
}
// This is a new haptic pulse, or we are overriding a prior one
size_t historyIndex = state.inputFrameID % ArrayLength(mFrameStartTime);
float startOffset = (float)(now - mFrameStartTime[historyIndex]).ToSeconds();
// state.pulseStart is guaranteed never to be in the future
mSession->VibrateHaptic(state.controllerIndex,
state.hapticIndex,
state.pulseIntensity,
state.pulseDuration + state.pulseStart - startOffset);
}
// Record the state for comparison in the next run
memcpy(&lastState, &state, sizeof(VRHapticState));
}
}
void
VRService::PushState(const mozilla::gfx::VRSystemState& aState)
{
@ -385,9 +438,12 @@ VRService::PullState(mozilla::gfx::VRBrowserState& aState)
}
#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));
if (mAPIShmem->browserGenerationA != mBrowserGeneration) {
memcpy(&tmp, mAPIShmem, sizeof(VRExternalShmem));
if (tmp.browserGenerationA == tmp.browserGenerationB && tmp.browserGenerationA != 0 && tmp.browserGenerationA != -1) {
memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
mBrowserGeneration = tmp.browserGenerationA;
}
}
#endif
}

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

@ -19,6 +19,8 @@ namespace gfx {
class VRSession;
static const int kVRFrameTimingHistoryDepth = 100;
class VRService
{
public:
@ -51,6 +53,7 @@ private:
* mBrowserState is memcpy'ed from the Shmem atomically
*/
VRBrowserState mBrowserState;
int64_t mBrowserGeneration;
UniquePtr<VRSession> mSession;
base::Thread* mServiceThread;
@ -58,8 +61,11 @@ private:
VRExternalShmem* MOZ_OWNING_REF mAPIShmem;
base::ProcessHandle mTargetShmemFile;
VRHapticState mLastHapticState[kVRHapticsMaxCount];
TimeStamp mFrameStartTime[kVRFrameTimingHistoryDepth];
bool IsInServiceThread();
void UpdateHaptics();
/**
* The VR Service thread is a state machine that always has one

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

@ -34,6 +34,10 @@ public:
virtual bool StartPresentation() = 0;
virtual void StopPresentation() = 0;
virtual bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) = 0;
virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
float aIntensity, float aDuration) = 0;
virtual void StopVibrateHaptic(uint32_t aControllerIdx) = 0;
virtual void StopAllHaptics() = 0;
#if defined(XP_WIN)
protected: