diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn index cfb9131388b10..849863272893f 100644 --- a/chrome/browser/vr/BUILD.gn +++ b/chrome/browser/vr/BUILD.gn @@ -656,6 +656,7 @@ if (!is_android) { ":vr_common", "//components/content_settings/core/browser", "//device/vr:directx_helpers", + "//device/vr:openxr_data", ] if (enable_openxr) { diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc index d9675131cbc05..2e63ec2dc51be 100644 --- a/chrome/browser/vr/webxr_vr_input_browser_test.cc +++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc @@ -7,6 +7,7 @@ #include "chrome/browser/vr/test/mock_xr_device_hook_base.h" #include "chrome/browser/vr/test/multi_class_browser_test.h" #include "chrome/browser/vr/test/webxr_vr_browser_test.h" +#include "device/vr/openxr/openxr_interaction_profiles.h" #include "device/vr/public/mojom/browser_test_interfaces.mojom.h" // Browser test equivalent of @@ -207,6 +208,14 @@ class WebXrControllerInputMock : public MockXRDeviceHookBase { UpdateControllerAndWait(controller_index, controller_data); } + void UpdateInteractionProfile( + device_test::mojom::InteractionProfileType new_profile) { + device_test::mojom::EventData data = {}; + data.type = device_test::mojom::EventType::kInteractionProfileChanged; + data.interaction_profile = new_profile; + PopulateEvent(std::move(data)); + } + // A controller is necessary to simulate voice input because of how the test // API works. unsigned int CreateVoiceController() { @@ -565,11 +574,9 @@ WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadCompleteData) { t->EndTest(); } -#if BUILDFLAG(ENABLE_OPENXR) -// Ensure that if OpenXR Runtime receive interaction profile chagnes event, +// Ensure that if OpenXR Runtime receive interaction profile changes event, // input profile name will be changed accordingly. -IN_PROC_BROWSER_TEST_F(WebXrVrOpenXrBrowserTest, - TestInteractionProfileChanged) { +WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInteractionProfileChanged) { WebXrControllerInputMock my_mock; // Create a controller that supports all reserved buttons. @@ -589,33 +596,102 @@ IN_PROC_BROWSER_TEST_F(WebXrVrOpenXrBrowserTest, device::ControllerRole::kControllerRoleRight, axis_types, supported_buttons); - this->LoadFileAndAwaitInitialization("test_webxr_input_same_object"); - this->EnterSessionWithUserGestureOrFail(); + t->LoadFileAndAwaitInitialization("test_webxr_input_same_object"); + t->EnterSessionWithUserGestureOrFail(); // We should only have seen the first change indicating we have input sources. - PollJavaScriptBooleanOrFail("inputChangeEvents === 1", kPollTimeoutShort); + t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1", + WebXrVrBrowserTestBase::kPollTimeoutShort); // We only expect one input source, cache it. - this->RunJavaScriptOrFail("validateInputSourceLength(1)"); - this->RunJavaScriptOrFail("updateCachedInputSource(0)"); + t->RunJavaScriptOrFail("validateInputSourceLength(1)"); + t->RunJavaScriptOrFail("updateCachedInputSource(0)"); - // Simulate Runtimes Sends change interaction profile event to change from - // Windows motion controller to Khronos simple Controller. - device_test::mojom::EventData data = {}; - data.type = device_test::mojom::EventType::kInteractionProfileChanged; - data.interaction_profile = - device_test::mojom::InteractionProfileType::kKHRSimple; - my_mock.PopulateEvent(data); + // Simulate the runtime sending an interaction profile change event to change + // from Windows motion controller to Khronos simple Controller. + my_mock.UpdateInteractionProfile( + device_test::mojom::InteractionProfileType::kKHRSimple); + // Make sure change events happens again since interaction profile changed + t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2", + WebXrVrBrowserTestBase::kPollTimeoutShort); + t->RunJavaScriptOrFail("validateInputSourceLength(1)"); + t->RunJavaScriptOrFail("validateCachedSourcePresence(false)"); - // Make sure change events happens again since interaction profile changedd - PollJavaScriptBooleanOrFail("inputChangeEvents === 2", kPollTimeoutShort); - this->RunJavaScriptOrFail("validateInputSourceLength(1)"); - this->RunJavaScriptOrFail("validateCachedSourcePresence(false)"); - - this->RunJavaScriptOrFail("done()"); - this->EndTest(); + t->RunJavaScriptOrFail("done()"); + t->EndTest(); +} + +// We explicitly translate between the two types because this ensures that we +// add a corresponding mojom InteractionProfileType whenever we add a new OpenXr +// Interaction Profile. Since the mojom type is only needed for tests, we can't +// just use only the mojom type, and because the mojom type may be used for +// other runtimes, we can't just typemap it. +device_test::mojom::InteractionProfileType GetMojomInteractionProfile( + device::OpenXrInteractionProfileType profile) { + switch (profile) { + case device::OpenXrInteractionProfileType::kMicrosoftMotion: + return device_test::mojom::InteractionProfileType::kWMRMotion; + case device::OpenXrInteractionProfileType::kKHRSimple: + return device_test::mojom::InteractionProfileType::kKHRSimple; + case device::OpenXrInteractionProfileType::kOculusTouch: + return device_test::mojom::InteractionProfileType::kOculusTouch; + case device::OpenXrInteractionProfileType::kValveIndex: + return device_test::mojom::InteractionProfileType::kValveIndex; + case device::OpenXrInteractionProfileType::kHTCVive: + return device_test::mojom::InteractionProfileType::kHTCVive; + case device::OpenXrInteractionProfileType::kSamsungOdyssey: + return device_test::mojom::InteractionProfileType::kSamsungOdyssey; + case device::OpenXrInteractionProfileType::kHPReverbG2: + return device_test::mojom::InteractionProfileType::kHPReverbG2; + case device::OpenXrInteractionProfileType::kHandSelectGrasp: + return device_test::mojom::InteractionProfileType::kHandSelectGrasp; + case device::OpenXrInteractionProfileType::kCount: + return device_test::mojom::InteractionProfileType::kInvalid; + } +} + +// Ensure that OpenXR can change between all known Interaction Profile types. +// If you're adding a new interaction profile, you may need to validate that +// openxr_test_helper has any required extensions listed as supported in it's +// header and that it knows about all of the buttons/input types that you're +// adding with the new interaction profile. +WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestAllKnownInteractionProfileTypes) { + WebXrControllerInputMock my_mock; + + // Explicitly set us to the first interaction profile before we start the + // session. + my_mock.UpdateInteractionProfile(GetMojomInteractionProfile( + static_cast<device::OpenXrInteractionProfileType>(0))); + auto controller_data = my_mock.CreateValidController( + device::ControllerRole::kControllerRoleRight); + my_mock.ConnectController(controller_data); + + t->LoadFileAndAwaitInitialization("test_webxr_input_sources_change_event"); + t->EnterSessionWithUserGestureOrFail(); + + // We should only have seen the first change indicating we have input sources. + uint32_t expected_change_events = 1; + t->PollJavaScriptBooleanOrFail( + "inputChangeEvents === " + base::NumberToString(expected_change_events), + WebXrVrBrowserTestBase::kPollTimeoutShort); + + // Note that since we explicitly set ourselves to the 0th value above, we want + // to start changing to the first item in the enum. + static uint32_t kFinalValue = + static_cast<uint32_t>(device::OpenXrInteractionProfileType::kCount); + for (uint32_t i = 1; i < kFinalValue; i++) { + my_mock.UpdateInteractionProfile(GetMojomInteractionProfile( + static_cast<device::OpenXrInteractionProfileType>(i))); + expected_change_events++; + // Make sure change events happens again since interaction profile changed + t->PollJavaScriptBooleanOrFail( + "inputChangeEvents === " + base::NumberToString(expected_change_events), + WebXrVrBrowserTestBase::kPollTimeoutShort); + } + + t->RunJavaScriptOrFail("done()"); + t->EndTest(); } -#endif // BUILDFLAG(ENABLE_OPENXR) // Test that controller input is registered via WebXR's input method. This uses // multiple controllers to make sure the input is going to the correct one. diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn index 0d4c9e5ec7682..84382c84ff61b 100644 --- a/device/vr/BUILD.gn +++ b/device/vr/BUILD.gn @@ -101,6 +101,21 @@ if (enable_vr) { ] } + if (enable_openxr) { + source_set("openxr_data") { + sources = [ + "openxr/openxr_defs.h", + "openxr/openxr_interaction_profiles.h", + ] + + deps = [ + "//base", + "//device/gamepad/public/cpp:shared_with_blink", + "//third_party/openxr:openxr_headers", + ] + } + } + # TODO(alcooper): Ultimately, this component should be either deleted entirely # or used as a helper component aggregating all XR Runtimes that are in use. # Each XR Device should be split into its own component. @@ -180,14 +195,12 @@ if (enable_vr) { "openxr/openxr_api_wrapper.h", "openxr/openxr_controller.cc", "openxr/openxr_controller.h", - "openxr/openxr_defs.h", "openxr/openxr_device.cc", "openxr/openxr_device.h", "openxr/openxr_extension_helper.cc", "openxr/openxr_extension_helper.h", "openxr/openxr_input_helper.cc", "openxr/openxr_input_helper.h", - "openxr/openxr_interaction_profiles.h", "openxr/openxr_path_helper.cc", "openxr/openxr_path_helper.h", "openxr/openxr_render_loop.cc", @@ -202,6 +215,7 @@ if (enable_vr) { ] deps += [ + ":openxr_data", "//components/version_info", "//third_party/openxr", ] @@ -282,7 +296,6 @@ if (enable_openxr) { include_dirs = [ "//third_party/openxr/src/include" ] sources = [ - "openxr/openxr_defs.h", "openxr/openxr_extension_helper.cc", "openxr/openxr_extension_helper.h", "openxr/openxr_util.cc", @@ -307,6 +320,8 @@ if (enable_openxr) { "//third_party/openxr:openxr_headers", ] + public_deps = [ ":openxr_data" ] + data_deps = [ "//device/vr:json_mock" ] } } diff --git a/device/vr/openxr/openxr_defs.h b/device/vr/openxr/openxr_defs.h index e887af419c8e7..a940b3b672dcc 100644 --- a/device/vr/openxr/openxr_defs.h +++ b/device/vr/openxr/openxr_defs.h @@ -14,4 +14,4 @@ constexpr char kMSFTHandInteractionExtensionName[] = "XR_MSFT_hand_interaction"; } // namespace device -#endif // DEVICE_VR_OPENXR_OPENXR_DEFS_H_ \ No newline at end of file +#endif // DEVICE_VR_OPENXR_OPENXR_DEFS_H_ diff --git a/device/vr/openxr/openxr_interaction_profiles.h b/device/vr/openxr/openxr_interaction_profiles.h index f2d301750a3d5..3bfe717413590 100644 --- a/device/vr/openxr/openxr_interaction_profiles.h +++ b/device/vr/openxr/openxr_interaction_profiles.h @@ -33,6 +33,23 @@ enum class OpenXrInteractionProfileType { kCount = 8, }; +const char kMicrosoftMotionInteractionProfilePath[] = + "/interaction_profiles/microsoft/motion_controller"; +const char kKHRSimpleInteractionProfilePath[] = + "/interaction_profiles/khr/simple_controller"; +const char kOculusTouchInteractionProfilePath[] = + "/interaction_profiles/oculus/touch_controller"; +const char kValveIndexInteractionProfilePath[] = + "/interaction_profiles/valve/index_controller"; +const char kHTCViveInteractionProfilePath[] = + "/interaction_profiles/htc/vive_controller"; +const char kSamsungOdysseyInteractionProfilePath[] = + "/interaction_profiles/samsung/odyssey_controller"; +const char kHPReverbG2InteractionProfilePath[] = + "/interaction_profiles/hp/mixed_reality_controller"; +const char kHandSelectGraspInteractionProfilePath[] = + "/interaction_profiles/microsoft/hand_interaction"; + enum class OpenXrButtonType { kTrigger = 0, kSqueeze = 1, @@ -456,7 +473,7 @@ constexpr OpenXrAxisPathMap kHPReverbG2ControllerAxisPathMaps[] = { constexpr OpenXrControllerInteractionProfile kMicrosoftMotionInteractionProfile = { OpenXrInteractionProfileType::kMicrosoftMotion, - "/interaction_profiles/microsoft/motion_controller", + kMicrosoftMotionInteractionProfilePath, nullptr, GamepadMapping::kXrStandard, kMicrosoftMotionInputProfiles, @@ -470,7 +487,7 @@ constexpr OpenXrControllerInteractionProfile constexpr OpenXrControllerInteractionProfile kKHRSimpleInteractionProfile = { OpenXrInteractionProfileType::kKHRSimple, - "/interaction_profiles/khr/simple_controller", + kKHRSimpleInteractionProfilePath, nullptr, GamepadMapping::kNone, kGenericButtonInputProfiles, @@ -484,7 +501,7 @@ constexpr OpenXrControllerInteractionProfile kKHRSimpleInteractionProfile = { constexpr OpenXrControllerInteractionProfile kOculusTouchInteractionProfile = { OpenXrInteractionProfileType::kOculusTouch, - "/interaction_profiles/oculus/touch_controller", + kOculusTouchInteractionProfilePath, nullptr, GamepadMapping::kXrStandard, kOculusTouchInputProfiles, @@ -498,7 +515,7 @@ constexpr OpenXrControllerInteractionProfile kOculusTouchInteractionProfile = { constexpr OpenXrControllerInteractionProfile kValveIndexInteractionProfile = { OpenXrInteractionProfileType::kValveIndex, - "/interaction_profiles/valve/index_controller", + kValveIndexInteractionProfilePath, nullptr, GamepadMapping::kXrStandard, kValveIndexInputProfiles, @@ -512,7 +529,7 @@ constexpr OpenXrControllerInteractionProfile kValveIndexInteractionProfile = { constexpr OpenXrControllerInteractionProfile kHTCViveInteractionProfile = { OpenXrInteractionProfileType::kHTCVive, - "/interaction_profiles/htc/vive_controller", + kHTCViveInteractionProfilePath, nullptr, GamepadMapping::kXrStandard, kHTCViveInputProfiles, @@ -526,7 +543,7 @@ constexpr OpenXrControllerInteractionProfile kHTCViveInteractionProfile = { constexpr OpenXrControllerInteractionProfile kSamsungOdysseyInteractionProfile = {OpenXrInteractionProfileType::kSamsungOdyssey, - "/interaction_profiles/samsung/odyssey_controller", + kSamsungOdysseyInteractionProfilePath, kExtSamsungOdysseyControllerExtensionName, GamepadMapping::kXrStandard, kSamsungOdysseyInputProfiles, @@ -540,7 +557,7 @@ constexpr OpenXrControllerInteractionProfile kSamsungOdysseyInteractionProfile = constexpr OpenXrControllerInteractionProfile kHPReverbG2InteractionProfile = { OpenXrInteractionProfileType::kHPReverbG2, - "/interaction_profiles/hp/mixed_reality_controller", + kHPReverbG2InteractionProfilePath, kExtHPMixedRealityControllerExtensionName, GamepadMapping::kXrStandard, kHPReverbG2InputProfiles, @@ -555,7 +572,7 @@ constexpr OpenXrControllerInteractionProfile kHPReverbG2InteractionProfile = { constexpr OpenXrControllerInteractionProfile kHandInteractionMSFTInteractionProfile = { OpenXrInteractionProfileType::kHandSelectGrasp, - "/interaction_profiles/microsoft/hand_interaction", + kHandSelectGraspInteractionProfilePath, kMSFTHandInteractionExtensionName, GamepadMapping::kXrStandard, kGenericHandSelectGraspInputProfile, diff --git a/device/vr/openxr/test/openxr_test_helper.cc b/device/vr/openxr/test/openxr_test_helper.cc index cb0c8a8690e49..203633ae4675b 100644 --- a/device/vr/openxr/test/openxr_test_helper.cc +++ b/device/vr/openxr/test/openxr_test_helper.cc @@ -7,6 +7,7 @@ #include <cmath> #include <limits> +#include "device/vr/openxr/openxr_interaction_profiles.h" #include "device/vr/openxr/openxr_util.h" #include "third_party/openxr/src/include/openxr/openxr_platform.h" #include "third_party/openxr/src/src/common/hex_and_handles.h" @@ -43,8 +44,7 @@ OpenXrTestHelper::OpenXrTestHelper() acquired_swapchain_texture_(0), next_space_(0), next_predicted_display_time_(0), - interaction_profile_( - interaction_profile::kMicrosoftMotionControllerInteractionProfile) {} + interaction_profile_(device::kMicrosoftMotionInteractionProfilePath) {} OpenXrTestHelper::~OpenXrTestHelper() = default; @@ -491,8 +491,12 @@ XrResult OpenXrTestHelper::UpdateAction(XrAction action) { switch (cur_action_properties.type) { case XR_ACTION_TYPE_FLOAT_INPUT: { - if (!PathContainsString(path_string, "/trigger")) { - NOTREACHED() << "Only trigger button has float action"; + if (!(PathContainsString(path_string, "/trigger") || + PathContainsString(path_string, "/squeeze") || + PathContainsString(path_string, "/force") || + PathContainsString(path_string, "/value"))) { + NOTREACHED() << "Found path with unsupported float action: " + << path_string; } float_action_states_[action].isActive = data.is_valid; break; @@ -512,8 +516,18 @@ XrResult OpenXrTestHelper::UpdateAction(XrAction action) { } else if (PathContainsString(path_string, "/select/")) { // for WMR simple controller select is mapped to test type trigger button_id = device::kAxisTrigger; + } else if (PathContainsString(path_string, "/thumbrest/")) { + button_id = device::kThumbRest; + } else if (PathContainsString(path_string, "/a/")) { + button_id = device::kA; + } else if (PathContainsString(path_string, "/b/")) { + button_id = device::kB; + } else if (PathContainsString(path_string, "/x/")) { + button_id = device::kX; + } else if (PathContainsString(path_string, "/y/")) { + button_id = device::kY; } else { - NOTREACHED() << "Curently test does not support this button"; + NOTREACHED() << "Unrecognized boolean button: " << path_string; } uint64_t button_mask = XrButtonMaskFromId(button_id); @@ -691,12 +705,28 @@ void OpenXrTestHelper::UpdateInteractionProfile( device_test::mojom::InteractionProfileType type) { switch (type) { case device_test::mojom::InteractionProfileType::kWMRMotion: - interaction_profile_ = - interaction_profile::kMicrosoftMotionControllerInteractionProfile; + interaction_profile_ = device::kMicrosoftMotionInteractionProfilePath; break; case device_test::mojom::InteractionProfileType::kKHRSimple: - interaction_profile_ = - interaction_profile::kKHRSimpleControllerInteractionProfile; + interaction_profile_ = device::kKHRSimpleInteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kOculusTouch: + interaction_profile_ = device::kOculusTouchInteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kValveIndex: + interaction_profile_ = device::kValveIndexInteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kHTCVive: + interaction_profile_ = device::kHTCViveInteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kSamsungOdyssey: + interaction_profile_ = device::kSamsungOdysseyInteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kHPReverbG2: + interaction_profile_ = device::kHPReverbG2InteractionProfilePath; + break; + case device_test::mojom::InteractionProfileType::kHandSelectGrasp: + interaction_profile_ = device::kHandSelectGraspInteractionProfilePath; break; case device_test::mojom::InteractionProfileType::kInvalid: NOTREACHED() << "Invalid EventData interaction_profile type"; diff --git a/device/vr/openxr/test/openxr_test_helper.h b/device/vr/openxr/test/openxr_test_helper.h index fc51a9ed612e0..69078097cbfc3 100644 --- a/device/vr/openxr/test/openxr_test_helper.h +++ b/device/vr/openxr/test/openxr_test_helper.h @@ -16,6 +16,7 @@ #include "base/stl_util.h" #include "base/synchronization/lock.h" +#include "device/vr/openxr/openxr_defs.h" #include "device/vr/test/test_hook.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/openxr/src/include/openxr/openxr.h" @@ -25,18 +26,6 @@ namespace gfx { class Transform; } // namespace gfx -namespace interaction_profile { -constexpr char kMicrosoftMotionControllerInteractionProfile[] = - "/interaction_profiles/microsoft/motion_controller"; - -constexpr char kKHRSimpleControllerInteractionProfile[] = - "/interaction_profiles/khr/simple_controller"; - -constexpr char kOculusTouchControllerInteractionProfile[] = - "/interaction_profiles/oculus/touch_controller"; - -} // namespace interaction_profile - class OpenXrTestHelper : public device::ServiceTestHook { public: OpenXrTestHelper(); @@ -136,7 +125,11 @@ class OpenXrTestHelper : public device::ServiceTestHook { // Properties of the mock OpenXR runtime that do not change are created static constexpr const char* const kExtensions[] = { XR_KHR_D3D11_ENABLE_EXTENSION_NAME, - XR_EXT_WIN32_APPCONTAINER_COMPATIBLE_EXTENSION_NAME}; + XR_EXT_WIN32_APPCONTAINER_COMPATIBLE_EXTENSION_NAME, + device::kExtSamsungOdysseyControllerExtensionName, + device::kExtHPMixedRealityControllerExtensionName, + device::kMSFTHandInteractionExtensionName, + }; static constexpr uint32_t kDimension = 128; static constexpr uint32_t kSwapCount = 1; static constexpr uint32_t kMinSwapchainBuffering = 3; diff --git a/device/vr/public/mojom/browser_test_interfaces.mojom b/device/vr/public/mojom/browser_test_interfaces.mojom index 8cb27219d91cc..fe268ef6bf10c 100644 --- a/device/vr/public/mojom/browser_test_interfaces.mojom +++ b/device/vr/public/mojom/browser_test_interfaces.mojom @@ -90,12 +90,18 @@ enum EventType { }; // InteractionProfileType is used to indicate which interaction profile runtime -// would like to change to +// would like to change to. enum InteractionProfileType { // Windows Mixed Reality Motion Controller - kWMRMotion, + kWMRMotion = 0, // Khronos Simple Controller kKHRSimple, + kOculusTouch, + kValveIndex, + kHTCVive, + kSamsungOdyssey, + kHPReverbG2, + kHandSelectGrasp, kInvalid, }; diff --git a/device/vr/test/test_hook.h b/device/vr/test/test_hook.h index 0d51c4a921157..6cad031cff1ef 100644 --- a/device/vr/test/test_hook.h +++ b/device/vr/test/test_hook.h @@ -28,6 +28,10 @@ enum XrButtonId { kDpadRight = 5, kDpadDown = 6, kA = 7, + kB = 8, + kX = 9, + kY = 10, + kThumbRest = 11, kProximitySensor = 31, kAxisTrackpad = 32, kAxisTrigger = 33,