diff options
Diffstat (limited to '')
-rw-r--r-- | dom/vr/VRDisplay.cpp | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/dom/vr/VRDisplay.cpp b/dom/vr/VRDisplay.cpp new file mode 100644 index 0000000000..47e02890af --- /dev/null +++ b/dom/vr/VRDisplay.cpp @@ -0,0 +1,808 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWrapperCache.h" + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/VRDisplay.h" +#include "mozilla/dom/VRDisplayBinding.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/Base64.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "Navigator.h" +#include "gfxUtils.h" +#include "gfxVR.h" +#include "VRDisplayClient.h" +#include "VRManagerChild.h" +#include "VRDisplayPresentation.h" +#include "nsIObserverService.h" +#include "nsIFrame.h" +#include "nsISupportsPrimitives.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace dom { + +VRFieldOfView::VRFieldOfView(nsISupports* aParent, double aUpDegrees, + double aRightDegrees, double aDownDegrees, + double aLeftDegrees) + : mParent(aParent), + mUpDegrees(aUpDegrees), + mRightDegrees(aRightDegrees), + mDownDegrees(aDownDegrees), + mLeftDegrees(aLeftDegrees) {} + +VRFieldOfView::VRFieldOfView(nsISupports* aParent, + const gfx::VRFieldOfView& aSrc) + : mParent(aParent), + mUpDegrees(aSrc.upDegrees), + mRightDegrees(aSrc.rightDegrees), + mDownDegrees(aSrc.downDegrees), + mLeftDegrees(aSrc.leftDegrees) {} + +bool VRDisplayCapabilities::HasPosition() const { + return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Position) || + bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated); +} + +bool VRDisplayCapabilities::HasOrientation() const { + return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Orientation); +} + +bool VRDisplayCapabilities::HasExternalDisplay() const { + return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_External); +} + +bool VRDisplayCapabilities::CanPresent() const { + return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Present); +} + +uint32_t VRDisplayCapabilities::MaxLayers() const { + return CanPresent() ? 1 : 0; +} + +void VRDisplay::UpdateDisplayClient( + already_AddRefed<gfx::VRDisplayClient> aClient) { + mClient = std::move(aClient); +} + +/*static*/ +bool VRDisplay::RefreshVRDisplays(uint64_t aWindowId) { + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + return vm && vm->RefreshVRDisplaysWithCallback(aWindowId); +} + +/*static*/ +void VRDisplay::UpdateVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays, + nsPIDOMWindowInner* aWindow) { + nsTArray<RefPtr<VRDisplay>> displays; + + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + nsTArray<RefPtr<gfx::VRDisplayClient>> updatedDisplays; + if (vm) { + vm->GetVRDisplays(updatedDisplays); + for (size_t i = 0; i < updatedDisplays.Length(); i++) { + RefPtr<gfx::VRDisplayClient> display = updatedDisplays[i]; + bool isNewDisplay = true; + for (size_t j = 0; j < aDisplays.Length(); j++) { + if (aDisplays[j]->GetClient()->GetDisplayInfo().GetDisplayID() == + display->GetDisplayInfo().GetDisplayID()) { + displays.AppendElement(aDisplays[j]); + isNewDisplay = false; + } else { + RefPtr<gfx::VRDisplayClient> ref = display; + aDisplays[j]->UpdateDisplayClient(do_AddRef(display)); + displays.AppendElement(aDisplays[j]); + isNewDisplay = false; + } + } + + if (isNewDisplay) { + displays.AppendElement(new VRDisplay(aWindow, display)); + } + } + } + + aDisplays = std::move(displays); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRFieldOfView, mParent) +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFieldOfView, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFieldOfView, Release) + +JSObject* VRFieldOfView::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRFieldOfView_Binding::Wrap(aCx, this, aGivenProto); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(VREyeParameters) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VREyeParameters) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mFOV) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->mOffset = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VREyeParameters) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mFOV) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VREyeParameters) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOffset) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VREyeParameters, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VREyeParameters, Release) + +VREyeParameters::VREyeParameters(nsISupports* aParent, + const gfx::Point3D& aEyeTranslation, + const gfx::VRFieldOfView& aFOV, + const gfx::IntSize& aRenderSize) + : mParent(aParent), + mEyeTranslation(aEyeTranslation), + mRenderSize(aRenderSize) { + mFOV = new VRFieldOfView(aParent, aFOV); + mozilla::HoldJSObjects(this); +} + +VREyeParameters::~VREyeParameters() { mozilla::DropJSObjects(this); } + +VRFieldOfView* VREyeParameters::FieldOfView() { return mFOV; } + +void VREyeParameters::GetOffset(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + if (!mOffset) { + // Lazily create the Float32Array + mOffset = + dom::Float32Array::Create(aCx, this, 3, mEyeTranslation.components); + if (!mOffset) { + aRv.NoteJSContextException(aCx); + return; + } + } + aRetval.set(mOffset); +} + +JSObject* VREyeParameters::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VREyeParameters_Binding::Wrap(aCx, this, aGivenProto); +} + +VRStageParameters::VRStageParameters( + nsISupports* aParent, const gfx::Matrix4x4& aSittingToStandingTransform, + const gfx::Size& aSize) + : mParent(aParent), + mSittingToStandingTransform(aSittingToStandingTransform), + mSittingToStandingTransformArray(nullptr), + mSize(aSize) { + mozilla::HoldJSObjects(this); +} + +VRStageParameters::~VRStageParameters() { mozilla::DropJSObjects(this); } + +JSObject* VRStageParameters::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRStageParameters_Binding::Wrap(aCx, this, aGivenProto); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(VRStageParameters) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRStageParameters) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->mSittingToStandingTransformArray = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRStageParameters) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRStageParameters) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK( + mSittingToStandingTransformArray) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRStageParameters, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRStageParameters, Release) + +void VRStageParameters::GetSittingToStandingTransform( + JSContext* aCx, JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) { + if (!mSittingToStandingTransformArray) { + // Lazily create the Float32Array + mSittingToStandingTransformArray = dom::Float32Array::Create( + aCx, this, 16, mSittingToStandingTransform.components); + if (!mSittingToStandingTransformArray) { + aRv.NoteJSContextException(aCx); + return; + } + } + aRetval.set(mSittingToStandingTransformArray); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRDisplayCapabilities, mParent) +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRDisplayCapabilities, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRDisplayCapabilities, Release) + +JSObject* VRDisplayCapabilities::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRDisplayCapabilities_Binding::Wrap(aCx, this, aGivenProto); +} + +VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState) + : Pose(aParent), mVRState(aState) { + mozilla::HoldJSObjects(this); +} + +VRPose::VRPose(nsISupports* aParent) : Pose(aParent) { + mVRState.inputFrameID = 0; + mVRState.timestamp = 0.0; + mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None; + mozilla::HoldJSObjects(this); +} + +VRPose::~VRPose() { mozilla::DropJSObjects(this); } + +void VRPose::GetPosition(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = + bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) || + bool(mVRState.flags & + gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated); + SetFloat32Array(aCx, this, aRetval, mPosition, + valid ? mVRState.pose.position : nullptr, 3, aRv); +} + +void VRPose::GetLinearVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = + bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) || + bool(mVRState.flags & + gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated); + SetFloat32Array(aCx, this, aRetval, mLinearVelocity, + valid ? mVRState.pose.linearVelocity : nullptr, 3, aRv); +} + +void VRPose::GetLinearAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = bool( + mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration); + SetFloat32Array(aCx, this, aRetval, mLinearAcceleration, + valid ? mVRState.pose.linearAcceleration : nullptr, 3, aRv); +} + +void VRPose::GetOrientation(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = + bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation); + SetFloat32Array(aCx, this, aRetval, mOrientation, + valid ? mVRState.pose.orientation : nullptr, 4, aRv); +} + +void VRPose::GetAngularVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = + bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation); + SetFloat32Array(aCx, this, aRetval, mAngularVelocity, + valid ? mVRState.pose.angularVelocity : nullptr, 3, aRv); +} + +void VRPose::GetAngularAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + const bool valid = bool( + mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration); + SetFloat32Array(aCx, this, aRetval, mAngularAcceleration, + valid ? mVRState.pose.angularAcceleration : nullptr, 3, aRv); +} + +void VRPose::Update(const gfx::VRHMDSensorState& aState) { mVRState = aState; } + +JSObject* VRPose::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRPose_Binding::Wrap(aCx, this, aGivenProto); +} + +/* virtual */ +JSObject* VRDisplay::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRDisplay_Binding::Wrap(aCx, this, aGivenProto); +} + +VRDisplay::VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient) + : DOMEventTargetHelper(aWindow), + mClient(aClient), + mDepthNear(0.01f) // Default value from WebVR Spec + , + mDepthFar(10000.0f) // Default value from WebVR Spec + , + mVRNavigationEventDepth(0), + mShutdown(false) { + const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo(); + mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities()); + if (info.GetCapabilities() & + gfx::VRDisplayCapabilityFlags::Cap_StageParameters) { + mStageParameters = new VRStageParameters( + aWindow, info.GetSittingToStandingTransform(), info.GetStageSize()); + } + mozilla::HoldJSObjects(this); + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (MOZ_LIKELY(obs)) { + obs->AddObserver(this, "inner-window-destroyed", false); + } +} + +VRDisplay::~VRDisplay() { + MOZ_ASSERT(mShutdown); + mozilla::DropJSObjects(this); +} + +void VRDisplay::LastRelease() { + // We don't want to wait for the CC to free up the presentation + // for use in other documents, so we do this in LastRelease(). + Shutdown(); +} + +already_AddRefed<VREyeParameters> VRDisplay::GetEyeParameters(VREye aEye) { + gfx::VRDisplayState::Eye eye = aEye == VREye::Left + ? gfx::VRDisplayState::Eye_Left + : gfx::VRDisplayState::Eye_Right; + RefPtr<VREyeParameters> params = new VREyeParameters( + GetParentObject(), mClient->GetDisplayInfo().GetEyeTranslation(eye), + mClient->GetDisplayInfo().GetEyeFOV(eye), + mClient->GetDisplayInfo().SuggestedEyeResolution()); + return params.forget(); +} + +VRDisplayCapabilities* VRDisplay::Capabilities() { return mCapabilities; } + +VRStageParameters* VRDisplay::GetStageParameters() { return mStageParameters; } + +uint32_t VRDisplay::DisplayId() const { + const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo(); + return info.GetDisplayID(); +} + +void VRDisplay::GetDisplayName(nsAString& aDisplayName) const { + const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo(); + CopyUTF8toUTF16(MakeStringSpan(info.GetDisplayName()), aDisplayName); +} + +void VRDisplay::UpdateFrameInfo() { + /** + * The WebVR 1.1 spec Requires that VRDisplay.getPose and + * VRDisplay.getFrameData must return the same values until the next + * VRDisplay.submitFrame. + * + * mFrameInfo is marked dirty at the end of the frame or start of a new + * composition and lazily created here in order to receive mid-frame + * pose-prediction updates while still ensuring conformance to the WebVR spec + * requirements. + * + * If we are not presenting WebVR content, the frame will never end and we + * should return the latest frame data always. + */ + mFrameInfo.Clear(); + + if ((mFrameInfo.IsDirty() && IsPresenting()) || + mClient->GetDisplayInfo().GetPresentingGroups() == 0) { + const gfx::VRHMDSensorState& state = mClient->GetSensorState(); + const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo(); + mFrameInfo.Update(info, state, mDepthNear, mDepthFar); + } +} + +bool VRDisplay::GetFrameData(VRFrameData& aFrameData) { + UpdateFrameInfo(); + if (!(mFrameInfo.mVRState.flags & + gfx::VRDisplayCapabilityFlags::Cap_Orientation)) { + // We must have at minimum Cap_Orientation for a valid pose. + return false; + } + aFrameData.Update(mFrameInfo); + return true; +} + +already_AddRefed<VRPose> VRDisplay::GetPose() { + UpdateFrameInfo(); + RefPtr<VRPose> obj = new VRPose(GetParentObject(), mFrameInfo.mVRState); + + return obj.forget(); +} + +void VRDisplay::ResetPose() { + // ResetPose is deprecated and unimplemented + // We must keep this stub function around as its referenced by + // VRDisplay.webidl. Not asserting here, as that could break existing web + // content. +} + +void VRDisplay::StartVRNavigation() { mClient->StartVRNavigation(); } + +void VRDisplay::StartHandlingVRNavigationEvent() { + mHandlingVRNavigationEventStart = TimeStamp::Now(); + ++mVRNavigationEventDepth; + TimeDuration timeout = + TimeDuration::FromMilliseconds(StaticPrefs::dom_vr_navigation_timeout()); + // A 0 or negative TimeDuration indicates that content may take + // as long as it wishes to respond to the event, as long as + // it happens before the event exits. + if (timeout.ToMilliseconds() > 0) { + mClient->StopVRNavigation(timeout); + } +} + +void VRDisplay::StopHandlingVRNavigationEvent() { + MOZ_ASSERT(mVRNavigationEventDepth > 0); + --mVRNavigationEventDepth; + if (mVRNavigationEventDepth == 0) { + mClient->StopVRNavigation(TimeDuration::FromMilliseconds(0)); + } +} + +bool VRDisplay::IsHandlingVRNavigationEvent() { + if (mVRNavigationEventDepth == 0) { + return false; + } + if (mHandlingVRNavigationEventStart.IsNull()) { + return false; + } + TimeDuration timeout = + TimeDuration::FromMilliseconds(StaticPrefs::dom_vr_navigation_timeout()); + return timeout.ToMilliseconds() <= 0 || + (TimeStamp::Now() - mHandlingVRNavigationEventStart) <= timeout; +} + +void VRDisplay::OnPresentationGenerationChanged() { ExitPresentInternal(); } + +already_AddRefed<Promise> VRDisplay::RequestPresent( + const nsTArray<VRLayer>& aLayers, CallerType aCallerType, + ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> global = GetParentObject(); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Promise> promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + bool isChromePresentation = aCallerType == CallerType::System; + uint32_t presentationGroup = + isChromePresentation ? gfx::kVRGroupChrome : gfx::kVRGroupContent; + + mClient->SetXRAPIMode(gfx::VRAPIMode::WebVR); + if (!UserActivation::IsHandlingUserInput() && !isChromePresentation && + !IsHandlingVRNavigationEvent() && StaticPrefs::dom_vr_require_gesture() && + !IsPresenting()) { + // The WebVR API states that if called outside of a user gesture, the + // promise must be rejected. We allow VR presentations to start within + // trusted events such as vrdisplayactivate, which triggers in response to + // HMD proximity sensors and when navigating within a VR presentation. + // This user gesture requirement is not enforced for chrome/system code. + promise->MaybeRejectWithUndefined(); + } else if (!IsPresenting() && IsAnyPresenting(presentationGroup)) { + // Only one presentation allowed per VRDisplay on a + // first-come-first-serve basis. + // If this Javascript context is presenting, then we can replace our + // presentation with a new one containing new layers but we should never + // replace the presentation of another context. + // Simultaneous presentations in other groups are allowed in separate + // Javascript contexts to enable browser UI from chrome/system contexts. + // Eventually, this restriction will be loosened to enable multitasking + // use cases. + promise->MaybeRejectWithUndefined(); + } else { + if (mPresentation) { + mPresentation->UpdateLayers(aLayers); + } else { + mPresentation = mClient->BeginPresentation(aLayers, presentationGroup); + } + mFrameInfo.Clear(); + promise->MaybeResolve(JS::UndefinedHandleValue); + } + return promise.forget(); +} + +NS_IMETHODIMP +VRDisplay::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "inner-window-destroyed") == 0) { + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); + + uint64_t innerID; + nsresult rv = wrapper->GetData(&innerID); + NS_ENSURE_SUCCESS(rv, rv); + + if (!GetOwner() || GetOwner()->WindowID() == innerID) { + Shutdown(); + } + + return NS_OK; + } + + // This should not happen. + return NS_ERROR_FAILURE; +} + +already_AddRefed<Promise> VRDisplay::ExitPresent(ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> global = GetParentObject(); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Promise> promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + if (!IsPresenting()) { + // We can not exit a presentation outside of the context that + // started the presentation. + promise->MaybeRejectWithUndefined(); + } else { + promise->MaybeResolve(JS::UndefinedHandleValue); + ExitPresentInternal(); + } + + return promise.forget(); +} + +void VRDisplay::ExitPresentInternal() { mPresentation = nullptr; } + +void VRDisplay::Shutdown() { + mShutdown = true; + ExitPresentInternal(); + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (MOZ_LIKELY(obs)) { + obs->RemoveObserver(this, "inner-window-destroyed"); + } +} + +void VRDisplay::GetLayers(nsTArray<VRLayer>& result) { + if (mPresentation) { + mPresentation->GetDOMLayers(result); + } else { + result = nsTArray<VRLayer>(); + } +} + +void VRDisplay::SubmitFrame() { + AUTO_PROFILER_TRACING_MARKER("VR", "SubmitFrameAtVRDisplay", OTHER); + + if (mClient && !mClient->IsPresentationGenerationCurrent()) { + mPresentation = nullptr; + mClient->MakePresentationGenerationCurrent(); + } + + if (mPresentation) { + mPresentation->SubmitFrame(); + } + mFrameInfo.Clear(); +} + +int32_t VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback, + ErrorResult& aError) { + if (mShutdown) { + return 0; + } + + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + + int32_t handle; + aError = vm->ScheduleFrameRequestCallback(aCallback, &handle); + return handle; +} + +void VRDisplay::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError) { + gfx::VRManagerChild* vm = gfx::VRManagerChild::Get(); + vm->CancelFrameRequestCallback(aHandle); +} + +bool VRDisplay::IsPresenting() const { + // IsPresenting returns true only if this Javascript context is presenting + // and will return false if another context is presenting. + return mPresentation != nullptr; +} + +bool VRDisplay::IsAnyPresenting(uint32_t aGroupMask) const { + // IsAnyPresenting returns true if either this VRDisplay object or any other + // from anther Javascript context is presenting with a group matching + // aGroupMask. + if (mPresentation && (mPresentation->GetGroup() & aGroupMask)) { + return true; + } + if (mClient->GetDisplayInfo().GetPresentingGroups() & aGroupMask) { + return true; + } + return false; +} + +bool VRDisplay::IsConnected() const { return mClient->GetIsConnected(); } + +uint32_t VRDisplay::PresentingGroups() const { + return mClient->GetDisplayInfo().GetPresentingGroups(); +} + +uint32_t VRDisplay::GroupMask() const { + return mClient->GetDisplayInfo().GetGroupMask(); +} + +void VRDisplay::SetGroupMask(const uint32_t& aGroupMask) { + mClient->SetGroupMask(aGroupMask); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(VRDisplay, DOMEventTargetHelper, + mCapabilities, mStageParameters) + +NS_IMPL_ADDREF_INHERITED(VRDisplay, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(VRDisplay, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VRDisplay) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, DOMEventTargetHelper) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_CLASS(VRFrameData) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRFrameData) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mPose) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->mLeftProjectionMatrix = nullptr; + tmp->mLeftViewMatrix = nullptr; + tmp->mRightProjectionMatrix = nullptr; + tmp->mRightViewMatrix = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRFrameData) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mPose) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRFrameData) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftProjectionMatrix) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftViewMatrix) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightProjectionMatrix) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightViewMatrix) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFrameData, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFrameData, Release) + +VRFrameData::VRFrameData(nsISupports* aParent) + : mParent(aParent), + mLeftProjectionMatrix(nullptr), + mLeftViewMatrix(nullptr), + mRightProjectionMatrix(nullptr), + mRightViewMatrix(nullptr) { + mozilla::HoldJSObjects(this); + mPose = new VRPose(aParent); +} + +VRFrameData::~VRFrameData() { mozilla::DropJSObjects(this); } + +/* static */ +already_AddRefed<VRFrameData> VRFrameData::Constructor( + const GlobalObject& aGlobal) { + RefPtr<VRFrameData> obj = new VRFrameData(aGlobal.GetAsSupports()); + return obj.forget(); +} + +JSObject* VRFrameData::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return VRFrameData_Binding::Wrap(aCx, this, aGivenProto); +} + +VRPose* VRFrameData::Pose() { return mPose; } + +double VRFrameData::Timestamp() const { + // Converting from seconds to milliseconds + return mFrameInfo.mVRState.timestamp * 1000.0f; +} + +void VRFrameData::GetLeftProjectionMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + Pose::SetFloat32Array(aCx, this, aRetval, mLeftProjectionMatrix, + mFrameInfo.mLeftProjection.components, 16, aRv); +} + +void VRFrameData::GetLeftViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + Pose::SetFloat32Array(aCx, this, aRetval, mLeftViewMatrix, + mFrameInfo.mLeftView.components, 16, aRv); +} + +void VRFrameData::GetRightProjectionMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + Pose::SetFloat32Array(aCx, this, aRetval, mRightProjectionMatrix, + mFrameInfo.mRightProjection.components, 16, aRv); +} + +void VRFrameData::GetRightViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) { + Pose::SetFloat32Array(aCx, this, aRetval, mRightViewMatrix, + mFrameInfo.mRightView.components, 16, aRv); +} + +void VRFrameData::Update(const VRFrameInfo& aFrameInfo) { + mFrameInfo = aFrameInfo; + mPose->Update(mFrameInfo.mVRState); +} + +void VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo, + const gfx::VRHMDSensorState& aState, float aDepthNear, + float aDepthFar) { + mVRState = aState; + if (mTimeStampOffset == 0.0f) { + /** + * A mTimeStampOffset value of 0.0f indicates that this is the first + * iteration and an offset has not yet been set. + * + * Generate a value for mTimeStampOffset such that if aState.timestamp is + * monotonically increasing, aState.timestamp + mTimeStampOffset will never + * be a negative number and will start at a pseudo-random offset + * between 1000.0f and 11000.0f seconds. + * + * We use a pseudo random offset rather than 0.0f just to discourage users + * from making the assumption that the timestamp returned in the WebVR API + * has a base of 0, which is not necessarily true in all UA's. + */ + mTimeStampOffset = + float(rand()) / float(RAND_MAX) * 10000.0f + 1000.0f - aState.timestamp; + } + mVRState.timestamp = aState.timestamp + mTimeStampOffset; + + // Avoid division by zero within ConstructProjectionMatrix + const float kEpsilon = 0.00001f; + if (fabs(aDepthFar - aDepthNear) < kEpsilon) { + aDepthFar = aDepthNear + kEpsilon; + } + + const gfx::VRFieldOfView leftFOV = + aInfo.mDisplayState.eyeFOV[gfx::VRDisplayState::Eye_Left]; + mLeftProjection = + leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true); + const gfx::VRFieldOfView rightFOV = + aInfo.mDisplayState.eyeFOV[gfx::VRDisplayState::Eye_Right]; + mRightProjection = + rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true); + memcpy(mLeftView.components, aState.leftViewMatrix, + sizeof(aState.leftViewMatrix)); + memcpy(mRightView.components, aState.rightViewMatrix, + sizeof(aState.rightViewMatrix)); +} + +VRFrameInfo::VRFrameInfo() : mTimeStampOffset(0.0f) { + mVRState.inputFrameID = 0; + mVRState.timestamp = 0.0; + mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None; +} + +bool VRFrameInfo::IsDirty() { return mVRState.timestamp == 0; } + +void VRFrameInfo::Clear() { mVRState.Clear(); } + +} // namespace dom +} // namespace mozilla |