1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "device/vr/openxr/openxr_controller.h"
6 
7 #include <stdint.h>
8 
9 #include "base/check.h"
10 #include "base/notreached.h"
11 #include "base/stl_util.h"
12 #include "device/gamepad/public/cpp/gamepads.h"
13 #include "device/vr/openxr/openxr_util.h"
14 #include "device/vr/util/xr_standard_gamepad_builder.h"
15 #include "ui/gfx/geometry/quaternion.h"
16 #include "ui/gfx/transform_util.h"
17 
18 namespace device {
19 
20 namespace {
21 
GetStringFromType(OpenXrHandednessType type)22 const char* GetStringFromType(OpenXrHandednessType type) {
23   switch (type) {
24     case OpenXrHandednessType::kLeft:
25       return "left";
26     case OpenXrHandednessType::kRight:
27       return "right";
28     case OpenXrHandednessType::kCount:
29       NOTREACHED();
30       return "";
31   }
32 }
33 
GetTopLevelUserPath(OpenXrHandednessType type)34 std::string GetTopLevelUserPath(OpenXrHandednessType type) {
35   return std::string("/user/hand/") + GetStringFromType(type);
36 }
37 
38 }  // namespace
39 
OpenXrController()40 OpenXrController::OpenXrController()
41     : description_(nullptr),
42       type_(OpenXrHandednessType::kCount),  // COUNT refers to invalid.
43       instance_(XR_NULL_HANDLE),
44       session_(XR_NULL_HANDLE),
45       action_set_(XR_NULL_HANDLE),
46       grip_pose_action_{XR_NULL_HANDLE},
47       grip_pose_space_(XR_NULL_HANDLE),
48       pointer_pose_action_(XR_NULL_HANDLE),
49       pointer_pose_space_(XR_NULL_HANDLE),
50       interaction_profile_(OpenXrInteractionProfileType::kCount) {}
51 
~OpenXrController()52 OpenXrController::~OpenXrController() {
53   // We don't need to destroy all of the actions because destroying an
54   // action set destroys all actions that are part of the set.
55 
56   if (action_set_ != XR_NULL_HANDLE) {
57     xrDestroyActionSet(action_set_);
58   }
59   if (grip_pose_space_ != XR_NULL_HANDLE) {
60     xrDestroySpace(grip_pose_space_);
61   }
62   if (pointer_pose_space_ != XR_NULL_HANDLE) {
63     xrDestroySpace(pointer_pose_space_);
64   }
65 }
Initialize(OpenXrHandednessType type,XrInstance instance,XrSession session,const OpenXRPathHelper * path_helper,const OpenXrExtensionHelper & extension_helper,std::map<XrPath,std::vector<XrActionSuggestedBinding>> * bindings)66 XrResult OpenXrController::Initialize(
67     OpenXrHandednessType type,
68     XrInstance instance,
69     XrSession session,
70     const OpenXRPathHelper* path_helper,
71     const OpenXrExtensionHelper& extension_helper,
72     std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) {
73   DCHECK(bindings);
74   type_ = type;
75   instance_ = instance;
76   session_ = session;
77   path_helper_ = path_helper;
78 
79   std::string action_set_name =
80       std::string(GetStringFromType(type_)) + "_action_set";
81 
82   XrActionSetCreateInfo action_set_create_info = {
83       XR_TYPE_ACTION_SET_CREATE_INFO};
84 
85   errno_t error = strcpy_s(action_set_create_info.actionSetName,
86                            base::size(action_set_create_info.actionSetName),
87                            action_set_name.c_str());
88   DCHECK(!error);
89   error = strcpy_s(action_set_create_info.localizedActionSetName,
90                    base::size(action_set_create_info.localizedActionSetName),
91                    action_set_name.c_str());
92   DCHECK(!error);
93 
94   RETURN_IF_XR_FAILED(
95       xrCreateActionSet(instance_, &action_set_create_info, &action_set_));
96 
97   RETURN_IF_XR_FAILED(InitializeControllerActions());
98 
99   SuggestBindings(extension_helper, bindings);
100   RETURN_IF_XR_FAILED(InitializeControllerSpaces());
101 
102   return XR_SUCCESS;
103 }
104 
InitializeControllerActions()105 XrResult OpenXrController::InitializeControllerActions() {
106   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kTrigger));
107   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kSqueeze));
108   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kTrackpad));
109   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kThumbstick));
110   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kThumbrest));
111   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kButton1));
112   RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kButton2));
113 
114   const std::string type_string = GetStringFromType(type_);
115   const std::string name_prefix = type_string + "_controller_";
116   // Axis Actions
117   RETURN_IF_XR_FAILED(
118       CreateAction(XR_ACTION_TYPE_VECTOR2F_INPUT, name_prefix + "trackpad_axis",
119                    &(axis_action_map_[OpenXrAxisType::kTrackpad])));
120   RETURN_IF_XR_FAILED(CreateAction(
121       XR_ACTION_TYPE_VECTOR2F_INPUT, name_prefix + "thumbstick_axis",
122       &(axis_action_map_[OpenXrAxisType::kThumbstick])));
123 
124   // Generic Pose Actions
125   RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_POSE_INPUT,
126                                    name_prefix + "grip_pose",
127                                    &grip_pose_action_));
128   RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_POSE_INPUT,
129                                    name_prefix + "aim_pose",
130                                    &pointer_pose_action_));
131 
132   return XR_SUCCESS;
133 }
134 
SuggestBindings(const OpenXrExtensionHelper & extension_helper,std::map<XrPath,std::vector<XrActionSuggestedBinding>> * bindings) const135 XrResult OpenXrController::SuggestBindings(
136     const OpenXrExtensionHelper& extension_helper,
137     std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) const {
138   const std::string binding_prefix = GetTopLevelUserPath(type_);
139 
140   for (auto interaction_profile : kOpenXrControllerInteractionProfiles) {
141     // If the interaction profile is defined by an extension, check it here,
142     // otherwise continue
143     const bool extension_required =
144         interaction_profile.required_extension != nullptr;
145     if (extension_required) {
146       const bool extension_enabled =
147           extension_helper.ExtensionEnumeration()->ExtensionSupported(
148               interaction_profile.required_extension);
149       if (!extension_enabled) {
150         continue;
151       }
152     }
153 
154     XrPath interaction_profile_path =
155         path_helper_->GetInteractionProfileXrPath(interaction_profile.type);
156     RETURN_IF_XR_FAILED(SuggestActionBinding(
157         bindings, interaction_profile_path, grip_pose_action_,
158         binding_prefix + "/input/grip/pose"));
159     RETURN_IF_XR_FAILED(SuggestActionBinding(
160         bindings, interaction_profile_path, pointer_pose_action_,
161         binding_prefix + "/input/aim/pose"));
162 
163     const OpenXrButtonPathMap* button_maps;
164     size_t button_map_size;
165     switch (type_) {
166       case OpenXrHandednessType::kLeft:
167         button_maps = interaction_profile.left_button_maps;
168         button_map_size = interaction_profile.left_button_map_size;
169         break;
170       case OpenXrHandednessType::kRight:
171         button_maps = interaction_profile.right_button_maps;
172         button_map_size = interaction_profile.right_button_map_size;
173         break;
174       case OpenXrHandednessType::kCount:
175         NOTREACHED() << "Controller can only be left or right";
176         return XR_ERROR_VALIDATION_FAILURE;
177     }
178 
179     for (size_t button_map_index = 0; button_map_index < button_map_size;
180          button_map_index++) {
181       const OpenXrButtonPathMap& cur_button_map = button_maps[button_map_index];
182       OpenXrButtonType button_type = cur_button_map.type;
183       for (size_t action_map_index = 0;
184            action_map_index < cur_button_map.action_map_size;
185            action_map_index++) {
186         const OpenXrButtonActionPathMap& cur_action_map =
187             cur_button_map.action_maps[action_map_index];
188         RETURN_IF_XR_FAILED(SuggestActionBinding(
189             bindings, interaction_profile_path,
190             button_action_map_.at(button_type).at(cur_action_map.type),
191             binding_prefix + cur_action_map.path));
192       }
193     }
194 
195     for (size_t axis_map_index = 0;
196          axis_map_index < interaction_profile.axis_map_size; axis_map_index++) {
197       const OpenXrAxisPathMap& cur_axis_map =
198           interaction_profile.axis_maps[axis_map_index];
199       RETURN_IF_XR_FAILED(
200           SuggestActionBinding(bindings, interaction_profile_path,
201                                axis_action_map_.at(cur_axis_map.type),
202                                binding_prefix + cur_axis_map.path));
203     }
204   }
205 
206   return XR_SUCCESS;
207 }
208 
InitializeControllerSpaces()209 XrResult OpenXrController::InitializeControllerSpaces() {
210   RETURN_IF_XR_FAILED(CreateActionSpace(grip_pose_action_, &grip_pose_space_));
211 
212   RETURN_IF_XR_FAILED(
213       CreateActionSpace(pointer_pose_action_, &pointer_pose_space_));
214 
215   return XR_SUCCESS;
216 }
217 
GetId() const218 uint32_t OpenXrController::GetId() const {
219   return static_cast<uint32_t>(type_);
220 }
221 
GetHandness() const222 device::mojom::XRHandedness OpenXrController::GetHandness() const {
223   switch (type_) {
224     case OpenXrHandednessType::kLeft:
225       return device::mojom::XRHandedness::LEFT;
226     case OpenXrHandednessType::kRight:
227       return device::mojom::XRHandedness::RIGHT;
228     case OpenXrHandednessType::kCount:
229       // LEFT controller and RIGHT controller are currently the only supported
230       // controllers. In the future, other controllers such as sound (which
231       // does not have a handedness) will be added here.
232       NOTREACHED();
233       return device::mojom::XRHandedness::NONE;
234   }
235 }
236 
GetDescription(XrTime predicted_display_time)237 mojom::XRInputSourceDescriptionPtr OpenXrController::GetDescription(
238     XrTime predicted_display_time) {
239   // Description only need to be set once unless interaction profiles changes.
240   if (!description_) {
241     // UpdateInteractionProfile() can not be called inside Initialize() function
242     // because XrGetCurrentInteractionProfile can't be called before
243     // xrSuggestInteractionProfileBindings getting called.
244     if (XR_FAILED(UpdateInteractionProfile())) {
245       return nullptr;
246     }
247     description_ = device::mojom::XRInputSourceDescription::New();
248     description_->handedness = GetHandness();
249     description_->target_ray_mode = device::mojom::XRTargetRayMode::POINTING;
250     description_->profiles =
251         path_helper_->GetInputProfiles(interaction_profile_);
252   }
253 
254   description_->input_from_pointer =
255       GetPointerFromGripTransform(predicted_display_time);
256 
257   return description_.Clone();
258 }
259 
GetButton(OpenXrButtonType type) const260 base::Optional<GamepadButton> OpenXrController::GetButton(
261     OpenXrButtonType type) const {
262   GamepadButton ret;
263   // Button should at least have one of the three actions;
264   bool has_value = false;
265 
266   DCHECK(button_action_map_.count(type) == 1);
267   auto button = button_action_map_.at(type);
268   XrActionStateBoolean press_state_bool = {XR_TYPE_ACTION_STATE_BOOLEAN};
269   if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kPress],
270                               &press_state_bool)) &&
271       press_state_bool.isActive) {
272     ret.pressed = press_state_bool.currentState;
273     has_value = true;
274   } else {
275     ret.pressed = false;
276   }
277 
278   XrActionStateBoolean touch_state_bool = {XR_TYPE_ACTION_STATE_BOOLEAN};
279   if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kTouch],
280                               &touch_state_bool)) &&
281       touch_state_bool.isActive) {
282     ret.touched = touch_state_bool.currentState;
283     has_value = true;
284   } else {
285     ret.touched = ret.pressed;
286   }
287 
288   XrActionStateFloat value_state_float = {XR_TYPE_ACTION_STATE_FLOAT};
289   if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kValue],
290                               &value_state_float)) &&
291       value_state_float.isActive) {
292     ret.value = value_state_float.currentState;
293     has_value = true;
294   } else {
295     ret.value = ret.pressed ? 1.0 : 0.0;
296   }
297 
298   if (!has_value) {
299     return base::nullopt;
300   }
301 
302   return ret;
303 }
304 
GetAxis(OpenXrAxisType type) const305 std::vector<double> OpenXrController::GetAxis(OpenXrAxisType type) const {
306   XrActionStateVector2f axis_state_v2f = {XR_TYPE_ACTION_STATE_VECTOR2F};
307   if (XR_FAILED(QueryState(axis_action_map_.at(type), &axis_state_v2f)) ||
308       !axis_state_v2f.isActive) {
309     return {};
310   }
311 
312   return {axis_state_v2f.currentState.x, axis_state_v2f.currentState.y};
313 }
314 
UpdateInteractionProfile()315 XrResult OpenXrController::UpdateInteractionProfile() {
316   XrPath top_level_user_path;
317 
318   std::string top_level_user_path_string = GetTopLevelUserPath(type_);
319   RETURN_IF_XR_FAILED(xrStringToPath(
320       instance_, top_level_user_path_string.c_str(), &top_level_user_path));
321 
322   XrInteractionProfileState interaction_profile_state = {
323       XR_TYPE_INTERACTION_PROFILE_STATE};
324   RETURN_IF_XR_FAILED(xrGetCurrentInteractionProfile(
325       session_, top_level_user_path, &interaction_profile_state));
326   interaction_profile_ = path_helper_->GetInputProfileType(
327       interaction_profile_state.interactionProfile);
328 
329   if (description_) {
330     // TODO(crbug.com/1006072):
331     // Query USB vendor and product ID From OpenXR.
332     description_->profiles =
333         path_helper_->GetInputProfiles(interaction_profile_);
334   }
335   return XR_SUCCESS;
336 }
337 
GetMojoFromGripTransform(XrTime predicted_display_time,XrSpace local_space,bool * emulated_position) const338 base::Optional<gfx::Transform> OpenXrController::GetMojoFromGripTransform(
339     XrTime predicted_display_time,
340     XrSpace local_space,
341     bool* emulated_position) const {
342   return GetTransformFromSpaces(predicted_display_time, grip_pose_space_,
343                                 local_space, emulated_position);
344 }
345 
GetPointerFromGripTransform(XrTime predicted_display_time) const346 base::Optional<gfx::Transform> OpenXrController::GetPointerFromGripTransform(
347     XrTime predicted_display_time) const {
348   bool emulated_position;
349   return GetTransformFromSpaces(predicted_display_time, pointer_pose_space_,
350                                 grip_pose_space_, &emulated_position);
351 }
352 
GetTransformFromSpaces(XrTime predicted_display_time,XrSpace target,XrSpace origin,bool * emulated_position) const353 base::Optional<gfx::Transform> OpenXrController::GetTransformFromSpaces(
354     XrTime predicted_display_time,
355     XrSpace target,
356     XrSpace origin,
357     bool* emulated_position) const {
358   XrSpaceLocation location = {XR_TYPE_SPACE_LOCATION};
359   // emulated_position indicates when there is a fallback from a fully-tracked
360   // (i.e. 6DOF) type case to some form of orientation-only type tracking
361   // (i.e. 3DOF/IMU type sensors)
362   // Thus we have to make sure orientation is tracked.
363   // Valid Bit only indicates it's either tracked or emulated, we have to check
364   // for XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT to make sure orientation is
365   // tracked.
366   if (FAILED(
367           xrLocateSpace(target, origin, predicted_display_time, &location)) ||
368       !(location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) ||
369       !(location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)) {
370     return base::nullopt;
371   }
372 
373   // Convert the orientation and translation given by runtime into a
374   // transformation matrix.
375   gfx::DecomposedTransform decomp;
376   decomp.quaternion =
377       gfx::Quaternion(location.pose.orientation.x, location.pose.orientation.y,
378                       location.pose.orientation.z, location.pose.orientation.w);
379   decomp.translate[0] = location.pose.position.x;
380   decomp.translate[1] = location.pose.position.y;
381   decomp.translate[2] = location.pose.position.z;
382 
383   *emulated_position = true;
384   if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
385     *emulated_position = false;
386   }
387 
388   return gfx::ComposeTransform(decomp);
389 }
390 
CreateActionsForButton(OpenXrButtonType button_type)391 XrResult OpenXrController::CreateActionsForButton(
392     OpenXrButtonType button_type) {
393   const std::string type_string = GetStringFromType(type_);
394   std::string name_prefix = type_string + "_controller_";
395 
396   switch (button_type) {
397     case OpenXrButtonType::kTrigger:
398       name_prefix += "trigger_";
399       break;
400     case OpenXrButtonType::kSqueeze:
401       name_prefix += "squeeze_";
402       break;
403     case OpenXrButtonType::kTrackpad:
404       name_prefix += "trackpad_";
405       break;
406     case OpenXrButtonType::kThumbstick:
407       name_prefix += "thumbstick_";
408       break;
409     case OpenXrButtonType::kThumbrest:
410       name_prefix += "thumbrest_";
411       break;
412     case OpenXrButtonType::kButton1:
413       name_prefix += "upper_button_";
414       break;
415     case OpenXrButtonType::kButton2:
416       name_prefix += "lower_button_";
417       break;
418   }
419 
420   std::unordered_map<OpenXrButtonActionType, XrAction>& cur_button =
421       button_action_map_[button_type];
422   XrAction new_action;
423   RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_BOOLEAN_INPUT,
424                                    name_prefix + "button_press", &new_action));
425   cur_button[OpenXrButtonActionType::kPress] = new_action;
426   RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_FLOAT_INPUT,
427                                    name_prefix + "button_value", &new_action));
428   cur_button[OpenXrButtonActionType::kValue] = new_action;
429   RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_BOOLEAN_INPUT,
430                                    name_prefix + "button_touch", &new_action));
431   cur_button[OpenXrButtonActionType::kTouch] = new_action;
432   return XR_SUCCESS;
433 }
434 
CreateAction(XrActionType type,const std::string & action_name,XrAction * action)435 XrResult OpenXrController::CreateAction(XrActionType type,
436                                         const std::string& action_name,
437                                         XrAction* action) {
438   DCHECK(action);
439   XrActionCreateInfo action_create_info = {XR_TYPE_ACTION_CREATE_INFO};
440   action_create_info.actionType = type;
441 
442   errno_t error =
443       strcpy_s(action_create_info.actionName,
444                base::size(action_create_info.actionName), action_name.data());
445   DCHECK(error == 0);
446   error = strcpy_s(action_create_info.localizedActionName,
447                    base::size(action_create_info.localizedActionName),
448                    action_name.data());
449   DCHECK(error == 0);
450   return xrCreateAction(action_set_, &action_create_info, action);
451 }
452 
SuggestActionBinding(std::map<XrPath,std::vector<XrActionSuggestedBinding>> * bindings,XrPath interaction_profile_path,XrAction action,std::string binding_string) const453 XrResult OpenXrController::SuggestActionBinding(
454     std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings,
455     XrPath interaction_profile_path,
456     XrAction action,
457     std::string binding_string) const {
458   XrPath binding_path;
459   // make sure all actions we try to suggest binding are initialized.
460   DCHECK(action != XR_NULL_HANDLE);
461   RETURN_IF_XR_FAILED(
462       xrStringToPath(instance_, binding_string.c_str(), &binding_path));
463   (*bindings)[interaction_profile_path].push_back({action, binding_path});
464 
465   return XR_SUCCESS;
466 }
467 
CreateActionSpace(XrAction action,XrSpace * space)468 XrResult OpenXrController::CreateActionSpace(XrAction action, XrSpace* space) {
469   DCHECK(space);
470   XrActionSpaceCreateInfo action_space_create_info = {};
471   action_space_create_info.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
472   action_space_create_info.action = action;
473   action_space_create_info.subactionPath = XR_NULL_PATH;
474   action_space_create_info.poseInActionSpace = PoseIdentity();
475   return xrCreateActionSpace(session_, &action_space_create_info, space);
476 }
477 
478 }  // namespace device
479