The open source OpenXR runtime
at main 1501 lines 51 kB view raw
1// Copyright 2023, Shawn Wallace 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief SteamVR driver device implementation. 6 * @author Shawn Wallace <yungwallace@live.com> 7 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com> 8 * @ingroup drv_steamvr_lh 9 */ 10 11#include "math/m_api.h" 12#include "math/m_predict.h" 13#include "math/m_relation_history.h" 14#include "math/m_space.h" 15 16#include "interfaces/context.hpp" 17 18#include "util/u_debug.h" 19#include "util/u_device.h" 20#include "util/u_hand_tracking.h" 21#include "util/u_logging.h" 22#include "util/u_json.hpp" 23 24#include "util/u_time.h" 25#include "xrt/xrt_defines.h" 26#include "xrt/xrt_device.h" 27#include "xrt/xrt_prober.h" 28 29#include "vive/vive_poses.h" 30 31#include "openvr_driver.h" 32 33#include "device.hpp" 34 35#include <cmath> 36#include <functional> 37#include <cstring> 38#include <numbers> 39#include <openvr_driver.h> 40#include <thread> 41#include <algorithm> 42#include <map> 43 44#define DEV_ERR(...) U_LOG_IFL_E(ctx->log_level, __VA_ARGS__) 45#define DEV_WARN(...) U_LOG_IFL_W(ctx->log_level, __VA_ARGS__) 46#define DEV_INFO(...) U_LOG_IFL_I(ctx->log_level, __VA_ARGS__) 47#define DEV_DEBUG(...) U_LOG_IFL_D(ctx->log_level, __VA_ARGS__) 48 49DEBUG_GET_ONCE_BOOL_OPTION(lh_emulate_hand, "LH_EMULATE_HAND", true) 50 51// Each device will have its own input class. 52struct InputClass 53{ 54 xrt_device_name name; 55 const std::vector<xrt_input_name> poses; 56 const std::unordered_map<std::string_view, xrt_input_name> non_poses; 57}; 58 59namespace { 60using namespace std::string_view_literals; 61// From https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md#bone-structure 62enum HandSkeletonBone : int32_t 63{ 64 eBone_Root = 0, 65 eBone_Wrist, 66 eBone_Thumb0, 67 eBone_Thumb1, 68 eBone_Thumb2, 69 eBone_Thumb3, 70 eBone_IndexFinger0, 71 eBone_IndexFinger1, 72 eBone_IndexFinger2, 73 eBone_IndexFinger3, 74 eBone_IndexFinger4, 75 eBone_MiddleFinger0, 76 eBone_MiddleFinger1, 77 eBone_MiddleFinger2, 78 eBone_MiddleFinger3, 79 eBone_MiddleFinger4, 80 eBone_RingFinger0, 81 eBone_RingFinger1, 82 eBone_RingFinger2, 83 eBone_RingFinger3, 84 eBone_RingFinger4, 85 eBone_PinkyFinger0, 86 eBone_PinkyFinger1, 87 eBone_PinkyFinger2, 88 eBone_PinkyFinger3, 89 eBone_PinkyFinger4, 90 eBone_Aux_Thumb, 91 eBone_Aux_IndexFinger, 92 eBone_Aux_MiddleFinger, 93 eBone_Aux_RingFinger, 94 eBone_Aux_PinkyFinger, 95 eBone_Count 96}; 97 98// Adding support for a new controller is a simple as adding it here. 99// The key for the map needs to be the name of input profile as indicated by the lighthouse driver. 100const std::unordered_map<std::string_view, InputClass> controller_classes{ 101 { 102 "vive_controller", 103 InputClass{ 104 XRT_DEVICE_VIVE_WAND, 105 { 106 XRT_INPUT_VIVE_GRIP_POSE, 107 XRT_INPUT_VIVE_AIM_POSE, 108 XRT_INPUT_GENERIC_PALM_POSE, 109 }, 110 { 111 {"/input/application_menu/click", XRT_INPUT_VIVE_MENU_CLICK}, 112 {"/input/trackpad/click", XRT_INPUT_VIVE_TRACKPAD_CLICK}, 113 {"/input/trackpad/touch", XRT_INPUT_VIVE_TRACKPAD_TOUCH}, 114 {"/input/system/click", XRT_INPUT_VIVE_SYSTEM_CLICK}, 115 {"/input/trigger/click", XRT_INPUT_VIVE_TRIGGER_CLICK}, 116 {"/input/trigger/value", XRT_INPUT_VIVE_TRIGGER_VALUE}, 117 {"/input/grip/click", XRT_INPUT_VIVE_SQUEEZE_CLICK}, 118 {"/input/trackpad", XRT_INPUT_VIVE_TRACKPAD}, 119 }, 120 }, 121 }, 122 { 123 "index_controller", 124 InputClass{ 125 XRT_DEVICE_INDEX_CONTROLLER, 126 { 127 XRT_INPUT_INDEX_GRIP_POSE, 128 XRT_INPUT_INDEX_AIM_POSE, 129 XRT_INPUT_GENERIC_PALM_POSE, 130 }, 131 { 132 {"/input/system/click", XRT_INPUT_INDEX_SYSTEM_CLICK}, 133 {"/input/system/touch", XRT_INPUT_INDEX_SYSTEM_TOUCH}, 134 {"/input/a/click", XRT_INPUT_INDEX_A_CLICK}, 135 {"/input/a/touch", XRT_INPUT_INDEX_A_TOUCH}, 136 {"/input/b/click", XRT_INPUT_INDEX_B_CLICK}, 137 {"/input/b/touch", XRT_INPUT_INDEX_B_TOUCH}, 138 {"/input/trigger/click", XRT_INPUT_INDEX_TRIGGER_CLICK}, 139 {"/input/trigger/touch", XRT_INPUT_INDEX_TRIGGER_TOUCH}, 140 {"/input/trigger/value", XRT_INPUT_INDEX_TRIGGER_VALUE}, 141 {"/input/grip/force", XRT_INPUT_INDEX_SQUEEZE_FORCE}, 142 {"/input/grip/value", XRT_INPUT_INDEX_SQUEEZE_VALUE}, 143 {"/input/thumbstick/click", XRT_INPUT_INDEX_THUMBSTICK_CLICK}, 144 {"/input/thumbstick/touch", XRT_INPUT_INDEX_THUMBSTICK_TOUCH}, 145 {"/input/thumbstick", XRT_INPUT_INDEX_THUMBSTICK}, 146 {"/input/trackpad/force", XRT_INPUT_INDEX_TRACKPAD_FORCE}, 147 {"/input/trackpad/touch", XRT_INPUT_INDEX_TRACKPAD_TOUCH}, 148 {"/input/trackpad", XRT_INPUT_INDEX_TRACKPAD}, 149 }, 150 }, 151 }, 152 { 153 "vive_tracker", 154 InputClass{ 155 XRT_DEVICE_VIVE_TRACKER, 156 { 157 XRT_INPUT_GENERIC_TRACKER_POSE, 158 }, 159 { 160 {"/input/power/click", XRT_INPUT_VIVE_TRACKER_SYSTEM_CLICK}, 161 {"/input/grip/click", XRT_INPUT_VIVE_TRACKER_SQUEEZE_CLICK}, 162 {"/input/application_menu/click", XRT_INPUT_VIVE_TRACKER_MENU_CLICK}, 163 {"/input/trigger/click", XRT_INPUT_VIVE_TRACKER_TRIGGER_CLICK}, 164 {"/input/thumb/click", XRT_INPUT_VIVE_TRACKER_TRACKPAD_CLICK}, 165 }, 166 }, 167 }, 168 { 169 "tundra_tracker", 170 InputClass{ 171 XRT_DEVICE_VIVE_TRACKER, 172 { 173 XRT_INPUT_GENERIC_TRACKER_POSE, 174 }, 175 { 176 {"/input/power/click", XRT_INPUT_VIVE_TRACKER_SYSTEM_CLICK}, 177 {"/input/grip/click", XRT_INPUT_VIVE_TRACKER_SQUEEZE_CLICK}, 178 {"/input/application_menu/click", XRT_INPUT_VIVE_TRACKER_MENU_CLICK}, 179 {"/input/trigger/click", XRT_INPUT_VIVE_TRACKER_TRIGGER_CLICK}, 180 {"/input/thumb/click", XRT_INPUT_VIVE_TRACKER_TRACKPAD_CLICK}, 181 }, 182 }, 183 }, 184}; 185int64_t 186chrono_timestamp_ns() 187{ 188 auto now = std::chrono::steady_clock::now().time_since_epoch(); 189 int64_t ts = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count(); 190 return ts; 191} 192 193// Template for calling a member function of Device from a free function 194template <typename DeviceType, auto Func, typename Ret, typename... Args> 195inline Ret 196device_bouncer(struct xrt_device *xdev, Args... args) 197{ 198 auto *dev = static_cast<DeviceType *>(xdev); 199 return std::invoke(Func, dev, args...); 200} 201 202// Setting used for brightness in the steamvr section. This isn't defined by the openvr header. 203static const char *analog_gain_settings_key = "analogGain"; 204 205/** 206 * Map a 0-1 (or > 1) brightness value into the analogGain value stored in SteamVR settings. 207 */ 208float 209brightness_to_analog_gain(float brightness) 210{ 211 // Lookup table from brightness to analog gain value 212 // Courtesy of OyasumiVR, MIT license, Copyright (c) 2022 Raphiiko 213 // https://github.com/Raphiiko/OyasumiVR/blob/c9e7fbcc2ea6caa07a8233a75218598087043171/src-ui/app/services/brightness-control/hardware-brightness-drivers/valve-index-hardware-brightness-control-driver.ts#L92 214 // TODO: We should support having a lookup table per headset model. If not present, fallback to lerp between the 215 // given min and max analog gain. Maybe we can assume 100% brightness = 1.0 analog gain, but we need info from 216 // more headsets. 217 static const auto lookup = std::map<float, float>{{ 218 {0.20, 0.03}, {0.23, 0.04}, {0.26, 0.05}, {0.27, 0.055}, {0.28, 0.06}, {0.30, 0.07}, {0.32, 0.08}, 219 {0.33, 0.09}, {0.34, 0.095}, {0.35, 0.1}, {0.36, 0.105}, {0.37, 0.11}, {0.37, 0.115}, {0.38, 0.12}, 220 {0.39, 0.125}, {0.40, 0.13}, {0.40, 0.135}, {0.41, 0.14}, {0.42, 0.145}, {0.42, 0.15}, {0.43, 0.155}, 221 {0.43, 0.16}, {0.44, 0.165}, {0.45, 0.17}, {0.45, 0.175}, {0.46, 0.18}, {0.46, 0.185}, {0.47, 0.19}, 222 {0.48, 0.195}, {0.48, 0.2}, {0.49, 0.21}, {0.53, 0.25}, {0.58, 0.3}, {0.59, 0.315}, {0.60, 0.32}, 223 {0.60, 0.33}, {0.61, 0.34}, {0.62, 0.35}, {0.66, 0.4}, {0.69, 0.445}, {0.70, 0.45}, {0.70, 0.46}, 224 {0.71, 0.465}, {0.71, 0.47}, {0.71, 0.475}, {0.72, 0.48}, {0.72, 0.49}, {0.73, 0.5}, {0.79, 0.6}, 225 {0.85, 0.7}, {0.90, 0.8}, {0.95, 0.9}, {1.00, 1}, {1.50, 1.50}, 226 }}; 227 228 if (const auto upper_it = lookup.upper_bound(brightness); upper_it == lookup.end()) { 229 return lookup.rbegin()->second; 230 } else if (upper_it == lookup.begin()) { 231 return upper_it->second; 232 } else { 233 // Linearly interpolate between the greater and lower points 234 const auto lower_it = std::prev(upper_it); 235 const auto brightness_range = (upper_it->first - lower_it->first); 236 const auto blend_amount = ((brightness - lower_it->first) / brightness_range); 237 return std::lerp(lower_it->second, upper_it->second, blend_amount); 238 } 239 240 return brightness; 241} 242xrt_pose 243bone_to_pose(const vr::VRBoneTransform_t &bone) 244{ 245 return xrt_pose{xrt_quat{bone.orientation.x, bone.orientation.y, bone.orientation.z, bone.orientation.w}, 246 xrt_vec3{bone.position.v[0], bone.position.v[1], bone.position.v[2]}}; 247} 248} // namespace 249 250Property::Property(vr::PropertyTypeTag_t tag, void *buffer, uint32_t bufferSize) 251{ 252 this->tag = tag; 253 this->buffer.resize(bufferSize); 254 std::memcpy(this->buffer.data(), buffer, bufferSize); 255} 256 257HmdDevice::HmdDevice(const DeviceBuilder &builder) : Device(builder) 258{ 259 this->name = XRT_DEVICE_GENERIC_HMD; 260 this->device_type = XRT_DEVICE_TYPE_HMD; 261 this->container_handle = 0; 262 263 inputs_vec = {xrt_input{true, 0, XRT_INPUT_GENERIC_HEAD_POSE, {}}}; 264 this->inputs = inputs_vec.data(); 265 this->input_count = inputs_vec.size(); 266 267#define SETUP_MEMBER_FUNC(name) this->xrt_device::name = &device_bouncer<HmdDevice, &HmdDevice::name, xrt_result_t> 268 SETUP_MEMBER_FUNC(get_view_poses); 269 SETUP_MEMBER_FUNC(compute_distortion); 270 SETUP_MEMBER_FUNC(set_brightness); 271 SETUP_MEMBER_FUNC(get_brightness); 272#undef SETUP_MEMBER_FUNC 273} 274 275ControllerDevice::ControllerDevice(vr::PropertyContainerHandle_t handle, const DeviceBuilder &builder) : Device(builder) 276{ 277 this->device_type = XRT_DEVICE_TYPE_UNKNOWN; 278 this->container_handle = handle; 279 280 this->xrt_device::get_hand_tracking = 281 &device_bouncer<ControllerDevice, &ControllerDevice::get_hand_tracking, xrt_result_t>; 282 this->xrt_device::set_output = &device_bouncer<ControllerDevice, &ControllerDevice::set_output, xrt_result_t>; 283 284 this->inputs_map["/skeleton/hand/left"] = &hand_tracking_inputs[XRT_HAND_LEFT]; 285 this->inputs_map["/skeleton/hand/right"] = &hand_tracking_inputs[XRT_HAND_RIGHT]; 286} 287 288Device::~Device() 289{ 290 m_relation_history_destroy(&relation_hist); 291} 292 293Device::Device(const DeviceBuilder &builder) : xrt_device({}), ctx(builder.ctx), driver(builder.driver) 294{ 295 m_relation_history_create(&relation_hist); 296 std::strncpy(this->serial, builder.serial, XRT_DEVICE_NAME_LEN - 1); 297 this->serial[XRT_DEVICE_NAME_LEN - 1] = 0; 298 this->tracking_origin = ctx.get(); 299 this->supported.orientation_tracking = true; 300 this->supported.position_tracking = true; 301 this->supported.hand_tracking = true; 302 this->supported.force_feedback = false; 303 this->supported.form_factor_check = false; 304 this->supported.battery_status = true; 305 this->supported.brightness_control = true; 306 307 this->xrt_device::update_inputs = &device_bouncer<Device, &Device::update_inputs, xrt_result_t>; 308#define SETUP_MEMBER_FUNC(name) this->xrt_device::name = &device_bouncer<Device, &Device::name> 309 SETUP_MEMBER_FUNC(get_tracked_pose); 310 SETUP_MEMBER_FUNC(get_battery_status); 311#undef SETUP_MEMBER_FUNC 312 313 this->xrt_device::destroy = [](xrt_device *xdev) { 314 auto *dev = static_cast<Device *>(xdev); 315 dev->driver->Deactivate(); 316 delete dev; 317 }; 318 319 init_chaperone(builder.steam_install); 320} 321 322// NOTE: No operations that would force inputs_vec or finger_inputs_vec to reallocate (such as insertion) 323// should be done after this function is called, otherwise the pointers in inputs_map/finger_inputs_map 324// would be invalidated. 325void 326ControllerDevice::set_input_class(const InputClass *input_class) 327{ 328 // this should only be called once 329 assert(inputs_vec.empty()); 330 this->input_class = input_class; 331 332 // reserve to ensure our pointers don't get invalidated 333 inputs_vec.reserve(input_class->poses.size() + input_class->non_poses.size() + 1); 334 for (xrt_input_name input : input_class->poses) { 335 inputs_vec.push_back({true, 0, input, {}}); 336 } 337 for (const auto &[path, input] : input_class->non_poses) { 338 assert(inputs_vec.capacity() >= inputs_vec.size() + 1); 339 inputs_vec.push_back({true, 0, input, {}}); 340 inputs_map.insert({path, &inputs_vec.back()}); 341 } 342 343 this->inputs = inputs_vec.data(); 344 this->input_count = inputs_vec.size(); 345} 346 347xrt_hand 348ControllerDevice::get_xrt_hand() 349{ 350 switch (this->device_type) { 351 case XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER: { 352 return xrt_hand::XRT_HAND_LEFT; 353 } 354 case XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER: { 355 return xrt_hand::XRT_HAND_RIGHT; 356 } 357 default: DEV_ERR("Device %s cannot be tracked as a hand!", serial); return xrt_hand::XRT_HAND_LEFT; 358 } 359} 360 361void 362ControllerDevice::set_active_hand(xrt_hand hand) 363{ 364 this->skeleton_hand = hand; 365} 366 367namespace { 368xrt_quat 369from_euler_angles(float x, float y, float z) 370{ 371 const xrt_vec3 v{x, y, z}; 372 xrt_quat out; 373 math_quat_from_euler_angles(&v, &out); 374 return out; 375} 376 377constexpr float pi = std::numbers::pi_v<float>; 378constexpr float frac_pi_2 = pi / 2.0f; 379 380// OpenVR skeletal poses are defined with the palms facing each other, but OpenXR 381// hand tracking is defined with the palms facing down. These per hand rotations 382// are necessary to translate to what OpenXR expects. 383const xrt_quat right_hand_rotate = from_euler_angles(0.0f, frac_pi_2, 0.0f); 384const xrt_quat left_hand_rotate = from_euler_angles(0.0f, frac_pi_2, pi); 385 386xrt_quat 387right_wrist_rotate_init() 388{ 389 const xrt_quat rot1 = from_euler_angles(0.0f, 0.0f, frac_pi_2); 390 const xrt_quat rot2 = from_euler_angles(0.0f, pi, 0.0f); 391 xrt_quat ret; 392 math_quat_rotate(&rot1, &rot2, &ret); 393 return ret; 394} 395xrt_quat 396left_wrist_rotate_init() 397{ 398 const xrt_quat rot1 = from_euler_angles(pi, 0.0f, 0.0f); 399 const xrt_quat rot2 = from_euler_angles(0.0f, 0.0f, -frac_pi_2); 400 xrt_quat ret; 401 math_quat_rotate(&rot1, &rot2, &ret); 402 return ret; 403} 404 405const xrt_quat right_wrist_rotate = right_wrist_rotate_init(); 406const xrt_quat left_wrist_rotate = left_wrist_rotate_init(); 407 408xrt_pose 409generate_palm_pose(const xrt_pose &metacarpal_pose, const xrt_pose &proximal_pose) 410{ 411 // OpenVR doesn't provide a palm joint, but the OpenXR palm is in the middle of 412 // the metacarpal and proximal bones of the middle finger, 413 // so we'll interpolate between them to generate it. 414 xrt_pose pose; 415 math_pose_interpolate(&metacarpal_pose, &proximal_pose, 0.5, &pose); 416 // Use metacarpal orientation, because the palm shouldn't really rotate 417 pose.orientation = metacarpal_pose.orientation; 418 return pose; 419} 420 421xrt_pose 422palm_offset_index(xrt_hand hand) 423{ 424 // Taken from: 425 // https://github.com/ValveSoftware/OpenXR-Canonical-Pose-Tool/blob/5e6f3f6db584d58483058ff3262e7eef02c3acfd/dist/steamvr/cpt_SteamVR-valve_index_controller.xml#L1 426 switch (hand) { 427 case XRT_HAND_LEFT: 428 return xrt_pose{.orientation = xrt_quat{.x = -0.46, .y = -0.02, .z = -0.01, .w = 0.89}, 429 .position = xrt_vec3{.x = -0.015, .y = 0.0, .z = 0.001}}; 430 case XRT_HAND_RIGHT: 431 return xrt_pose{.orientation = xrt_quat{.x = -0.46, .y = 0.02, .z = 0.01, .w = 0.89}, 432 .position = xrt_vec3{.x = 0.015, .y = 0.0, .z = 0.001}}; 433 } 434 435 return {}; 436} 437 438} // namespace 439 440void 441ControllerDevice::set_skeleton(std::span<const vr::VRBoneTransform_t> bones, 442 xrt_hand hand, 443 bool is_simulated, 444 const char *path) 445{ 446 assert(bones.size() == eBone_Count); 447 generate_palm_pose_offset(bones, hand); 448 if (!is_simulated && debug_get_bool_option_lh_emulate_hand()) { 449 assert(inputs_vec.capacity() >= inputs_vec.size() + 1); 450 const xrt_input_name tracker_name = 451 (hand == XRT_HAND_RIGHT) ? XRT_INPUT_HT_CONFORMING_RIGHT : XRT_INPUT_HT_CONFORMING_LEFT; 452 inputs_vec.push_back({true, 0, tracker_name, {}}); 453 inputs_map.insert({path, &inputs_vec.back()}); 454 this->input_count = inputs_vec.size(); 455 has_hand_tracking = true; 456 } 457} 458 459void 460ControllerDevice::generate_palm_pose_offset(std::span<const vr::VRBoneTransform_t> bones, xrt_hand hand) 461{ 462 if (this->input_class->name == XRT_DEVICE_INDEX_CONTROLLER) { 463 xrt_pose grip_offset; 464 vive_poses_get_pose_offset(this->input_class->name, this->device_type, XRT_INPUT_INDEX_GRIP_POSE, 465 &grip_offset); 466 auto offset = palm_offset_index(hand); 467 math_pose_transform(&grip_offset, &offset, &offset); 468 palm_offsets[hand] = offset; 469 return; 470 } 471 // The palm pose offset is generated from the OpenVR provided skeleton. 472 // https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md#notes-on-the-skeleton 473 474 xrt_pose root = bone_to_pose(bones[eBone_Root]); 475 xrt_pose wrist = bone_to_pose(bones[eBone_Wrist]); 476 xrt_pose metacarpal = bone_to_pose(bones[eBone_MiddleFinger0]); 477 xrt_pose proximal = bone_to_pose(bones[eBone_MiddleFinger1]); 478 479 // The skeleton pose is given with the Root bone as origin. 480 // To convert from this, according to OpenVR docs we transform the wrist 481 // and then counter-transform the metacarpals 482 xrt_pose root_inv; 483 math_pose_invert(&root, &root_inv); 484 math_pose_transform(&root_inv, &wrist, &wrist); 485 math_pose_transform(&root, &metacarpal, &metacarpal); 486 math_pose_transform(&wrist, &metacarpal, &metacarpal); 487 math_pose_transform(&metacarpal, &proximal, &proximal); 488 489 xrt_pose palm_offset = generate_palm_pose(metacarpal, proximal); 490 xrt_quat palm_rotate = from_euler_angles(0.0f, 0.0f, frac_pi_2); 491 492 switch (hand) { 493 case XRT_HAND_LEFT: { 494 math_quat_rotate(&palm_offset.orientation, &left_hand_rotate, &palm_offset.orientation); 495 math_quat_invert(&palm_rotate, &palm_rotate); 496 break; 497 } 498 case XRT_HAND_RIGHT: { 499 math_quat_rotate(&palm_offset.orientation, &right_hand_rotate, &palm_offset.orientation); 500 break; 501 } 502 } 503 math_quat_rotate(&palm_offset.orientation, &palm_rotate, &palm_offset.orientation); 504 505 // For controllers like the Vive Wands which can be in any hand, it will store both the left hand 506 // and the right hand skeletons, so we need to store both. 507 palm_offsets[hand] = palm_offset; 508} 509 510void 511ControllerDevice::update_skeleton_transforms(std::span<const vr::VRBoneTransform_t> bones) 512{ 513 if (!has_hand_tracking) { 514 return; 515 } 516 517 assert(bones.size() == eBone_Count); 518 519 xrt_hand_joint_set joint_set; 520 int64_t timestamp; 521 if (!m_relation_history_get_latest(relation_hist, &timestamp, &joint_set.hand_pose)) { 522 return; 523 } 524 joint_set.is_active = true; 525 auto &joints = joint_set.values.hand_joint_set_default; 526 527 xrt_pose root = bone_to_pose(bones[eBone_Root]); 528 xrt_pose wrist = bone_to_pose(bones[eBone_Wrist]); 529 530 // Here we're doing the same transformation as seen in generate_palm_pose_offset. 531 xrt_pose root_inv; 532 math_pose_invert(&root, &root_inv); 533 math_pose_transform(&root_inv, &wrist, &wrist); 534 535 constexpr auto valid_flags = (enum xrt_space_relation_flags)( 536 XRT_SPACE_RELATION_POSITION_VALID_BIT | XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | 537 XRT_SPACE_RELATION_POSITION_TRACKED_BIT | XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT); 538 539 xrt_pose wrist_xr = wrist; 540 541 switch (skeleton_hand) { 542 case XRT_HAND_LEFT: { 543 math_quat_rotate(&wrist_xr.orientation, &left_wrist_rotate, &wrist_xr.orientation); 544 break; 545 } 546 case XRT_HAND_RIGHT: { 547 math_quat_rotate(&wrist_xr.orientation, &right_wrist_rotate, &wrist_xr.orientation); 548 break; 549 } 550 } 551 552 joints[XRT_HAND_JOINT_WRIST].relation.pose = wrist_xr; 553 joints[XRT_HAND_JOINT_WRIST].relation.relation_flags = valid_flags; 554 555 xrt_pose parent_pose; 556 for (int joint = XRT_HAND_JOINT_THUMB_METACARPAL; joint <= XRT_HAND_JOINT_LITTLE_TIP; ++joint) { 557 // Luckily openvr and openxr joint values match 558 xrt_pose pose = bone_to_pose(bones[joint]); 559 joints[joint].relation.relation_flags = valid_flags; 560 561 if (u_hand_joint_is_metacarpal((xrt_hand_joint)joint)) { 562 // Counter transform metacarpals 563 math_pose_transform(&root, &pose, &pose); 564 math_pose_transform(&wrist, &pose, &pose); 565 } else { 566 math_pose_transform(&parent_pose, &pose, &pose); 567 } 568 569 parent_pose = pose; 570 571 // Rotate joint to OpenXR orientation 572 switch (skeleton_hand) { 573 case XRT_HAND_LEFT: math_quat_rotate(&pose.orientation, &left_hand_rotate, &pose.orientation); break; 574 case XRT_HAND_RIGHT: math_quat_rotate(&pose.orientation, &right_hand_rotate, &pose.orientation); break; 575 } 576 joints[joint].relation.pose = pose; 577 } 578 579 joints[XRT_HAND_JOINT_PALM].relation.relation_flags = valid_flags; 580 joints[XRT_HAND_JOINT_PALM].relation.pose = 581 generate_palm_pose(joints[XRT_HAND_JOINT_MIDDLE_METACARPAL].relation.pose, 582 joints[XRT_HAND_JOINT_MIDDLE_PROXIMAL].relation.pose); 583 584 u_hand_joints_apply_joint_width(&joint_set); 585 this->joint_history.push_back(JointsWithTimestamp{joint_set, timestamp}); 586} 587 588xrt_input * 589Device::get_input_from_name(const std::string_view name) 590{ 591 static const std::array ignore_inputs = {"/input/finger/index"sv, "/input/finger/middle"sv, 592 "/input/finger/ring"sv, "/input/finger/pinky"sv, 593 "/input/grip/touch"sv}; 594 595 // Return nullptr without any other output to suppress a pile of useless warnings found below. 596 if (std::ranges::find(ignore_inputs, name) != std::ranges::end(ignore_inputs)) { 597 return nullptr; 598 } 599 auto input = inputs_map.find(name); 600 if (input == inputs_map.end()) { 601 DEV_WARN("requested unknown input name %s for device %s", std::string(name).c_str(), serial); 602 return nullptr; 603 } 604 return input->second; 605} 606 607void 608ControllerDevice::set_haptic_handle(vr::VRInputComponentHandle_t handle) 609{ 610 // this should only be set once 611 assert(output == nullptr); 612 DEV_DEBUG("setting haptic handle for %" PRIu64, handle); 613 haptic_handle = handle; 614 xrt_output_name name; 615 switch (this->name) { 616 case XRT_DEVICE_VIVE_WAND: { 617 name = XRT_OUTPUT_NAME_VIVE_HAPTIC; 618 break; 619 } 620 case XRT_DEVICE_INDEX_CONTROLLER: { 621 name = XRT_OUTPUT_NAME_INDEX_HAPTIC; 622 break; 623 } 624 case XRT_DEVICE_VIVE_TRACKER: { 625 name = XRT_OUTPUT_NAME_VIVE_TRACKER_HAPTIC; 626 break; 627 } 628 default: { 629 DEV_WARN("Unknown device name (%u), haptics will not work", this->name); 630 return; 631 } 632 } 633 output = std::make_unique<xrt_output>(xrt_output{name}); 634 this->output_count = 1; 635 this->outputs = output.get(); 636} 637 638xrt_result_t 639Device::update_inputs() 640{ 641 std::lock_guard<std::mutex> lock(frame_mutex); 642 ctx->maybe_run_frame(++current_frame); 643 return XRT_SUCCESS; 644} 645 646xrt_result_t 647ControllerDevice::get_hand_tracking(enum xrt_input_name name, 648 int64_t desired_timestamp_ns, 649 struct xrt_hand_joint_set *out_value, 650 int64_t *out_timestamp_ns) 651{ 652 if (!has_hand_tracking) { 653 return XRT_ERROR_NOT_IMPLEMENTED; 654 } 655 656 // No handtracking data? 657 if (joint_history.empty()) { 658 out_value->is_active = false; 659 return XRT_SUCCESS; 660 } 661 662 const auto it = std::ranges::lower_bound(joint_history, desired_timestamp_ns, {}, 663 [](const JointsWithTimestamp &joint) { return joint.timestamp; }); 664 665 auto predict_joint_set = [out_value, out_timestamp_ns, 666 desired_timestamp_ns](const JointsWithTimestamp &joints) { 667 out_value->is_active = joints.joint_set.is_active; 668 int64_t delta_ns = desired_timestamp_ns - joints.timestamp; 669 double delta_s = time_ns_to_s(delta_ns); 670 671 *out_timestamp_ns = desired_timestamp_ns; 672 m_predict_relation(&joints.joint_set.hand_pose, delta_s, &out_value->hand_pose); 673 for (int i = 0; i < XRT_HAND_JOINT_COUNT; i++) { 674 auto &new_joint = joints.joint_set.values.hand_joint_set_default[i]; 675 auto &interp_joint = out_value->values.hand_joint_set_default[i]; 676 677 m_predict_relation(&new_joint.relation, delta_s, &interp_joint.relation); 678 interp_joint.radius = new_joint.radius; 679 } 680 }; 681 682 if (it == joint_history.end()) { 683 // Timestamp is newer than anything in history 684 predict_joint_set(joint_history.back()); 685 } else if (desired_timestamp_ns == it->timestamp) { 686 *out_value = it->joint_set; 687 *out_timestamp_ns = it->timestamp; 688 } else if (it == joint_history.begin()) { 689 // Timestamp is older than anything in history 690 predict_joint_set(joint_history.front()); 691 } else { 692 // Interpolate 693 auto it_previous = it - 1; 694 695 auto delta_before = desired_timestamp_ns - it_previous->timestamp; 696 auto delta_after = it->timestamp - desired_timestamp_ns; 697 float lerp_amt = (float)delta_before / (float)(delta_after - delta_before); 698 699 auto interpolate = [lerp_amt](xrt_space_relation &previous, xrt_space_relation &next, 700 xrt_space_relation &out) { 701 auto flags = (xrt_space_relation_flags)(previous.relation_flags & next.relation_flags); 702 m_space_relation_interpolate(&previous, &next, lerp_amt, flags, &out); 703 }; 704 705 interpolate(it_previous->joint_set.hand_pose, it->joint_set.hand_pose, out_value->hand_pose); 706 707 for (int i = 0; i < XRT_HAND_JOINT_COUNT; i++) { 708 auto &prev = it_previous->joint_set.values.hand_joint_set_default[i]; 709 auto &next = it->joint_set.values.hand_joint_set_default[i]; 710 auto &out = out_value->values.hand_joint_set_default[i]; 711 interpolate(prev.relation, next.relation, out.relation); 712 out.radius = next.radius; 713 } 714 715 *out_timestamp_ns = desired_timestamp_ns; 716 } 717 718 return XRT_SUCCESS; 719} 720 721void 722Device::get_pose(uint64_t at_timestamp_ns, xrt_space_relation *out_relation) 723{ 724 m_relation_history_get(this->relation_hist, at_timestamp_ns, out_relation); 725} 726 727xrt_result_t 728Device::get_battery_status(bool *out_present, bool *out_charging, float *out_charge) 729{ 730 *out_present = this->provides_battery_status; 731 *out_charging = this->charging; 732 *out_charge = this->charge; 733 return XRT_SUCCESS; 734} 735 736xrt_result_t 737HmdDevice::get_brightness(float *out_brightness) 738{ 739 *out_brightness = this->brightness; 740 return XRT_SUCCESS; 741} 742 743xrt_result_t 744HmdDevice::set_brightness(float brightness, bool relative) 745{ 746 constexpr auto min_brightness = 0.2f; 747 constexpr auto max_brightness = 1.5f; 748 749 const auto target_brightness = relative ? (this->brightness + brightness) : brightness; 750 this->brightness = std::clamp(target_brightness, min_brightness, max_brightness); 751 const auto analog_gain = 752 std::clamp(brightness_to_analog_gain(this->brightness), analog_gain_range.min, analog_gain_range.max); 753 ctx->settings.SetFloat(vr::k_pch_SteamVR_Section, analog_gain_settings_key, analog_gain); 754 return XRT_SUCCESS; 755} 756 757xrt_result_t 758HmdDevice::get_tracked_pose(xrt_input_name name, uint64_t at_timestamp_ns, xrt_space_relation *out_relation) 759{ 760 switch (name) { 761 case XRT_INPUT_GENERIC_HEAD_POSE: Device::get_pose(at_timestamp_ns, out_relation); break; 762 default: U_LOG_XDEV_UNSUPPORTED_INPUT(this, ctx->log_level, name); return XRT_ERROR_INPUT_UNSUPPORTED; 763 } 764 765 return XRT_SUCCESS; 766} 767 768xrt_result_t 769ControllerDevice::get_tracked_pose(xrt_input_name name, uint64_t at_timestamp_ns, xrt_space_relation *out_relation) 770{ 771 xrt_space_relation rel = {}; 772 Device::get_pose(at_timestamp_ns, &rel); 773 774 xrt_pose pose_offset = XRT_POSE_IDENTITY; 775 776 if (name == XRT_INPUT_GENERIC_PALM_POSE) { 777 if (!palm_offsets[skeleton_hand].has_value()) { 778 DEV_ERR("%s hand skeleton has not been initialized", 779 skeleton_hand == XRT_HAND_LEFT ? "left" : "right"); 780 *out_relation = XRT_SPACE_RELATION_ZERO; 781 return XRT_SUCCESS; 782 } 783 pose_offset = *palm_offsets[skeleton_hand]; 784 } else { 785 vive_poses_get_pose_offset(input_class->name, device_type, name, &pose_offset); 786 } 787 xrt_relation_chain relchain = {}; 788 789 m_relation_chain_push_pose(&relchain, &pose_offset); 790 m_relation_chain_push_relation(&relchain, &rel); 791 m_relation_chain_resolve(&relchain, out_relation); 792 793 struct xrt_pose *p = &out_relation->pose; 794 DEV_DEBUG("controller %u: GET_POSITION (%f %f %f) GET_ORIENTATION (%f, %f, %f, %f)", name, p->position.x, 795 p->position.y, p->position.z, p->orientation.x, p->orientation.y, p->orientation.z, p->orientation.w); 796 797 return XRT_SUCCESS; 798} 799 800xrt_result_t 801ControllerDevice::set_output(xrt_output_name name, const xrt_output_value *value) 802 803{ 804 const auto &vib = value->vibration; 805 if (vib.amplitude == 0.0) 806 return XRT_SUCCESS; 807 vr::VREvent_HapticVibration_t event; 808 event.containerHandle = container_handle; 809 event.componentHandle = haptic_handle; 810 event.fDurationSeconds = (float)vib.duration_ns / 1e9f; 811 // 0.0f in OpenXR means let the driver determine a frequency, but 812 // in OpenVR means no haptic, so let's set a reasonable default. 813 float frequency = vib.frequency; 814 if (frequency == 0.0) { 815 frequency = 200.0f; 816 } 817 818 event.fFrequency = frequency; 819 event.fAmplitude = vib.amplitude; 820 821 ctx->add_haptic_event(event); 822 return XRT_SUCCESS; 823} 824 825void 826HmdDevice::SetDisplayEyeToHead(uint32_t unWhichDevice, 827 const vr::HmdMatrix34_t &eyeToHeadLeft, 828 const vr::HmdMatrix34_t &eyeToHeadRight) 829{ 830 xrt_matrix_3x3 leftEye_prequat; 831 xrt_matrix_3x3 rightEye_prequat; 832 833 xrt_pose leftEye_postquat; 834 xrt_pose rightEye_postquat; 835 836 // This is a HmdMatrix34 to xrt_matrix_3x3 copy. 837 for (int i = 0; i < 3; ++i) { 838 for (int j = 0; j < 3; ++j) { 839 leftEye_prequat.v[i * 3 + j] = eyeToHeadLeft.m[i][j]; 840 rightEye_prequat.v[i * 3 + j] = eyeToHeadRight.m[i][j]; 841 } 842 } 843 844 math_quat_from_matrix_3x3(&leftEye_prequat, &leftEye_postquat.orientation); 845 math_quat_from_matrix_3x3(&rightEye_prequat, &rightEye_postquat.orientation); 846 leftEye_postquat.position.x = eyeToHeadLeft.m[0][3]; 847 leftEye_postquat.position.y = eyeToHeadLeft.m[1][3]; 848 leftEye_postquat.position.z = eyeToHeadLeft.m[2][3]; 849 850 rightEye_postquat.position.x = eyeToHeadRight.m[0][3]; 851 rightEye_postquat.position.y = eyeToHeadRight.m[1][3]; 852 rightEye_postquat.position.z = eyeToHeadRight.m[2][3]; 853 854 this->eye[0].orientation = leftEye_postquat.orientation; 855 this->eye[0].position = leftEye_postquat.position; 856 this->eye[1].orientation = rightEye_postquat.orientation; 857 this->eye[1].position = rightEye_postquat.position; 858} 859 860xrt_result_t 861HmdDevice::get_view_poses(const xrt_vec3 *default_eye_relation, 862 uint64_t at_timestamp_ns, 863 xrt_view_type view_type, 864 uint32_t view_count, 865 xrt_space_relation *out_head_relation, 866 xrt_fov *out_fovs, 867 xrt_pose *out_poses) 868{ 869 struct xrt_vec3 eye_relation = *default_eye_relation; 870 eye_relation.x = ipd; 871 872 xrt_result_t xret = u_device_get_view_poses( // 873 this, // 874 &eye_relation, // 875 at_timestamp_ns, // 876 view_type, // 877 view_count, // 878 out_head_relation, // 879 out_fovs, // 880 out_poses); // 881 if (xret != XRT_SUCCESS) { 882 return xret; 883 } 884 885 out_poses[0].orientation = this->eye[0].orientation; 886 out_poses[0].position.z = this->eye[0].position.z; 887 out_poses[0].position.y = this->eye[0].position.y; 888 out_poses[1].orientation = this->eye[1].orientation; 889 out_poses[1].position.z = this->eye[1].position.z; 890 out_poses[1].position.y = this->eye[1].position.y; 891 892 return XRT_SUCCESS; 893} 894 895xrt_result_t 896HmdDevice::compute_distortion(uint32_t view, float u, float v, xrt_uv_triplet *out_result) 897{ 898 // Vive Pro 2 has a vertically flipped distortion map. 899 if (this->variant == VIVE_VARIANT_PRO2) { 900 v = 1.0f - v; 901 } 902 903 vr::EVREye eye = (view == 0) ? vr::Eye_Left : vr::Eye_Right; 904 vr::DistortionCoordinates_t coords = this->hmd_parts->display->ComputeDistortion(eye, u, v); 905 out_result->r = {coords.rfRed[0], coords.rfRed[1]}; 906 out_result->g = {coords.rfGreen[0], coords.rfGreen[1]}; 907 out_result->b = {coords.rfBlue[0], coords.rfBlue[1]}; 908 return XRT_SUCCESS; 909} 910 911void 912HmdDevice::set_hmd_parts(std::unique_ptr<Parts> parts) 913{ 914 { 915 std::lock_guard lk(hmd_parts_mut); 916 hmd_parts = std::move(parts); 917 this->hmd = &hmd_parts->base; 918 } 919 hmd_parts_cv.notify_all(); 920} 921 922namespace { 923xrt_quat 924copy_quat(const vr::HmdQuaternion_t &quat) 925{ 926 return xrt_quat{(float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w}; 927} 928 929xrt_vec3 930copy_vec3(const double (&vec)[3]) 931{ 932 return xrt_vec3{(float)vec[0], (float)vec[1], (float)vec[2]}; 933} 934 935xrt_pose 936copy_pose(const vr::HmdQuaternion_t &orientation, const double (&position)[3]) 937{ 938 return xrt_pose{copy_quat(orientation), copy_vec3(position)}; 939} 940} // namespace 941 942void 943Device::init_chaperone(const std::string &steam_install) 944{ 945 static bool initialized = false; 946 if (initialized) 947 return; 948 949 initialized = true; 950 951 // Lighthouse driver seems to create a lighthousedb.json and a chaperone_info.vrchap (which is json) 952 // We will use the known_universes from the lighthousedb.json to match to a universe from chaperone_info.vrchap 953 954 using xrt::auxiliary::util::json::JSONNode; 955 auto lighthousedb = JSONNode::loadFromFile(steam_install + "/config/lighthouse/lighthousedb.json"); 956 if (lighthousedb.isInvalid()) { 957 DEV_ERR("Couldn't load lighthousedb file, playspace center will be off - was Room Setup run?"); 958 return; 959 } 960 auto chap_info = JSONNode::loadFromFile(steam_install + "/config/chaperone_info.vrchap"); 961 if (chap_info.isInvalid()) { 962 DEV_ERR("Couldn't load chaperone info, playspace center will be off - was Room Setup run?"); 963 return; 964 } 965 966 JSONNode info = {}; 967 bool universe_found = false; 968 969 // XXX: This may be broken if there are multiple known universes - how do we determine which to use then? 970 auto known_universes = lighthousedb["known_universes"].asArray(); 971 for (auto &universe : known_universes) { 972 const std::string id = universe["id"].asString(); 973 for (const JSONNode &u : chap_info["universes"].asArray()) { 974 if (u["universeID"].asString() == id) { 975 DEV_INFO("Found info for universe %s", id.c_str()); 976 info = u; 977 universe_found = true; 978 break; 979 } 980 } 981 if (universe_found) { 982 break; 983 } 984 } 985 986 if (info.isInvalid()) { 987 DEV_ERR("Couldn't find chaperone info for any known universe, playspace center will be off"); 988 return; 989 } 990 991 std::vector<JSONNode> translation_arr = info["standing"]["translation"].asArray(); 992 993 // If the array is missing elements, add zero. 994 for (size_t i = translation_arr.size(); i < 3; i++) { 995 translation_arr.push_back(JSONNode("0.0")); 996 } 997 998 const double yaw = info["standing"]["yaw"].asDouble(); 999 const xrt_vec3 yaw_axis{0.0, -1.0, 0.0}; 1000 math_quat_from_angle_vector(static_cast<float>(yaw), &yaw_axis, &chaperone.orientation); 1001 chaperone.position = copy_vec3({ 1002 translation_arr[0].asDouble(), 1003 translation_arr[1].asDouble(), 1004 translation_arr[2].asDouble(), 1005 }); 1006 math_quat_rotate_vec3(&chaperone.orientation, &chaperone.position, &chaperone.position); 1007 DEV_INFO("Initialized chaperone data."); 1008} 1009 1010inline xrt_space_relation_flags 1011operator|(xrt_space_relation_flags a, xrt_space_relation_flags b) 1012{ 1013 return static_cast<xrt_space_relation_flags>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b)); 1014} 1015 1016inline xrt_space_relation_flags & 1017operator|=(xrt_space_relation_flags &a, xrt_space_relation_flags b) 1018{ 1019 a = a | b; 1020 return a; 1021} 1022 1023void 1024Device::update_pose(const vr::DriverPose_t &newPose) const 1025{ 1026 xrt_space_relation relation = {}; 1027 1028 if (newPose.poseIsValid) { 1029 // The pose is known to be valid but that alone is not enough to say whether the data comes from 1030 // inference or if it represents actively tracked position and orientation data. Furthermore we avoid 1031 // assumptions regarding the validity of time-derivatives until we know that they're based on tracked 1032 // data. This is a conservative strategy that should reduce concerns regarding drift. see 1033 // https://registry.khronos.org/OpenXR/specs/1.1/man/html/XrSpaceLocationFlagBits.html 1034 relation.relation_flags |= xrt_space_relation_flags::XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | 1035 xrt_space_relation_flags::XRT_SPACE_RELATION_POSITION_VALID_BIT; 1036 1037 switch (newPose.result) { 1038 // see https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md 1039 case vr::TrackingResult_Running_OK: 1040 // If the tracker is running ok then we have actively tracked 6DoF data 1041 relation.relation_flags |= 1042 xrt_space_relation_flags::XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | 1043 xrt_space_relation_flags::XRT_SPACE_RELATION_POSITION_TRACKED_BIT | 1044 xrt_space_relation_flags::XRT_SPACE_RELATION_ANGULAR_VELOCITY_VALID_BIT | 1045 xrt_space_relation_flags::XRT_SPACE_RELATION_LINEAR_VELOCITY_VALID_BIT; 1046 break; 1047 case vr::TrackingResult_Fallback_RotationOnly: 1048 case vr::TrackingResult_Running_OutOfRange: 1049 // If the tracking is degraded we should still be able to assume that we still have tracked 3DoF 1050 // data 1051 relation.relation_flags |= 1052 xrt_space_relation_flags::XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | 1053 xrt_space_relation_flags::XRT_SPACE_RELATION_ANGULAR_VELOCITY_VALID_BIT; 1054 break; 1055 default: break; 1056 } 1057 } 1058 1059 // The driver still outputs good pose data regardless of the pose results above 1060 relation.pose = copy_pose(newPose.qRotation, newPose.vecPosition); 1061 relation.linear_velocity = copy_vec3(newPose.vecVelocity); 1062 relation.angular_velocity = copy_vec3(newPose.vecAngularVelocity); 1063 1064 // local transform (head to driver offset) 1065 const xrt_pose local = copy_pose(newPose.qDriverFromHeadRotation, newPose.vecDriverFromHeadTranslation); 1066 1067 // IMU linear velocity contribution due to rotation around driver origin (tangential velocity) 1068 xrt_vec3 tangential_velocity; 1069 math_vec3_cross(&relation.angular_velocity, &local.position, &tangential_velocity); 1070 math_quat_rotate_vec3(&relation.pose.orientation, &tangential_velocity, &tangential_velocity); 1071 math_vec3_accum(&tangential_velocity, &relation.linear_velocity); 1072 1073 // apply local transform 1074 math_quat_rotate_vec3(&relation.pose.orientation, &relation.angular_velocity, &relation.angular_velocity); 1075 math_pose_transform(&relation.pose, &local, &relation.pose); 1076 1077 // apply world transform 1078 const xrt_pose world = copy_pose(newPose.qWorldFromDriverRotation, newPose.vecWorldFromDriverTranslation); 1079 math_pose_transform(&world, &relation.pose, &relation.pose); 1080 math_quat_rotate_vec3(&world.orientation, &relation.linear_velocity, &relation.linear_velocity); 1081 math_quat_rotate_vec3(&world.orientation, &relation.angular_velocity, &relation.angular_velocity); 1082 1083 // apply chaperone transform 1084 math_pose_transform(&chaperone, &relation.pose, &relation.pose); 1085 math_quat_rotate_vec3(&chaperone.orientation, &relation.linear_velocity, &relation.linear_velocity); 1086 math_quat_rotate_vec3(&chaperone.orientation, &relation.angular_velocity, &relation.angular_velocity); 1087 1088 const uint64_t ts = chrono_timestamp_ns() + static_cast<uint64_t>(newPose.poseTimeOffset * 1000000.0); 1089 1090 m_relation_history_push(relation_hist, &relation, ts); 1091} 1092 1093vr::ETrackedPropertyError 1094Device::handle_properties(const vr::PropertyWrite_t *batch, uint32_t count) 1095{ 1096 for (uint32_t i = 0; i < count; ++i) { 1097 vr::ETrackedPropertyError err = handle_property_write(batch[i]); 1098 if (err != vr::ETrackedPropertyError::TrackedProp_Success) { 1099 return err; 1100 } 1101 } 1102 return vr::ETrackedPropertyError::TrackedProp_Success; 1103} 1104 1105vr::ETrackedPropertyError 1106Device::handle_read_properties(vr::PropertyRead_t *batch, uint32_t count) 1107{ 1108 for (uint32_t i = 0; i < count; ++i) { 1109 vr::ETrackedPropertyError err = handle_generic_property_read(batch[i]); 1110 if (err != vr::ETrackedPropertyError::TrackedProp_Success) { 1111 return err; 1112 } 1113 } 1114 return vr::ETrackedPropertyError::TrackedProp_Success; 1115} 1116 1117void 1118HmdDevice::set_nominal_frame_interval(uint64_t interval_ns) 1119{ 1120 auto set = [this, interval_ns] { hmd_parts->base.screens[0].nominal_frame_interval_ns = interval_ns; }; 1121 1122 if (hmd_parts) { 1123 set(); 1124 } else { 1125 std::thread t([this, set] { 1126 std::unique_lock lk(hmd_parts_mut); 1127 hmd_parts_cv.wait(lk, [this] { return hmd_parts != nullptr; }); 1128 set(); 1129 }); 1130 t.detach(); 1131 } 1132} 1133 1134void 1135HmdDevice::set_scanout_type(xrt_scanout_direction direction, int64_t time_ns) 1136{ 1137 auto set = [this, direction, time_ns] { 1138 hmd_parts->base.screens[0].scanout_direction = direction; 1139 hmd_parts->base.screens[0].scanout_time_ns = time_ns; 1140 }; 1141 1142 if (hmd_parts) { 1143 set(); 1144 } else { 1145 std::thread t([this, set] { 1146 std::unique_lock lk(hmd_parts_mut); 1147 hmd_parts_cv.wait(lk, [this] { return hmd_parts != nullptr; }); 1148 set(); 1149 }); 1150 t.detach(); 1151 } 1152} 1153 1154namespace { 1155// From openvr driver documentation 1156// (https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md#Input-Profiles): 1157// "Input profiles are expected to be a valid JSON file, 1158// and should be located: <driver_name>/resources/input/<device_name>_profile.json" 1159// So we will just parse the file name to get the device name. 1160std::string_view 1161parse_profile(std::string_view path) 1162{ 1163 size_t name_start_idx = path.find_last_of('/') + 1; 1164 size_t name_end_idx = path.find_last_of('_'); 1165 return path.substr(name_start_idx, name_end_idx - name_start_idx); 1166} 1167} // namespace 1168 1169vr::ETrackedPropertyError 1170Device::handle_generic_property_write(const vr::PropertyWrite_t &prop) 1171{ 1172 switch (prop.writeType) { 1173 case vr::EPropertyWriteType::PropertyWrite_Set: 1174 if (properties.count(prop.prop) > 0) { 1175 Property &p = properties.at(prop.prop); 1176 if (p.tag != prop.unTag) { 1177 return vr::ETrackedPropertyError::TrackedProp_WrongDataType; 1178 } 1179 p.buffer.resize(prop.unBufferSize); 1180 std::memcpy(p.buffer.data(), prop.pvBuffer, prop.unBufferSize); 1181 return vr::ETrackedPropertyError::TrackedProp_Success; 1182 } else { 1183 properties.emplace(std::piecewise_construct, std::forward_as_tuple(prop.prop), 1184 std::forward_as_tuple(prop.unTag, prop.pvBuffer, prop.unBufferSize)); 1185 } 1186 break; 1187 case vr::EPropertyWriteType::PropertyWrite_Erase: properties.erase(prop.prop); break; 1188 case vr::EPropertyWriteType::PropertyWrite_SetError: 1189 DEV_DEBUG("Property write type SetError not supported! (property %d)", prop.prop); 1190 break; 1191 } 1192 return vr::ETrackedPropertyError::TrackedProp_Success; 1193} 1194 1195vr::ETrackedPropertyError 1196Device::handle_generic_property_read(vr::PropertyRead_t &prop) 1197{ 1198 if (properties.count(prop.prop) == 0) { 1199 // not verified if this is the correct error 1200 return vr::ETrackedPropertyError::TrackedProp_UnknownProperty; 1201 } 1202 Property &p = properties.at(prop.prop); 1203 prop.unTag = p.tag; 1204 prop.unRequiredBufferSize = p.buffer.size(); 1205 if (prop.pvBuffer == nullptr || prop.unBufferSize < p.buffer.size()) { 1206 prop.eError = vr::ETrackedPropertyError::TrackedProp_BufferTooSmall; 1207 return prop.eError; 1208 } 1209 std::memcpy(prop.pvBuffer, p.buffer.data(), p.buffer.size()); 1210 prop.eError = vr::ETrackedPropertyError::TrackedProp_Success; 1211 return prop.eError; 1212} 1213 1214vr::ETrackedPropertyError 1215Device::handle_property_write(const vr::PropertyWrite_t &prop) 1216{ 1217 switch (prop.prop) { 1218 case vr::Prop_ManufacturerName_String: { 1219 this->manufacturer = std::string(static_cast<char *>(prop.pvBuffer), prop.unBufferSize); 1220 if (!this->model.empty()) { 1221 std::snprintf(this->str, std::size(this->str), "%s %s", this->manufacturer.c_str(), 1222 this->model.c_str()); 1223 } 1224 break; 1225 } 1226 case vr::Prop_ModelNumber_String: { 1227 this->model = std::string(static_cast<char *>(prop.pvBuffer), prop.unBufferSize); 1228 if (!this->manufacturer.empty()) { 1229 std::snprintf(this->str, std::size(this->str), "%s %s", this->manufacturer.c_str(), 1230 this->model.c_str()); 1231 } 1232 break; 1233 } 1234 default: { 1235 DEV_DEBUG("Unhandled property: %i", prop.prop); 1236 break; 1237 } 1238 } 1239 return handle_generic_property_write(prop); 1240} 1241 1242bool 1243HmdDevice::init_vive_pro_2(struct xrt_prober *xp) 1244{ 1245 xrt_result_t xret; 1246 int ret = 0; 1247 1248 struct xrt_prober_device **devices = nullptr; 1249 size_t device_count; 1250 1251 xret = xrt_prober_lock_list(xp, &devices, &device_count); 1252 if (xret != XRT_SUCCESS) { 1253 DEV_ERR("Failed to lock prober device list"); 1254 return false; 1255 } 1256 1257 for (size_t i = 0; i < device_count; i++) { 1258 struct xrt_prober_device *dev = devices[i]; 1259 1260 if (dev->vendor_id == VP2_VID && dev->product_id == VP2_PID) { 1261 DEV_INFO("Found Vive Pro 2 HID device"); 1262 struct os_hid_device *hid_dev = nullptr; 1263 ret = xrt_prober_open_hid_interface(xp, dev, 0, &hid_dev); 1264 if (ret != 0) { 1265 DEV_ERR("Failed to open Vive Pro 2 HID interface"); 1266 break; 1267 } 1268 1269 ret = vp2_hid_open(hid_dev, &this->vp2.hid); 1270 if (ret != 0) { 1271 DEV_ERR("Failed to open Vive Pro 2 HID device"); 1272 break; 1273 } 1274 1275 break; 1276 } 1277 } 1278 1279 xrt_prober_unlock_list(xp, &devices); 1280 1281 int width, height; 1282 vp2_resolution_get_extents(vp2_get_resolution(this->vp2.hid), &width, &height); 1283 1284 for (int i = 0; i < 2; i++) { 1285 this->hmd_parts->base.views[i].display.w_pixels = width / 2; 1286 this->hmd_parts->base.views[i].display.h_pixels = height; 1287 1288 this->hmd_parts->base.views[i].viewport.w_pixels = width / 2; 1289 this->hmd_parts->base.views[i].viewport.h_pixels = height; 1290 } 1291 1292 this->hmd_parts->base.views[1].viewport.x_pixels = width / 2; 1293 1294 this->hmd_parts->base.screens[0].w_pixels = width; 1295 this->hmd_parts->base.screens[0].h_pixels = height; 1296 1297 return ret == 0; 1298} 1299 1300vr::ETrackedPropertyError 1301HmdDevice::handle_property_write(const vr::PropertyWrite_t &prop) 1302{ 1303 switch (prop.prop) { 1304 case vr::Prop_ModelNumber_String: { 1305 std::string model_number(static_cast<char *>(prop.pvBuffer), prop.unBufferSize); 1306 1307 this->variant = vive_determine_variant(model_number.c_str()); 1308 1309 return Device::handle_property_write(prop); 1310 } 1311 case vr::Prop_DisplayFrequency_Float: { 1312 assert(prop.unBufferSize == sizeof(float)); 1313 float freq = *static_cast<float *>(prop.pvBuffer); 1314 int64_t interval_ns = (1.f / freq) * 1e9f; 1315 set_nominal_frame_interval(interval_ns); 1316 if (variant == VIVE_VARIANT_PRO) { 1317 set_scanout_type(XRT_SCANOUT_DIRECTION_TOP_TO_BOTTOM, interval_ns * 1600.0 / 1624.0); 1318 } else if (variant == VIVE_VARIANT_BEYOND) { 1319 set_scanout_type(XRT_SCANOUT_DIRECTION_TOP_TO_BOTTOM, interval_ns * 2544.0 / 2568.0); 1320 } else if (variant == VIVE_VARIANT_PRO2) { 1321 set_scanout_type(XRT_SCANOUT_DIRECTION_TOP_TO_BOTTOM, interval_ns * 2448.0 / 2574.0); 1322 } else { 1323 set_scanout_type(XRT_SCANOUT_DIRECTION_NONE, 0); 1324 } 1325 break; 1326 } 1327 case vr::Prop_UserIpdMeters_Float: { 1328 if (*static_cast<float *>(prop.pvBuffer) != 0) { 1329 ipd = *static_cast<float *>(prop.pvBuffer); 1330 } 1331 break; 1332 } 1333 case vr::Prop_SecondsFromVsyncToPhotons_Float: { 1334 vsync_to_photon_ns = *static_cast<float *>(prop.pvBuffer) * 1e9f; 1335 break; 1336 } 1337 case vr::Prop_DeviceProvidesBatteryStatus_Bool: { 1338 float supported = *static_cast<bool *>(prop.pvBuffer); 1339 this->provides_battery_status = supported; 1340 DEV_DEBUG("Has battery status: HMD: %s", supported ? "true" : "false"); 1341 break; 1342 } 1343 case vr::Prop_DeviceIsCharging_Bool: { 1344 float charging = *static_cast<bool *>(prop.pvBuffer); 1345 this->charging = charging; 1346 DEV_DEBUG("Charging: HMD: %s", charging ? "true" : "false"); 1347 break; 1348 } 1349 case vr::Prop_DeviceBatteryPercentage_Float: { 1350 float bat = *static_cast<float *>(prop.pvBuffer); 1351 this->charge = bat; 1352 DEV_DEBUG("Battery: HMD: %f", bat); 1353 break; 1354 } 1355 case vr::Prop_DisplaySupportsAnalogGain_Bool: { 1356 this->supported.brightness_control = *static_cast<bool *>(prop.pvBuffer); 1357 break; 1358 } 1359 case vr::Prop_DisplayMinAnalogGain_Float: { 1360 this->analog_gain_range.min = *static_cast<float *>(prop.pvBuffer); 1361 break; 1362 } 1363 case vr::Prop_DisplayMaxAnalogGain_Float: { 1364 this->analog_gain_range.max = *static_cast<float *>(prop.pvBuffer); 1365 break; 1366 } 1367 default: { 1368 return Device::handle_property_write(prop); 1369 } 1370 } 1371 return handle_generic_property_write(prop); 1372} 1373 1374vr::ETrackedPropertyError 1375ControllerDevice::handle_property_write(const vr::PropertyWrite_t &prop) 1376{ 1377 switch (prop.prop) { 1378 case vr::Prop_InputProfilePath_String: { 1379 std::string_view profile = 1380 parse_profile(std::string_view(static_cast<char *>(prop.pvBuffer), prop.unBufferSize)); 1381 auto input_class = controller_classes.find(profile); 1382 if (input_class == controller_classes.end()) { 1383 DEV_ERR("Could not find input class for controller profile %s", std::string(profile).c_str()); 1384 } else { 1385 this->name = input_class->second.name; 1386 set_input_class(&input_class->second); 1387 } 1388 break; 1389 } 1390 case vr::Prop_ModelNumber_String: { 1391 using namespace std::literals::string_view_literals; 1392 vr::PropertyWrite_t fixedProp = prop; 1393 const std::string_view name = {static_cast<char *>(prop.pvBuffer), prop.unBufferSize}; 1394 if (name == "SlimeVR Virtual Tracker\0"sv) { 1395 static const InputClass input_class = { 1396 XRT_DEVICE_VIVE_TRACKER, {XRT_INPUT_GENERIC_TRACKER_POSE}, {}}; 1397 this->name = input_class.name; 1398 set_input_class(&input_class); 1399 this->manufacturer = name.substr(0, name.find_first_of(' ')); 1400 fixedProp.pvBuffer = (char *)fixedProp.pvBuffer + this->manufacturer.size() + 1401 (this->manufacturer.size() != name.size()); 1402 fixedProp.unBufferSize = name.end() - (char *)fixedProp.pvBuffer; 1403 } 1404 return Device::handle_property_write(fixedProp); 1405 } 1406 case vr::Prop_ControllerRoleHint_Int32: { 1407 vr::ETrackedControllerRole role = *static_cast<vr::ETrackedControllerRole *>(prop.pvBuffer); 1408 switch (role) { 1409 case vr::TrackedControllerRole_Invalid: { 1410 this->device_type = XRT_DEVICE_TYPE_ANY_HAND_CONTROLLER; 1411 break; 1412 } 1413 case vr::TrackedControllerRole_RightHand: { 1414 this->device_type = XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER; 1415 set_active_hand(XRT_HAND_RIGHT); 1416 break; 1417 } 1418 case vr::TrackedControllerRole_LeftHand: { 1419 this->device_type = XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER; 1420 set_active_hand(XRT_HAND_LEFT); 1421 break; 1422 } 1423 case vr::TrackedControllerRole_OptOut: { 1424 this->device_type = XRT_DEVICE_TYPE_GENERIC_TRACKER; 1425 break; 1426 } 1427 default: { 1428 this->device_type = XRT_DEVICE_TYPE_UNKNOWN; 1429 DEV_WARN("requested unimplemented role hint %i", this->device_type); 1430 break; 1431 } 1432 } 1433 break; 1434 } 1435 case vr::Prop_DeviceProvidesBatteryStatus_Bool: { 1436 float supported = *static_cast<bool *>(prop.pvBuffer); 1437 const char *name; 1438 switch (this->device_type) { 1439 case XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER: { 1440 name = "Left"; 1441 break; 1442 } 1443 case XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER: { 1444 name = "Right"; 1445 break; 1446 } 1447 default: { 1448 name = "Unknown"; 1449 break; 1450 } 1451 } 1452 this->provides_battery_status = supported; 1453 DEV_DEBUG("Has battery status: %s: %s", name, supported ? "true" : "false"); 1454 break; 1455 } 1456 case vr::Prop_DeviceIsCharging_Bool: { 1457 float charging = *static_cast<bool *>(prop.pvBuffer); 1458 const char *name; 1459 switch (this->device_type) { 1460 case XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER: { 1461 name = "Left"; 1462 break; 1463 } 1464 case XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER: { 1465 name = "Right"; 1466 break; 1467 } 1468 default: { 1469 name = "Unknown"; 1470 } 1471 } 1472 this->charging = charging; 1473 DEV_DEBUG("Charging: %s: %s", name, charging ? "true" : "false"); 1474 break; 1475 } 1476 case vr::Prop_DeviceBatteryPercentage_Float: { 1477 float bat = *static_cast<float *>(prop.pvBuffer); 1478 const char *name; 1479 switch (this->device_type) { 1480 case XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER: { 1481 name = "Left"; 1482 break; 1483 } 1484 case XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER: { 1485 name = "Right"; 1486 break; 1487 } 1488 default: { 1489 name = "Unknown"; 1490 } 1491 } 1492 this->charge = bat; 1493 DEV_DEBUG("Battery: %s: %f", name, bat); 1494 break; 1495 } 1496 default: { 1497 return Device::handle_property_write(prop); 1498 } 1499 } 1500 return handle_generic_property_write(prop); 1501}