зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
2611229c08
Коммит
f65dbeca74
|
@ -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,22 +277,17 @@ 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.
|
||||
bShouldStartFrame = true;
|
||||
} else {
|
||||
// If content fails to call VRDisplay.submitFrame, we must eventually
|
||||
// time-out and trigger a new frame.
|
||||
if (mLastFrameStart.IsNull()) {
|
||||
TimeStamp lastFrameStart = mDisplayInfo.mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
|
||||
if (lastFrameStart.IsNull()) {
|
||||
bShouldStartFrame = true;
|
||||
} else {
|
||||
TimeDuration duration = TimeStamp::Now() - mLastFrameStart;
|
||||
TimeDuration duration = TimeStamp::Now() - lastFrameStart;
|
||||
if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) {
|
||||
bShouldStartFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldStartFrame) {
|
||||
VRManager *vm = VRManager::Get();
|
||||
|
@ -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
|
||||
|
@ -628,48 +727,67 @@ VRSystemManagerExternal::VibrateHaptic(uint32_t aControllerIdx,
|
|||
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
|
||||
|
|
|
@ -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,14 +276,35 @@ 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.
|
||||
*/
|
||||
vm->NotifyVsync(TimeStamp::Now());
|
||||
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.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
@ -297,6 +318,7 @@ VRManagerParent::RecvCreateVRServiceTestController(const nsCString& aID, const u
|
|||
++controllerIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We might not have a controllerPuppet if the test did
|
||||
// not create a VR display first.
|
||||
|
|
|
@ -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;
|
||||
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:
|
||||
|
|
Загрузка…
Ссылка в новой задаче