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