summaryrefslogtreecommitdiffstats
path: root/gfx/vr/VRDisplayClient.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/vr/VRDisplayClient.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/vr/VRDisplayClient.cpp')
-rw-r--r--gfx/vr/VRDisplayClient.cpp666
1 files changed, 666 insertions, 0 deletions
diff --git a/gfx/vr/VRDisplayClient.cpp b/gfx/vr/VRDisplayClient.cpp
new file mode 100644
index 0000000000..914fb05893
--- /dev/null
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -0,0 +1,666 @@
+/* -*- 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 <math.h>
+
+#include "prlink.h"
+#include "prenv.h"
+
+#include "nsIGlobalObject.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/XRSession.h"
+#include "mozilla/dom/XRInputSourceArray.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/WebXRBinding.h"
+#include "nsServiceManagerUtils.h"
+
+#ifdef XP_WIN
+# include "../layers/d3d11/CompositorD3D11.h"
+#endif
+
+#include "VRDisplayClient.h"
+#include "VRDisplayPresentation.h"
+#include "VRManagerChild.h"
+#include "VRLayerChild.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+using mozilla::dom::GamepadHandle;
+using mozilla::dom::GamepadHandleKind;
+
+VRDisplayClient::VRDisplayClient(const VRDisplayInfo& aDisplayInfo)
+ : mDisplayInfo(aDisplayInfo),
+ bLastEventWasMounted(false),
+ bLastEventWasPresenting(false),
+ mPresentationCount(0),
+ mLastEventFrameId(0),
+ mLastPresentingGeneration(0),
+ mLastEventControllerState{},
+ // For now WebVR is default to prevent a VRDisplay restore bug in WebVR
+ // compatibility mode. See Bug 1630512
+ mAPIMode(VRAPIMode::WebVR) {
+ MOZ_COUNT_CTOR(VRDisplayClient);
+}
+
+VRDisplayClient::~VRDisplayClient() { MOZ_COUNT_DTOR(VRDisplayClient); }
+
+void VRDisplayClient::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) {
+ mDisplayInfo = aDisplayInfo;
+ FireEvents();
+}
+
+already_AddRefed<VRDisplayPresentation> VRDisplayClient::BeginPresentation(
+ const nsTArray<mozilla::dom::VRLayer>& aLayers, uint32_t aGroup) {
+ PresentationCreated();
+ RefPtr<VRDisplayPresentation> presentation =
+ new VRDisplayPresentation(this, aLayers, aGroup);
+ return presentation.forget();
+}
+
+void VRDisplayClient::PresentationCreated() { ++mPresentationCount; }
+
+void VRDisplayClient::PresentationDestroyed() { --mPresentationCount; }
+
+void VRDisplayClient::SessionStarted(dom::XRSession* aSession) {
+ PresentationCreated();
+ MakePresentationGenerationCurrent();
+ mSessions.AppendElement(aSession);
+}
+void VRDisplayClient::SessionEnded(dom::XRSession* aSession) {
+ mSessions.RemoveElement(aSession);
+ PresentationDestroyed();
+}
+
+void VRDisplayClient::StartFrame() {
+ RefPtr<VRManagerChild> vm = VRManagerChild::Get();
+ vm->RunFrameRequestCallbacks();
+
+ nsTArray<RefPtr<dom::XRSession>> sessions;
+ sessions.AppendElements(mSessions);
+ for (auto session : sessions) {
+ session->StartFrame();
+ }
+}
+
+void VRDisplayClient::SetGroupMask(uint32_t aGroupMask) {
+ VRManagerChild* vm = VRManagerChild::Get();
+ vm->SendSetGroupMask(mDisplayInfo.mDisplayID, aGroupMask);
+}
+
+bool VRDisplayClient::IsPresentationGenerationCurrent() const {
+ if (mLastPresentingGeneration !=
+ mDisplayInfo.mDisplayState.presentingGeneration) {
+ return false;
+ }
+
+ return true;
+}
+
+void VRDisplayClient::MakePresentationGenerationCurrent() {
+ mLastPresentingGeneration = mDisplayInfo.mDisplayState.presentingGeneration;
+}
+
+gfx::VRAPIMode VRDisplayClient::GetXRAPIMode() const { return mAPIMode; }
+
+void VRDisplayClient::SetXRAPIMode(gfx::VRAPIMode aMode) {
+ mAPIMode = aMode;
+ Telemetry::Accumulate(Telemetry::WEBXR_API_MODE,
+ static_cast<uint32_t>(mAPIMode));
+}
+
+void VRDisplayClient::FireEvents() {
+ RefPtr<VRManagerChild> vm = VRManagerChild::Get();
+ // Only fire these events for non-chrome VR sessions
+ bool isPresenting = (mDisplayInfo.mPresentingGroups & kVRGroupContent) != 0;
+
+ // Check if we need to trigger onVRDisplayPresentChange event
+ if (bLastEventWasPresenting != isPresenting) {
+ bLastEventWasPresenting = isPresenting;
+ vm->FireDOMVRDisplayPresentChangeEvent(mDisplayInfo.mDisplayID);
+ }
+
+ // Check if we need to trigger onvrdisplayactivate event
+ if (!bLastEventWasMounted && mDisplayInfo.mDisplayState.isMounted) {
+ bLastEventWasMounted = true;
+ if (StaticPrefs::dom_vr_autoactivate_enabled()) {
+ vm->FireDOMVRDisplayMountedEvent(mDisplayInfo.mDisplayID);
+ }
+ }
+
+ // Check if we need to trigger onvrdisplaydeactivate event
+ if (bLastEventWasMounted && !mDisplayInfo.mDisplayState.isMounted) {
+ bLastEventWasMounted = false;
+ if (StaticPrefs::dom_vr_autoactivate_enabled()) {
+ vm->FireDOMVRDisplayUnmountedEvent(mDisplayInfo.mDisplayID);
+ }
+ }
+
+ if (mLastPresentingGeneration !=
+ mDisplayInfo.mDisplayState.presentingGeneration) {
+ mLastPresentingGeneration = mDisplayInfo.mDisplayState.presentingGeneration;
+ vm->NotifyPresentationGenerationChanged(mDisplayInfo.mDisplayID);
+ }
+
+ // In WebXR spec, Gamepad instances returned by an XRInputSource's gamepad
+ // attribute MUST NOT be included in the array returned by
+ // navigator.getGamepads().
+ if (mAPIMode == VRAPIMode::WebVR) {
+ FireGamepadEvents();
+ }
+ // Update controller states into XRInputSourceArray.
+ for (auto& session : mSessions) {
+ dom::XRInputSourceArray* inputs = session->InputSources();
+ if (inputs) {
+ inputs->Update(session);
+ }
+ }
+
+ // Check if we need to trigger VRDisplay.requestAnimationFrame
+ if (mLastEventFrameId != mDisplayInfo.mFrameId) {
+ mLastEventFrameId = mDisplayInfo.mFrameId;
+ StartFrame();
+ }
+}
+
+void VRDisplayClient::GamepadMappingForWebVR(
+ VRControllerState& aControllerState) {
+ float triggerValue[kVRControllerMaxButtons];
+ memcpy(triggerValue, aControllerState.triggerValue,
+ sizeof(aControllerState.triggerValue));
+ const uint64_t buttonPressed = aControllerState.buttonPressed;
+ const uint64_t buttonTouched = aControllerState.buttonTouched;
+
+ auto SetTriggerValue = [&](uint64_t newSlot, uint64_t oldSlot) {
+ aControllerState.triggerValue[newSlot] = triggerValue[oldSlot];
+ };
+ auto ShiftButtonBitForNewSlot = [&](uint64_t newSlot, uint64_t oldSlot,
+ bool aIsTouch = false) {
+ if (aIsTouch) {
+ return ((buttonTouched & (1ULL << oldSlot)) != 0) * (1ULL << newSlot);
+ }
+ SetTriggerValue(newSlot, oldSlot);
+ return ((buttonPressed & (1ULL << oldSlot)) != 0) * (1ULL << newSlot);
+ };
+
+ switch (aControllerState.type) {
+ case VRControllerType::HTCVive:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) |
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(3, 4);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 1, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(3, 4, true);
+ aControllerState.numButtons = 4;
+ aControllerState.numAxes = 2;
+ break;
+ case VRControllerType::MSMR:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) |
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(3, 3) |
+ ShiftButtonBitForNewSlot(4, 4);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(3, 3, true) |
+ ShiftButtonBitForNewSlot(4, 4, true);
+ aControllerState.numButtons = 5;
+ aControllerState.numAxes = 4;
+ break;
+ case VRControllerType::HTCViveCosmos:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 0) | ShiftButtonBitForNewSlot(1, 1) |
+ ShiftButtonBitForNewSlot(4, 3) | ShiftButtonBitForNewSlot(2, 4) |
+ ShiftButtonBitForNewSlot(3, 5) | ShiftButtonBitForNewSlot(5, 6);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 0, true) |
+ ShiftButtonBitForNewSlot(1, 1, true) |
+ ShiftButtonBitForNewSlot(4, 3, true) |
+ ShiftButtonBitForNewSlot(2, 4, true) |
+ ShiftButtonBitForNewSlot(3, 5, true) |
+ ShiftButtonBitForNewSlot(5, 6, true);
+ aControllerState.axisValue[0] = aControllerState.axisValue[2];
+ aControllerState.axisValue[1] = aControllerState.axisValue[3];
+ aControllerState.numButtons = 6;
+ aControllerState.numAxes = 2;
+ break;
+ case VRControllerType::HTCViveFocus:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(1, 0, true);
+ aControllerState.numButtons = 2;
+ aControllerState.numAxes = 2;
+ break;
+ case VRControllerType::HTCViveFocusPlus: {
+ aControllerState.buttonPressed = ShiftButtonBitForNewSlot(0, 2) |
+ ShiftButtonBitForNewSlot(1, 0) |
+ ShiftButtonBitForNewSlot(2, 1);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true);
+ aControllerState.numButtons = 3;
+ aControllerState.numAxes = 2;
+
+ static Matrix4x4 focusPlusTransform;
+ Matrix4x4 originalMtx;
+ if (focusPlusTransform.IsIdentity()) {
+ focusPlusTransform.RotateX(-0.70f);
+ focusPlusTransform.PostTranslate(0.0f, 0.0f, 0.01f);
+ focusPlusTransform.Inverse();
+ }
+ gfx::Quaternion quat(aControllerState.pose.orientation[0],
+ aControllerState.pose.orientation[1],
+ aControllerState.pose.orientation[2],
+ aControllerState.pose.orientation[3]);
+ originalMtx.SetRotationFromQuaternion(quat);
+ originalMtx._41 = aControllerState.pose.position[0];
+ originalMtx._42 = aControllerState.pose.position[1];
+ originalMtx._43 = aControllerState.pose.position[2];
+ originalMtx = focusPlusTransform * originalMtx;
+
+ gfx::Point3D pos, scale;
+ originalMtx.Decompose(pos, quat, scale);
+
+ aControllerState.pose.position[0] = pos.x;
+ aControllerState.pose.position[1] = pos.y;
+ aControllerState.pose.position[2] = pos.z;
+
+ aControllerState.pose.orientation[0] = quat.x;
+ aControllerState.pose.orientation[1] = quat.y;
+ aControllerState.pose.orientation[2] = quat.z;
+ aControllerState.pose.orientation[3] = quat.w;
+ break;
+ }
+ case VRControllerType::OculusGo: {
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(1, 0, true);
+ aControllerState.numButtons = 2;
+ aControllerState.numAxes = 2;
+
+ static Matrix4x4 goTransform;
+ Matrix4x4 originalMtx;
+
+ if (goTransform.IsIdentity()) {
+ goTransform.RotateX(-0.60f);
+ goTransform.Inverse();
+ }
+ gfx::Quaternion quat(aControllerState.pose.orientation[0],
+ aControllerState.pose.orientation[1],
+ aControllerState.pose.orientation[2],
+ aControllerState.pose.orientation[3]);
+ originalMtx.SetRotationFromQuaternion(quat);
+ originalMtx._41 = aControllerState.pose.position[0];
+ originalMtx._42 = aControllerState.pose.position[1];
+ originalMtx._43 = aControllerState.pose.position[2];
+ originalMtx = goTransform * originalMtx;
+
+ gfx::Point3D pos, scale;
+ originalMtx.Decompose(pos, quat, scale);
+
+ aControllerState.pose.position[0] = pos.x;
+ aControllerState.pose.position[1] = pos.y;
+ aControllerState.pose.position[2] = pos.z;
+
+ aControllerState.pose.orientation[0] = quat.x;
+ aControllerState.pose.orientation[1] = quat.y;
+ aControllerState.pose.orientation[2] = quat.z;
+ aControllerState.pose.orientation[3] = quat.w;
+ break;
+ }
+ case VRControllerType::OculusTouch:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) |
+ ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) |
+ ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) |
+ ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(3, 4, true) |
+ ShiftButtonBitForNewSlot(4, 5, true) |
+ ShiftButtonBitForNewSlot(5, 6, true);
+ aControllerState.axisValue[0] = aControllerState.axisValue[2];
+ aControllerState.axisValue[1] = aControllerState.axisValue[3];
+ aControllerState.numButtons = 6;
+ aControllerState.numAxes = 2;
+ break;
+ case VRControllerType::OculusTouch2:
+ case VRControllerType::OculusTouch3: {
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) |
+ ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) |
+ ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) |
+ ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(3, 4, true) |
+ ShiftButtonBitForNewSlot(4, 5, true) |
+ ShiftButtonBitForNewSlot(5, 6, true);
+ aControllerState.axisValue[0] = aControllerState.axisValue[2];
+ aControllerState.axisValue[1] = aControllerState.axisValue[3];
+ aControllerState.numButtons = 6;
+ aControllerState.numAxes = 2;
+
+ static Matrix4x4 touchTransform;
+ Matrix4x4 originalMtx;
+
+ if (touchTransform.IsIdentity()) {
+ touchTransform.RotateX(-0.77f);
+ touchTransform.PostTranslate(0.0f, 0.0f, -0.025f);
+ touchTransform.Inverse();
+ }
+ gfx::Quaternion quat(aControllerState.pose.orientation[0],
+ aControllerState.pose.orientation[1],
+ aControllerState.pose.orientation[2],
+ aControllerState.pose.orientation[3]);
+ originalMtx.SetRotationFromQuaternion(quat);
+ originalMtx._41 = aControllerState.pose.position[0];
+ originalMtx._42 = aControllerState.pose.position[1];
+ originalMtx._43 = aControllerState.pose.position[2];
+ originalMtx = touchTransform * originalMtx;
+
+ gfx::Point3D pos, scale;
+ originalMtx.Decompose(pos, quat, scale);
+
+ aControllerState.pose.position[0] = pos.x;
+ aControllerState.pose.position[1] = pos.y;
+ aControllerState.pose.position[2] = pos.z;
+
+ aControllerState.pose.orientation[0] = quat.x;
+ aControllerState.pose.orientation[1] = quat.y;
+ aControllerState.pose.orientation[2] = quat.z;
+ aControllerState.pose.orientation[3] = quat.w;
+ break;
+ }
+ case VRControllerType::ValveIndex:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) |
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(5, 3) |
+ ShiftButtonBitForNewSlot(3, 4) | ShiftButtonBitForNewSlot(4, 5) |
+ ShiftButtonBitForNewSlot(6, 6) | ShiftButtonBitForNewSlot(7, 7) |
+ ShiftButtonBitForNewSlot(8, 8) | ShiftButtonBitForNewSlot(9, 9);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(5, 3, true) |
+ ShiftButtonBitForNewSlot(3, 4, true) |
+ ShiftButtonBitForNewSlot(4, 5, true) |
+ ShiftButtonBitForNewSlot(6, 6, true) |
+ ShiftButtonBitForNewSlot(7, 7, true) |
+ ShiftButtonBitForNewSlot(8, 8, true) |
+ ShiftButtonBitForNewSlot(9, 9, true);
+ aControllerState.numButtons = 10;
+ aControllerState.numAxes = 4;
+ break;
+ case VRControllerType::PicoGaze:
+ aControllerState.buttonPressed = ShiftButtonBitForNewSlot(0, 0);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 0, true);
+ aControllerState.numButtons = 1;
+ aControllerState.numAxes = 0;
+ break;
+ case VRControllerType::PicoG2:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) |
+ ShiftButtonBitForNewSlot(1, 0, true);
+ aControllerState.numButtons = 2;
+ aControllerState.numAxes = 2;
+ break;
+ case VRControllerType::PicoNeo2:
+ aControllerState.buttonPressed =
+ ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) |
+ ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) |
+ ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6);
+ aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) |
+ ShiftButtonBitForNewSlot(1, 0, true) |
+ ShiftButtonBitForNewSlot(2, 1, true) |
+ ShiftButtonBitForNewSlot(3, 4, true) |
+ ShiftButtonBitForNewSlot(4, 5, true) |
+ ShiftButtonBitForNewSlot(5, 6, true);
+ aControllerState.axisValue[0] = aControllerState.axisValue[2];
+ aControllerState.axisValue[1] = aControllerState.axisValue[3];
+ aControllerState.numButtons = 6;
+ aControllerState.numAxes = 2;
+ break;
+ default:
+ // Undefined controller types, we will keep its the same order.
+ break;
+ }
+}
+
+void VRDisplayClient::FireGamepadEvents() {
+ RefPtr<dom::GamepadManager> gamepadManager(dom::GamepadManager::GetService());
+ if (!gamepadManager) {
+ return;
+ }
+ for (int stateIndex = 0; stateIndex < kVRControllerMaxCount; stateIndex++) {
+ VRControllerState state = {}, lastState = {};
+ memcpy(&state, &mDisplayInfo.mControllerState[stateIndex],
+ sizeof(VRControllerState));
+ memcpy(&lastState, &mLastEventControllerState[stateIndex],
+ sizeof(VRControllerState));
+ GamepadMappingForWebVR(state);
+ GamepadMappingForWebVR(lastState);
+
+ uint32_t gamepadHandleValue =
+ mDisplayInfo.mDisplayID * kVRControllerMaxCount + stateIndex;
+
+ GamepadHandle gamepadHandle{gamepadHandleValue, GamepadHandleKind::VR};
+
+ bool bIsNew = false;
+
+ // Send events to notify that controllers are removed
+ if (state.controllerName[0] == '\0') {
+ // Controller is not present
+ if (lastState.controllerName[0] != '\0') {
+ // Controller has been removed
+ dom::GamepadRemoved info;
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ }
+ // Do not process any further events for removed controllers
+ continue;
+ }
+
+ // Send events to notify that new controllers are added
+ RefPtr<dom::Gamepad> existing = gamepadManager->GetGamepad(gamepadHandle);
+ // ControllerState in OpenVR action-based API gets delay to query btn and
+ // axis count. So, we need to check if they are more than zero.
+ if ((lastState.controllerName[0] == '\0' || !existing) &&
+ (state.numButtons > 0 || state.numAxes > 0)) {
+ dom::GamepadAdded info(NS_ConvertUTF8toUTF16(state.controllerName),
+ dom::GamepadMappingType::_empty, state.hand,
+ mDisplayInfo.mDisplayID, state.numButtons,
+ state.numAxes, state.numHaptics, 0, 0);
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ bIsNew = true;
+ }
+
+ // Send events for handedness changes
+ if (state.hand != lastState.hand) {
+ dom::GamepadHandInformation info(state.hand);
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ }
+
+ // Send events for axis value changes
+ for (uint32_t axisIndex = 0; axisIndex < state.numAxes; axisIndex++) {
+ if (state.axisValue[axisIndex] != lastState.axisValue[axisIndex]) {
+ dom::GamepadAxisInformation info(axisIndex, state.axisValue[axisIndex]);
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ }
+ }
+
+ // Send events for trigger, touch, and button value changes
+ if (!bIsNew) {
+ // When a new controller is added, we do not emit button events for
+ // the initial state of the inputs.
+ for (uint32_t buttonIndex = 0; buttonIndex < state.numButtons;
+ buttonIndex++) {
+ bool bPressed = (state.buttonPressed & (1ULL << buttonIndex)) != 0;
+ bool bTouched = (state.buttonTouched & (1ULL << buttonIndex)) != 0;
+ bool bLastPressed =
+ (lastState.buttonPressed & (1ULL << buttonIndex)) != 0;
+ bool bLastTouched =
+ (lastState.buttonTouched & (1ULL << buttonIndex)) != 0;
+
+ if (state.triggerValue[buttonIndex] !=
+ lastState.triggerValue[buttonIndex] ||
+ bPressed != bLastPressed || bTouched != bLastTouched) {
+ dom::GamepadButtonInformation info(
+ buttonIndex, state.triggerValue[buttonIndex], bPressed, bTouched);
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ }
+ }
+ }
+
+ // Send events for pose changes
+ // Note that VRPose is asserted to be a POD type so memcmp is safe
+ if (state.flags != lastState.flags ||
+ state.isPositionValid != lastState.isPositionValid ||
+ state.isOrientationValid != lastState.isOrientationValid ||
+ memcmp(&state.pose, &lastState.pose, sizeof(VRPose)) != 0) {
+ // Convert pose to GamepadPoseState
+ dom::GamepadPoseState poseState;
+ poseState.Clear();
+ poseState.flags = state.flags;
+
+ // Orientation values
+ poseState.isOrientationValid = state.isOrientationValid;
+ poseState.orientation[0] = state.pose.orientation[0];
+ poseState.orientation[1] = state.pose.orientation[1];
+ poseState.orientation[2] = state.pose.orientation[2];
+ poseState.orientation[3] = state.pose.orientation[3];
+ poseState.angularVelocity[0] = state.pose.angularVelocity[0];
+ poseState.angularVelocity[1] = state.pose.angularVelocity[1];
+ poseState.angularVelocity[2] = state.pose.angularVelocity[2];
+ poseState.angularAcceleration[0] = state.pose.angularAcceleration[0];
+ poseState.angularAcceleration[1] = state.pose.angularAcceleration[1];
+ poseState.angularAcceleration[2] = state.pose.angularAcceleration[2];
+
+ // Position values
+ poseState.isPositionValid = state.isPositionValid;
+ poseState.position[0] = state.pose.position[0];
+ poseState.position[1] = state.pose.position[1];
+ poseState.position[2] = state.pose.position[2];
+ poseState.linearVelocity[0] = state.pose.linearVelocity[0];
+ poseState.linearVelocity[1] = state.pose.linearVelocity[1];
+ poseState.linearVelocity[2] = state.pose.linearVelocity[2];
+ poseState.linearAcceleration[0] = state.pose.linearAcceleration[0];
+ poseState.linearAcceleration[1] = state.pose.linearAcceleration[1];
+ poseState.linearAcceleration[2] = state.pose.linearAcceleration[2];
+
+ // Send the event
+ dom::GamepadPoseInformation info(poseState);
+ dom::GamepadChangeEventBody body(info);
+ dom::GamepadChangeEvent event(gamepadHandle, body);
+ gamepadManager->Update(event);
+ }
+ }
+
+ // Note that VRControllerState is asserted to be a POD type and memcpy is
+ // safe.
+ memcpy(mLastEventControllerState, mDisplayInfo.mControllerState,
+ sizeof(VRControllerState) * kVRControllerMaxCount);
+}
+
+const VRHMDSensorState& VRDisplayClient::GetSensorState() const {
+ return mDisplayInfo.GetSensorState();
+}
+
+bool VRDisplayClient::GetIsConnected() const {
+ return mDisplayInfo.GetIsConnected();
+}
+
+bool VRDisplayClient::IsPresenting() {
+ return mDisplayInfo.mPresentingGroups != 0;
+}
+
+void VRDisplayClient::NotifyDisconnected() {
+ mDisplayInfo.mDisplayState.isConnected = false;
+}
+
+void VRDisplayClient::UpdateSubmitFrameResult(
+ const VRSubmitFrameResultInfo& aResult) {
+ mSubmitFrameResult = aResult;
+}
+
+void VRDisplayClient::GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult) {
+ aResult = mSubmitFrameResult;
+}
+
+void VRDisplayClient::StartVRNavigation() {
+ /**
+ * A VR-to-VR site navigation has started, notify VRManager
+ * so we don't drop out of VR during the transition
+ */
+ VRManagerChild* vm = VRManagerChild::Get();
+ vm->SendStartVRNavigation(mDisplayInfo.mDisplayID);
+}
+
+void VRDisplayClient::StopVRNavigation(const TimeDuration& aTimeout) {
+ /**
+ * A VR-to-VR site navigation has ended and the new site
+ * has received a vrdisplayactivate event.
+ * Don't actually consider the navigation transition over
+ * until aTimeout has elapsed.
+ * This may be called multiple times, in which case the timeout
+ * should be reset to aTimeout.
+ * When aTimeout is TimeDuration(0), we should consider the
+ * transition immediately ended.
+ */
+ VRManagerChild* vm = VRManagerChild::Get();
+ vm->SendStopVRNavigation(mDisplayInfo.mDisplayID, aTimeout);
+}
+
+bool VRDisplayClient::IsReferenceSpaceTypeSupported(
+ dom::XRReferenceSpaceType aType) const {
+ /**
+ * https://immersive-web.github.io/webxr/#reference-space-is-supported
+ *
+ * We do not yet support local or local-floor for inline sessions.
+ * This could be expanded if we later support WebXR for inline-ar
+ * sessions on Firefox Fenix.
+ *
+ * We do not yet support unbounded reference spaces.
+ */
+ switch (aType) {
+ case dom::XRReferenceSpaceType::Viewer:
+ // Viewer is always supported, for both inline and immersive sessions
+ return true;
+ case dom::XRReferenceSpaceType::Local:
+ case dom::XRReferenceSpaceType::Local_floor:
+ // Local and Local_Floor are always supported for immersive sessions
+ return bool(mDisplayInfo.GetCapabilities() &
+ (VRDisplayCapabilityFlags::Cap_ImmersiveVR |
+ VRDisplayCapabilityFlags::Cap_ImmersiveAR));
+ case dom::XRReferenceSpaceType::Bounded_floor:
+ return bool(mDisplayInfo.GetCapabilities() &
+ VRDisplayCapabilityFlags::Cap_StageParameters);
+ default:
+ NS_WARNING(
+ "Unknown XRReferenceSpaceType passed to "
+ "VRDisplayClient::IsReferenceSpaceTypeSupported");
+ return false;
+ }
+}