diff options
Diffstat (limited to 'dom/vr')
-rw-r--r-- | dom/vr/VRDisplay.cpp | 802 | ||||
-rw-r--r-- | dom/vr/VRDisplay.h | 362 | ||||
-rw-r--r-- | dom/vr/VREventObserver.cpp | 79 | ||||
-rw-r--r-- | dom/vr/VREventObserver.h | 33 | ||||
-rw-r--r-- | dom/vr/moz.build | 22 |
5 files changed, 1298 insertions, 0 deletions
diff --git a/dom/vr/VRDisplay.cpp b/dom/vr/VRDisplay.cpp new file mode 100644 index 000000000..80922422f --- /dev/null +++ b/dom/vr/VRDisplay.cpp @@ -0,0 +1,802 @@ +/* -*- 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/VRDisplay.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/dom/VRDisplayBinding.h" +#include "Navigator.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 +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; +} + +/*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() == display->GetDisplayInfo()) { + displays.AppendElement(aDisplays[j]); + isNewDisplay = false; + } + } + + if (isNewDisplay) { + displays.AppendElement(new VRDisplay(aWindow, display)); + } + } + } + + aDisplays = 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 VRFieldOfViewBinding::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_SCRIPT_OBJECTS +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 VREyeParametersBinding::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 VRStageParametersBinding::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_SCRIPT_OBJECTS +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 VRDisplayCapabilitiesBinding::Wrap(aCx, this, aGivenProto); +} + +VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState) + : Pose(aParent) + , mVRState(aState) +{ + mFrameId = aState.inputFrameID; + mozilla::HoldJSObjects(this); +} + +VRPose::VRPose(nsISupports* aParent) + : Pose(aParent) +{ + mFrameId = 0; + mVRState.Clear(); + mozilla::HoldJSObjects(this); +} + +VRPose::~VRPose() +{ + mozilla::DropJSObjects(this); +} + +void +VRPose::GetPosition(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mPosition, mVRState.position, 3, + !mPosition && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position), + aRv); +} + +void +VRPose::GetLinearVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mLinearVelocity, mVRState.linearVelocity, 3, + !mLinearVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position), + aRv); +} + +void +VRPose::GetLinearAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mLinearAcceleration, mVRState.linearAcceleration, 3, + !mLinearAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration), + aRv); + +} + +void +VRPose::GetOrientation(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mOrientation, mVRState.orientation, 4, + !mOrientation && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation), + aRv); +} + +void +VRPose::GetAngularVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mAngularVelocity, mVRState.angularVelocity, 3, + !mAngularVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation), + aRv); +} + +void +VRPose::GetAngularAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + SetFloat32Array(aCx, aRetval, mAngularAcceleration, mVRState.angularAcceleration, 3, + !mAngularAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration), + aRv); +} + +JSObject* +VRPose::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return VRPoseBinding::Wrap(aCx, this, aGivenProto); +} + +/* virtual */ JSObject* +VRDisplay::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return VRDisplayBinding::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 +{ + const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo(); + mDisplayId = info.GetDisplayID(); + mDisplayName = NS_ConvertASCIItoUTF16(info.GetDisplayName()); + mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities()); + if (info.GetCapabilities() & gfx::VRDisplayCapabilityFlags::Cap_StageParameters) { + mStageParameters = new VRStageParameters(aWindow, + info.GetSittingToStandingTransform(), + info.GetStageSize()); + } + mozilla::HoldJSObjects(this); +} + +VRDisplay::~VRDisplay() +{ + ExitPresentInternal(); + 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(). + ExitPresentInternal(); +} + +already_AddRefed<VREyeParameters> +VRDisplay::GetEyeParameters(VREye aEye) +{ + gfx::VRDisplayInfo::Eye eye = aEye == VREye::Left ? gfx::VRDisplayInfo::Eye_Left : gfx::VRDisplayInfo::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; +} + +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. + */ + if (mFrameInfo.IsDirty() || !mPresentation) { + gfx::VRHMDSensorState state = mClient->GetSensorState(); + const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo(); + mFrameInfo.Update(info, state, mDepthNear, mDepthFar); + } +} + +bool +VRDisplay::GetFrameData(VRFrameData& aFrameData) +{ + UpdateFrameInfo(); + 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() +{ + mClient->ZeroSensor(); +} + +already_AddRefed<Promise> +VRDisplay::RequestPresent(const nsTArray<VRLayer>& aLayers, ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Promise> promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + NS_ENSURE_TRUE(obs, nullptr); + + if (mClient->GetIsPresenting()) { + // Only one presentation allowed per VRDisplay + // on a first-come-first-serve basis. + promise->MaybeRejectWithUndefined(); + } else { + mPresentation = mClient->BeginPresentation(aLayers); + mFrameInfo.Clear(); + + nsresult rv = obs->AddObserver(this, "inner-window-destroyed", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPresentation = nullptr; + promise->MaybeRejectWithUndefined(); + } else { + 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) { + ExitPresentInternal(); + } + + return NS_OK; + } + + // This should not happen. + return NS_ERROR_FAILURE; +} + +already_AddRefed<Promise> +VRDisplay::ExitPresent(ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(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::GetLayers(nsTArray<VRLayer>& result) +{ + if (mPresentation) { + mPresentation->GetDOMLayers(result); + } else { + result = nsTArray<VRLayer>(); + } +} + +void +VRDisplay::SubmitFrame() +{ + if (mPresentation) { + mPresentation->SubmitFrame(); + } + mFrameInfo.Clear(); +} + +int32_t +VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback, +ErrorResult& aError) +{ + 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::IsConnected() const +{ + return mClient->GetIsConnected(); +} + +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_INHERITED(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_SCRIPT_OBJECTS +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, ErrorResult& aRv) +{ + RefPtr<VRFrameData> obj = new VRFrameData(aGlobal.GetAsSupports()); + return obj.forget(); +} + +JSObject* +VRFrameData::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return VRFrameDataBinding::Wrap(aCx, this, aGivenProto); +} + +VRPose* +VRFrameData::Pose() +{ + return mPose; +} + +void +VRFrameData::LazyCreateMatrix(JS::Heap<JSObject*>& aArray, gfx::Matrix4x4& aMat, JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) +{ + if (!aArray) { + // Lazily create the Float32Array + aArray = dom::Float32Array::Create(aCx, this, 16, aMat.components); + if (!aArray) { + aRv.NoteJSContextException(aCx); + return; + } + } + if (aArray) { + JS::ExposeObjectToActiveJS(aArray); + } + aRetval.set(aArray); +} + +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) +{ + LazyCreateMatrix(mLeftProjectionMatrix, mFrameInfo.mLeftProjection, aCx, + aRetval, aRv); +} + +void +VRFrameData::GetLeftViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + LazyCreateMatrix(mLeftViewMatrix, mFrameInfo.mLeftView, aCx, aRetval, aRv); +} + +void +VRFrameData::GetRightProjectionMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + LazyCreateMatrix(mRightProjectionMatrix, mFrameInfo.mRightProjection, aCx, + aRetval, aRv); +} + +void +VRFrameData::GetRightViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) +{ + LazyCreateMatrix(mRightViewMatrix, mFrameInfo.mRightView, aCx, aRetval, aRv); +} + +void +VRFrameData::Update(const VRFrameInfo& aFrameInfo) +{ + mFrameInfo = aFrameInfo; + + mLeftProjectionMatrix = nullptr; + mLeftViewMatrix = nullptr; + mRightProjectionMatrix = nullptr; + mRightViewMatrix = nullptr; + + mPose = new VRPose(GetParentObject(), mFrameInfo.mVRState); +} + +void +VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo, + const gfx::VRHMDSensorState& aState, + float aDepthNear, + float aDepthFar) +{ + mVRState = aState; + + gfx::Quaternion qt; + if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) { + qt.x = mVRState.orientation[0]; + qt.y = mVRState.orientation[1]; + qt.z = mVRState.orientation[2]; + qt.w = mVRState.orientation[3]; + } + gfx::Point3D pos; + if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) { + pos.x = -mVRState.position[0]; + pos.y = -mVRState.position[1]; + pos.z = -mVRState.position[2]; + } + gfx::Matrix4x4 matHead; + matHead.SetRotationFromQuaternion(qt); + matHead.PreTranslate(pos); + + mLeftView = matHead; + mLeftView.PostTranslate(-aInfo.mEyeTranslation[gfx::VRDisplayInfo::Eye_Left]); + + mRightView = matHead; + mRightView.PostTranslate(-aInfo.mEyeTranslation[gfx::VRDisplayInfo::Eye_Right]); + + // Avoid division by zero within ConstructProjectionMatrix + const float kEpsilon = 0.00001f; + if (fabs(aDepthFar - aDepthNear) < kEpsilon) { + aDepthFar = aDepthNear + kEpsilon; + } + + const gfx::VRFieldOfView leftFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Left]; + mLeftProjection = leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true); + const gfx::VRFieldOfView rightFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Right]; + mRightProjection = rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true); +} + +VRFrameInfo::VRFrameInfo() +{ + mVRState.Clear(); +} + +bool +VRFrameInfo::IsDirty() +{ + return mVRState.timestamp == 0; +} + +void +VRFrameInfo::Clear() +{ + mVRState.Clear(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/vr/VRDisplay.h b/dom/vr/VRDisplay.h new file mode 100644 index 000000000..d40d3d8ac --- /dev/null +++ b/dom/vr/VRDisplay.h @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_VRDisplay_h_ +#define mozilla_dom_VRDisplay_h_ + +#include <stdint.h> + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/VRDisplayBinding.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/DOMPoint.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/Pose.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "gfxVR.h" + +namespace mozilla { +namespace gfx { +class VRDisplayClient; +class VRDisplayPresentation; +struct VRFieldOfView; +enum class VRDisplayCapabilityFlags : uint16_t; +struct VRHMDSensorState; +} +namespace dom { +class Navigator; + +class VRFieldOfView final : public nsWrapperCache +{ +public: + VRFieldOfView(nsISupports* aParent, + double aUpDegrees, double aRightDegrees, + double aDownDegrees, double aLeftDegrees); + VRFieldOfView(nsISupports* aParent, const gfx::VRFieldOfView& aSrc); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFieldOfView) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFieldOfView) + + double UpDegrees() const { return mUpDegrees; } + double RightDegrees() const { return mRightDegrees; } + double DownDegrees() const { return mDownDegrees; } + double LeftDegrees() const { return mLeftDegrees; } + + nsISupports* GetParentObject() const { return mParent; } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + +protected: + virtual ~VRFieldOfView() {} + + nsCOMPtr<nsISupports> mParent; + + double mUpDegrees; + double mRightDegrees; + double mDownDegrees; + double mLeftDegrees; +}; + +class VRDisplayCapabilities final : public nsWrapperCache +{ +public: + VRDisplayCapabilities(nsISupports* aParent, const gfx::VRDisplayCapabilityFlags& aFlags) + : mParent(aParent) + , mFlags(aFlags) + { + } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRDisplayCapabilities) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRDisplayCapabilities) + + nsISupports* GetParentObject() const + { + return mParent; + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + bool HasPosition() const; + bool HasOrientation() const; + bool HasExternalDisplay() const; + bool CanPresent() const; + uint32_t MaxLayers() const; + +protected: + ~VRDisplayCapabilities() {} + nsCOMPtr<nsISupports> mParent; + gfx::VRDisplayCapabilityFlags mFlags; +}; + +class VRPose final : public Pose +{ + +public: + VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState); + explicit VRPose(nsISupports* aParent); + + uint32_t FrameID() const { return mFrameId; } + + virtual void GetPosition(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + virtual void GetLinearVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + virtual void GetLinearAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + virtual void GetOrientation(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + virtual void GetAngularVelocity(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + virtual void GetAngularAcceleration(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv) override; + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + +protected: + ~VRPose(); + + uint32_t mFrameId; + gfx::VRHMDSensorState mVRState; +}; + +struct VRFrameInfo +{ + VRFrameInfo(); + + void Update(const gfx::VRDisplayInfo& aInfo, + const gfx::VRHMDSensorState& aState, + float aDepthNear, + float aDepthFar); + + void Clear(); + bool IsDirty(); + + gfx::VRHMDSensorState mVRState; + gfx::Matrix4x4 mLeftProjection; + gfx::Matrix4x4 mLeftView; + gfx::Matrix4x4 mRightProjection; + gfx::Matrix4x4 mRightView; + +}; + +class VRFrameData final : public nsWrapperCache +{ +public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFrameData) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFrameData) + + explicit VRFrameData(nsISupports* aParent); + static already_AddRefed<VRFrameData> Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv); + + void Update(const VRFrameInfo& aFrameInfo); + + // WebIDL Members + double Timestamp() const; + void GetLeftProjectionMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); + void GetLeftViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); + void GetRightProjectionMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); + void GetRightViewMatrix(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); + + VRPose* Pose(); + + // WebIDL Boilerplate + nsISupports* GetParentObject() const { return mParent; } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + +protected: + ~VRFrameData(); + nsCOMPtr<nsISupports> mParent; + + VRFrameInfo mFrameInfo; + RefPtr<VRPose> mPose; + JS::Heap<JSObject*> mLeftProjectionMatrix; + JS::Heap<JSObject*> mLeftViewMatrix; + JS::Heap<JSObject*> mRightProjectionMatrix; + JS::Heap<JSObject*> mRightViewMatrix; + + void LazyCreateMatrix(JS::Heap<JSObject*>& aArray, gfx::Matrix4x4& aMat, + JSContext* aCx, JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); +}; + +class VRStageParameters final : public nsWrapperCache +{ +public: + VRStageParameters(nsISupports* aParent, + const gfx::Matrix4x4& aSittingToStandingTransform, + const gfx::Size& aSize); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRStageParameters) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRStageParameters) + + void GetSittingToStandingTransform(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aRv); + float SizeX() const { return mSize.width; } + float SizeZ() const { return mSize.height; } + + nsISupports* GetParentObject() const { return mParent; } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + +protected: + ~VRStageParameters(); + + nsCOMPtr<nsISupports> mParent; + + gfx::Matrix4x4 mSittingToStandingTransform; + JS::Heap<JSObject*> mSittingToStandingTransformArray; + gfx::Size mSize; +}; + +class VREyeParameters final : public nsWrapperCache +{ +public: + VREyeParameters(nsISupports* aParent, + const gfx::Point3D& aEyeTranslation, + const gfx::VRFieldOfView& aFOV, + const gfx::IntSize& aRenderSize); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VREyeParameters) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VREyeParameters) + + void GetOffset(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal, + ErrorResult& aRv); + + VRFieldOfView* FieldOfView(); + + uint32_t RenderWidth() const { return mRenderSize.width; } + uint32_t RenderHeight() const { return mRenderSize.height; } + + nsISupports* GetParentObject() const { return mParent; } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; +protected: + ~VREyeParameters(); + + nsCOMPtr<nsISupports> mParent; + + + gfx::Point3D mEyeTranslation; + gfx::IntSize mRenderSize; + JS::Heap<JSObject*> mOffset; + RefPtr<VRFieldOfView> mFOV; +}; + +class VRDisplay final : public DOMEventTargetHelper + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRDisplay, DOMEventTargetHelper) + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + bool IsPresenting() const; + bool IsConnected() const; + + VRDisplayCapabilities* Capabilities(); + VRStageParameters* GetStageParameters(); + + uint32_t DisplayId() const { return mDisplayId; } + void GetDisplayName(nsAString& aDisplayName) const { aDisplayName = mDisplayName; } + + static bool RefreshVRDisplays(uint64_t aWindowId); + static void UpdateVRDisplays(nsTArray<RefPtr<VRDisplay> >& aDisplays, + nsPIDOMWindowInner* aWindow); + + gfx::VRDisplayClient *GetClient() { + return mClient; + } + + virtual already_AddRefed<VREyeParameters> GetEyeParameters(VREye aEye); + + bool GetFrameData(VRFrameData& aFrameData); + already_AddRefed<VRPose> GetPose(); + void ResetPose(); + + double DepthNear() { + return mDepthNear; + } + + double DepthFar() { + return mDepthFar; + } + + void SetDepthNear(double aDepthNear) { + // XXX When we start sending depth buffers to VRLayer's we will want + // to communicate this with the VRDisplayHost + mDepthNear = aDepthNear; + } + + void SetDepthFar(double aDepthFar) { + // XXX When we start sending depth buffers to VRLayer's we will want + // to communicate this with the VRDisplayHost + mDepthFar = aDepthFar; + } + + already_AddRefed<Promise> RequestPresent(const nsTArray<VRLayer>& aLayers, ErrorResult& aRv); + already_AddRefed<Promise> ExitPresent(ErrorResult& aRv); + void GetLayers(nsTArray<VRLayer>& result); + void SubmitFrame(); + + int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback, + mozilla::ErrorResult& aError); + void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError); + +protected: + VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient); + virtual ~VRDisplay(); + virtual void LastRelease() override; + + void ExitPresentInternal(); + void UpdateFrameInfo(); + + RefPtr<gfx::VRDisplayClient> mClient; + + uint32_t mDisplayId; + nsString mDisplayName; + + RefPtr<VRDisplayCapabilities> mCapabilities; + RefPtr<VRStageParameters> mStageParameters; + + double mDepthNear; + double mDepthFar; + + RefPtr<gfx::VRDisplayPresentation> mPresentation; + + /** + * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData + * must return the same values until the next VRDisplay.submitFrame. + * mFrameInfo is updated only on the first call to either function within one + * frame. Subsequent calls before the next SubmitFrame or ExitPresent call + * will use these cached values. + */ + VRFrameInfo mFrameInfo; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/vr/VREventObserver.cpp b/dom/vr/VREventObserver.cpp new file mode 100644 index 000000000..1b6d1b978 --- /dev/null +++ b/dom/vr/VREventObserver.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VREventObserver.h" + +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "VRManagerChild.h" + +namespace mozilla { +namespace dom { + +using namespace gfx; + +/** + * This class is used by nsGlobalWindow to implement window.onvrdisplayconnected, + * window.onvrdisplaydisconnected, and window.onvrdisplaypresentchange. + */ +VREventObserver::VREventObserver(nsGlobalWindow* aGlobalWindow) + : mWindow(aGlobalWindow) +{ + MOZ_ASSERT(aGlobalWindow && aGlobalWindow->IsInnerWindow()); + + VRManagerChild* vmc = VRManagerChild::Get(); + if (vmc) { + vmc->AddListener(this); + } +} + +VREventObserver::~VREventObserver() +{ + VRManagerChild* vmc = VRManagerChild::Get(); + if (vmc) { + vmc->RemoveListener(this); + } +} + +void +VREventObserver::NotifyVRDisplayConnect() +{ + /** + * We do not call nsGlobalWindow::NotifyActiveVRDisplaysChanged here, as we + * can assume that a newly enumerated display is not presenting WebVR + * content. + */ + if (mWindow->AsInner()->IsCurrentInnerWindow()) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mWindow->GetOuterWindow()->DispatchCustomEvent( + NS_LITERAL_STRING("vrdisplayconnected")); + } +} + +void +VREventObserver::NotifyVRDisplayDisconnect() +{ + if (mWindow->AsInner()->IsCurrentInnerWindow()) { + mWindow->NotifyActiveVRDisplaysChanged(); + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mWindow->GetOuterWindow()->DispatchCustomEvent( + NS_LITERAL_STRING("vrdisplaydisconnected")); + } +} + +void +VREventObserver::NotifyVRDisplayPresentChange() +{ + if (mWindow->AsInner()->IsCurrentInnerWindow()) { + mWindow->NotifyActiveVRDisplaysChanged(); + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mWindow->GetOuterWindow()->DispatchCustomEvent( + NS_LITERAL_STRING("vrdisplaypresentchange")); + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/vr/VREventObserver.h b/dom/vr/VREventObserver.h new file mode 100644 index 000000000..a30bb5960 --- /dev/null +++ b/dom/vr/VREventObserver.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_VREventObserver_h +#define mozilla_dom_VREventObserver_h + +class nsGlobalWindow; + +namespace mozilla { +namespace dom { + +class VREventObserver final +{ +public: + ~VREventObserver(); + explicit VREventObserver(nsGlobalWindow* aGlobalWindow); + + void NotifyVRDisplayConnect(); + void NotifyVRDisplayDisconnect(); + void NotifyVRDisplayPresentChange(); + +private: + // Weak pointer, instance is owned by mWindow. + nsGlobalWindow* MOZ_NON_OWNING_REF mWindow; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_VREventObserver_h diff --git a/dom/vr/moz.build b/dom/vr/moz.build new file mode 100644 index 000000000..a4aa8d69b --- /dev/null +++ b/dom/vr/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.dom += [ + 'VRDisplay.h', + 'VREventObserver.h', + ] + +UNIFIED_SOURCES = [ + 'VRDisplay.cpp', + 'VREventObserver.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/base' +] |