The open source OpenXR runtime
at main 917 lines 30 kB view raw
1// Copyright 2023, Shawn Wallace 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief SteamVR driver context implementation and entrypoint. 6 * @author Shawn Wallace <yungwallace@live.com> 7 * @ingroup drv_steamvr_lh 8 */ 9 10#include <cstring> 11#include <dlfcn.h> 12#include <memory> 13#include <cmath> 14#include <unordered_map> 15#include <string_view> 16#include <filesystem> 17#include <istream> 18#include <thread> 19 20#include "openvr_driver.h" 21#include "vdf_parser.hpp" 22#include "steamvr_lh_interface.h" 23#include "interfaces/context.hpp" 24#include "device.hpp" 25#include "util/u_device.h" 26#include "util/u_misc.h" 27#include "util/u_device.h" 28#include "util/u_system_helpers.h" 29#include "vive/vive_bindings.h" 30#include "util/u_device.h" 31 32#include "math/m_api.h" 33 34namespace { 35 36DEBUG_GET_ONCE_LOG_OPTION(lh_log, "LIGHTHOUSE_LOG", U_LOGGING_INFO) 37DEBUG_GET_ONCE_BOOL_OPTION(lh_load_slimevr, "LH_LOAD_SLIMEVR", false) 38DEBUG_GET_ONCE_NUM_OPTION(lh_discover_wait_ms, "LH_DISCOVER_WAIT_MS", 3000) 39 40static constexpr size_t MAX_CONTROLLERS = 16; 41 42 43struct steamvr_lh_system 44{ 45 // System devices wrapper. 46 struct xrt_system_devices base; 47 48 //! Pointer to driver context 49 std::shared_ptr<Context> ctx; 50}; 51 52struct steamvr_lh_system *svrs = U_TYPED_CALLOC(struct steamvr_lh_system); 53 54 55// ~/.steam/root is a symlink to where the Steam root is 56const std::string STEAM_INSTALL_DIR = std::string(getenv("HOME")) + "/.steam/root"; 57constexpr auto STEAMVR_APPID = "250820"; 58 59// Parse libraryfolder.vdf to find where SteamVR is installed 60std::string 61find_steamvr_install() 62{ 63 using namespace tyti; 64 u_logging_level level = debug_get_log_option_lh_log(); 65 std::ifstream file(STEAM_INSTALL_DIR + "/steamapps/libraryfolders.vdf"); 66 if (!file.is_open()) { 67 U_LOG_IFL_E(level, "Failed to open libraryfolders.vdf"); 68 return std::string(); 69 } 70 71 vdf::basic_object<char> root; 72 try { 73 root = vdf::read(file); 74 } catch (std::exception &ex) { 75 U_LOG_IFL_E(level, "Failed to read libraryfolders.vdf: %s", ex.what()); 76 return std::string(); 77 } 78 79 assert(root.name == "libraryfolders"); 80 for (auto &[_, child] : root.childs) { 81 U_LOG_D("Found library folder %s", child->attribs["path"].c_str()); 82 std::shared_ptr<vdf::object> apps = child->childs["apps"]; 83 for (auto &[appid, _] : apps->attribs) { 84 if (appid == STEAMVR_APPID) { 85 std::string path = child->attribs["path"] + "/steamapps/common/SteamVR"; 86 if (std::filesystem::exists(path)) { 87 return path; 88 } 89 } 90 } 91 } 92 return std::string(); 93} 94 95} // namespace 96 97#define CTX_ERR(...) U_LOG_IFL_E(log_level, __VA_ARGS__) 98#define CTX_WARN(...) U_LOG_IFL_W(log_level, __VA_ARGS__) 99#define CTX_INFO(...) U_LOG_IFL_I(log_level, __VA_ARGS__) 100#define CTX_TRACE(...) U_LOG_IFL_T(log_level, __VA_ARGS__) 101#define CTX_DEBUG(...) U_LOG_IFL_D(log_level, __VA_ARGS__) 102 103/** 104 * Since only the devices will live after our get_devices function is called, we make our Context 105 * a shared ptr that is owned by the devices that exist, so that it is also cleaned up by the 106 * devices that exist when they are all destroyed. 107 */ 108std::shared_ptr<Context> 109Context::create(const std::string &steam_install, 110 const std::string &steamvr_install, 111 std::vector<vr::IServerTrackedDeviceProvider *> providers) 112{ 113 // xrt_tracking_origin initialization 114 std::shared_ptr<Context> c = 115 std::make_shared<Context>(steam_install, steamvr_install, debug_get_log_option_lh_log()); 116 c->providers = std::move(providers); 117 std::strncpy(c->name, "SteamVR Lighthouse Tracking", XRT_TRACKING_NAME_LEN); 118 c->type = XRT_TRACKING_TYPE_LIGHTHOUSE; 119 c->initial_offset = XRT_POSE_IDENTITY; 120 for (vr::IServerTrackedDeviceProvider *const &driver : c->providers) { 121 vr::EVRInitError err = driver->Init(c.get()); 122 if (err != vr::VRInitError_None) { 123 U_LOG_IFL_E(c->log_level, "OpenVR driver initialization failed: error %u", err); 124 return nullptr; 125 } 126 } 127 return c; 128} 129 130Context::Context(const std::string &steam_install, const std::string &steamvr_install, u_logging_level level) 131 : settings(steam_install, steamvr_install, this), resources(level, steamvr_install), log_level(level) 132{} 133 134Context::~Context() 135{ 136 for (vr::IServerTrackedDeviceProvider *const &provider : providers) 137 provider->Cleanup(); 138} 139 140/***** IVRDriverContext methods *****/ 141 142void * 143Context::GetGenericInterface(const char *pchInterfaceVersion, vr::EVRInitError *peError) 144{ 145 CTX_DEBUG("Requested interface %s", pchInterfaceVersion); 146#define MATCH_INTERFACE(version, interface) \ 147 if (std::strcmp(pchInterfaceVersion, version) == 0) { \ 148 return interface; \ 149 } 150#define MATCH_INTERFACE_THIS(interface) MATCH_INTERFACE(interface##_Version, static_cast<interface *>(this)) 151 152 // Known interfaces 153 MATCH_INTERFACE_THIS(vr::IVRServerDriverHost); 154 MATCH_INTERFACE_THIS(vr::IVRDriverInput); 155 // This interface is not in a public header yet, but just passing IVRDriverInput_003 seems to work. 156 MATCH_INTERFACE("IVRDriverInput_004", static_cast<vr::IVRDriverInput *>(this)); 157 MATCH_INTERFACE_THIS(vr::IVRProperties); 158 MATCH_INTERFACE_THIS(vr::IVRDriverLog); 159 MATCH_INTERFACE(vr::IVRSettings_Version, &settings); 160 MATCH_INTERFACE(vr::IVRResources_Version, &resources); 161 MATCH_INTERFACE(vr::IVRIOBuffer_Version, &iobuf); 162 MATCH_INTERFACE(vr::IVRDriverManager_Version, &man); 163 MATCH_INTERFACE(vr::IVRBlockQueue_Version, &blockqueue); 164 MATCH_INTERFACE(vr::IVRPaths_Version, &paths); 165 // This version of the interface is not in a public header. 166 // Luckily it seems to be compatible with the previous version. 167 MATCH_INTERFACE("IVRPaths_002", &paths); 168 169 // Internal interfaces 170 MATCH_INTERFACE("IVRServer_XXX", &server); 171 MATCH_INTERFACE("IVRServerInternal_XXX", &server); 172 return nullptr; 173} 174 175vr::DriverHandle_t 176Context::GetDriverHandle() 177{ 178 return 1; 179} 180 181 182/***** IVRServerDriverHost methods *****/ 183 184bool 185Context::setup_hmd(const char *serial, vr::ITrackedDeviceServerDriver *driver) 186{ 187 this->hmd = new HmdDevice(DeviceBuilder{this->shared_from_this(), driver, serial, STEAM_INSTALL_DIR}); 188#define VERIFY(expr, msg) \ 189 if (!(expr)) { \ 190 CTX_ERR("Activating HMD failed: %s", msg); \ 191 delete this->hmd; \ 192 this->hmd = nullptr; \ 193 return false; \ 194 } 195 vr::EVRInitError err = driver->Activate(0); 196 VERIFY(err == vr::VRInitError_None, std::to_string(err).c_str()); 197 198 auto *display = static_cast<vr::IVRDisplayComponent *>(driver->GetComponent(vr::IVRDisplayComponent_Version3)); 199 if (display == NULL) { 200 display = static_cast<vr::IVRDisplayComponent *>(driver->GetComponent(vr::IVRDisplayComponent_Version)); 201 } 202 VERIFY(display, "IVRDisplayComponent is null"); 203#undef VERIFY 204 205 auto hmd_parts = std::make_unique<HmdDevice::Parts>(); 206 hmd_parts->base.view_count = 2; 207 for (size_t idx = 0; idx < 2; ++idx) { 208 vr::EVREye eye = (idx == 0) ? vr::Eye_Left : vr::Eye_Right; 209 xrt_view &view = hmd_parts->base.views[idx]; 210 211 display->GetEyeOutputViewport(eye, &view.viewport.x_pixels, &view.viewport.y_pixels, 212 &view.viewport.w_pixels, &view.viewport.h_pixels); 213 214 view.display.w_pixels = view.viewport.w_pixels; 215 view.display.h_pixels = view.viewport.h_pixels; 216 view.rot = u_device_rotation_ident; 217 } 218 219 hmd_parts->base.screens[0].w_pixels = 220 hmd_parts->base.views[0].display.w_pixels + hmd_parts->base.views[1].display.w_pixels; 221 hmd_parts->base.screens[0].h_pixels = hmd_parts->base.views[0].display.h_pixels; 222 // nominal frame interval will be set when lighthouse gives us the display frequency 223 // see HmdDevice::handle_property_write 224 225 hmd_parts->base.blend_modes[0] = XRT_BLEND_MODE_OPAQUE; 226 hmd_parts->base.blend_mode_count = 1; 227 228 auto &distortion = hmd_parts->base.distortion; 229 distortion.models = XRT_DISTORTION_MODEL_COMPUTE; 230 distortion.preferred = XRT_DISTORTION_MODEL_COMPUTE; 231 for (size_t idx = 0; idx < 2; ++idx) { 232 xrt_fov &fov = distortion.fov[idx]; 233 float tan_left, tan_right, tan_top, tan_bottom; 234 display->GetProjectionRaw((vr::EVREye)idx, &tan_left, &tan_right, &tan_top, &tan_bottom); 235 fov.angle_left = atanf(tan_left); 236 fov.angle_right = atanf(tan_right); 237 fov.angle_up = atanf(tan_bottom); 238 fov.angle_down = atanf(tan_top); 239 } 240 241 hmd_parts->display = display; 242 hmd->set_hmd_parts(std::move(hmd_parts)); 243 244 return true; 245} 246 247bool 248Context::setup_controller(const char *serial, vr::ITrackedDeviceServerDriver *driver) 249{ 250 // Find the first available slot for a new controller 251 size_t device_idx = 0; 252 for (; device_idx < MAX_CONTROLLERS; ++device_idx) { 253 if (!controller[device_idx]) 254 break; 255 } 256 257 // Check if we've exceeded the maximum number of controllers 258 if (device_idx == MAX_CONTROLLERS) { 259 CTX_WARN("Attempted to activate more than %zu controllers - this is unsupported", MAX_CONTROLLERS); 260 return false; 261 } 262 263 // Create the new controller 264 controller[device_idx] = new ControllerDevice( 265 device_idx + 1, DeviceBuilder{this->shared_from_this(), driver, serial, STEAM_INSTALL_DIR}); 266 267 vr::EVRInitError err = driver->Activate(device_idx + 1); 268 if (err != vr::VRInitError_None) { 269 CTX_ERR("Activating controller failed: error %u", err); 270 return false; 271 } 272 273 enum xrt_device_name name = controller[device_idx]->name; 274 switch (name) { 275 case XRT_DEVICE_VIVE_WAND: 276 controller[device_idx]->binding_profiles = vive_binding_profiles_wand; 277 controller[device_idx]->binding_profile_count = vive_binding_profiles_wand_count; 278 break; 279 280 break; 281 case XRT_DEVICE_INDEX_CONTROLLER: 282 controller[device_idx]->binding_profiles = vive_binding_profiles_index; 283 controller[device_idx]->binding_profile_count = vive_binding_profiles_index_count; 284 break; 285 default: break; 286 } 287 288 return true; 289} 290 291void 292Context::run_frame() 293{ 294 for (vr::IServerTrackedDeviceProvider *const &provider : providers) 295 provider->RunFrame(); 296} 297 298void 299Context::maybe_run_frame(uint64_t new_frame) 300{ 301 if (new_frame > current_frame) { 302 ++current_frame; 303 run_frame(); 304 } 305} 306// NOLINTBEGIN(bugprone-easily-swappable-parameters) 307bool 308Context::TrackedDeviceAdded(const char *pchDeviceSerialNumber, 309 vr::ETrackedDeviceClass eDeviceClass, 310 vr::ITrackedDeviceServerDriver *pDriver) 311{ 312 CTX_INFO("New device added: %s", pchDeviceSerialNumber); 313 switch (eDeviceClass) { 314 case vr::TrackedDeviceClass_HMD: { 315 CTX_INFO("Found lighthouse HMD: %s", pchDeviceSerialNumber); 316 return setup_hmd(pchDeviceSerialNumber, pDriver); 317 } 318 case vr::TrackedDeviceClass_Controller: { 319 CTX_INFO("Found lighthouse controller: %s", pchDeviceSerialNumber); 320 return setup_controller(pchDeviceSerialNumber, pDriver); 321 } 322 case vr::TrackedDeviceClass_TrackingReference: { 323 CTX_INFO("Found lighthouse base station: %s", pchDeviceSerialNumber); 324 return false; 325 } 326 case vr::TrackedDeviceClass_GenericTracker: { 327 CTX_INFO("Found lighthouse tracker: %s", pchDeviceSerialNumber); 328 return setup_controller(pchDeviceSerialNumber, pDriver); 329 } 330 default: { 331 CTX_WARN("Attempted to add unsupported device class: %u", eDeviceClass); 332 return false; 333 } 334 } 335} 336 337void 338Context::TrackedDevicePoseUpdated(uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize) 339{ 340 assert(sizeof(newPose) == unPoseStructSize); 341 342 // Check for valid device index, allowing for the HMD plus up to MAX_CONTROLLERS controllers 343 if (unWhichDevice > MAX_CONTROLLERS) 344 return; 345 346 Device *dev = nullptr; 347 348 // If unWhichDevice is 0, it refers to the HMD; otherwise, it refers to one of the controllers 349 if (unWhichDevice == 0) { 350 dev = static_cast<Device *>(this->hmd); 351 } else { 352 // unWhichDevice - 1 will give the index into the controller array 353 dev = static_cast<Device *>(this->controller[unWhichDevice - 1]); 354 } 355 356 assert(dev); 357 dev->update_pose(newPose); 358} 359 360void 361Context::VsyncEvent(double vsyncTimeOffsetSeconds) 362{} 363 364void 365Context::VendorSpecificEvent(uint32_t unWhichDevice, 366 vr::EVREventType eventType, 367 const vr::VREvent_Data_t &eventData, 368 double eventTimeOffset) 369{ 370 std::lock_guard lk(event_queue_mut); 371 events.push_back({std::chrono::steady_clock::now(), 372 { 373 .eventType = eventType, 374 .trackedDeviceIndex = unWhichDevice, 375 .eventAgeSeconds = {}, 376 .data = eventData, 377 }}); 378} 379 380bool 381Context::IsExiting() 382{ 383 return false; 384} 385 386void 387Context::add_haptic_event(vr::VREvent_HapticVibration_t event) 388{ 389 vr::VREvent_t e; 390 e.eventType = vr::EVREventType::VREvent_Input_HapticVibration; 391 e.trackedDeviceIndex = event.containerHandle - 1; 392 vr::VREvent_Data_t d; 393 d.hapticVibration = event; 394 e.data = d; 395 396 std::lock_guard lk(event_queue_mut); 397 events.push_back({std::chrono::steady_clock::now(), e}); 398} 399 400bool 401Context::PollNextEvent(vr::VREvent_t *pEvent, uint32_t uncbVREvent) 402{ 403 if (!events.empty()) { 404 assert(sizeof(vr::VREvent_t) == uncbVREvent); 405 Event e; 406 { 407 std::lock_guard lk(event_queue_mut); 408 e = events.front(); 409 events.pop_front(); 410 } 411 *pEvent = e.inner; 412 using float_sec = std::chrono::duration<float>; 413 float_sec event_age = std::chrono::steady_clock::now() - e.insert_time; 414 pEvent->eventAgeSeconds = event_age.count(); 415 return true; 416 } 417 return false; 418} 419 420void 421Context::GetRawTrackedDevicePoses(float fPredictedSecondsFromNow, 422 vr::TrackedDevicePose_t *pTrackedDevicePoseArray, 423 uint32_t unTrackedDevicePoseArrayCount) 424{ 425 // This is the bare minimum required for SlimeVR's HMD feedback to work 426 if (unTrackedDevicePoseArrayCount != 10 || this->hmd == nullptr) 427 return; 428 const uint64_t time = 429 std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()) 430 .count(); 431 xrt_space_relation head = {}; 432 xrt_device_get_tracked_pose(this->hmd, XRT_INPUT_GENERIC_HEAD_POSE, time, &head); 433 xrt_matrix_3x3 rot = {}; 434 math_matrix_3x3_from_quat(&head.pose.orientation, &rot); 435 pTrackedDevicePoseArray[0].mDeviceToAbsoluteTracking = {{ 436 {rot.v[0], rot.v[3], rot.v[6], head.pose.position.x}, 437 {rot.v[1], rot.v[4], rot.v[7], head.pose.position.y}, 438 {rot.v[2], rot.v[5], rot.v[8], head.pose.position.z}, 439 }}; 440} 441 442void 443Context::RequestRestart(const char *pchLocalizedReason, 444 const char *pchExecutableToStart, 445 const char *pchArguments, 446 const char *pchWorkingDirectory) 447{} 448 449uint32_t 450Context::GetFrameTimings(vr::Compositor_FrameTiming *pTiming, uint32_t nFrames) 451{ 452 return 0; 453} 454 455void 456Context::SetDisplayEyeToHead(uint32_t unWhichDevice, 457 const vr::HmdMatrix34_t &eyeToHeadLeft, 458 const vr::HmdMatrix34_t &eyeToHeadRight) 459{ 460 hmd->SetDisplayEyeToHead(unWhichDevice, eyeToHeadLeft, eyeToHeadRight); 461} 462 463void 464Context::SetDisplayProjectionRaw(uint32_t unWhichDevice, const vr::HmdRect2_t &eyeLeft, const vr::HmdRect2_t &eyeRight) 465{} 466 467void 468Context::SetRecommendedRenderTargetSize(uint32_t unWhichDevice, uint32_t nWidth, uint32_t nHeight) 469{} 470 471/***** IVRDriverInput methods *****/ 472 473 474vr::EVRInputError 475Context::create_component_common(vr::PropertyContainerHandle_t container, 476 const char *name, 477 vr::VRInputComponentHandle_t *pHandle) 478{ 479 *pHandle = vr::k_ulInvalidInputComponentHandle; 480 Device *device = prop_container_to_device(container); 481 if (!device) { 482 return vr::VRInputError_InvalidHandle; 483 } 484 if (xrt_input *input = device->get_input_from_name(name); input) { 485 CTX_DEBUG("creating component %s for %p", name, (void *)device); 486 vr::VRInputComponentHandle_t handle = new_handle(); 487 handle_to_input[handle] = input; 488 *pHandle = handle; 489 } 490 return vr::VRInputError_None; 491} 492 493xrt_input * 494Context::update_component_common(vr::VRInputComponentHandle_t handle, 495 double offset, 496 std::chrono::steady_clock::time_point now) 497{ 498 xrt_input *input{nullptr}; 499 if (handle != vr::k_ulInvalidInputComponentHandle) { 500 input = handle_to_input[handle]; 501 std::chrono::duration<double, std::chrono::seconds::period> offset_dur(offset); 502 std::chrono::duration offset = (now + offset_dur).time_since_epoch(); 503 int64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(offset).count(); 504 input->active = true; 505 input->timestamp = timestamp; 506 } 507 return input; 508} 509 510vr::EVRInputError 511Context::CreateBooleanComponent(vr::PropertyContainerHandle_t ulContainer, 512 const char *pchName, 513 vr::VRInputComponentHandle_t *pHandle) 514{ 515 return create_component_common(ulContainer, pchName, pHandle); 516} 517 518vr::EVRInputError 519Context::UpdateBooleanComponent(vr::VRInputComponentHandle_t ulComponent, bool bNewValue, double fTimeOffset) 520{ 521 xrt_input *input = update_component_common(ulComponent, fTimeOffset); 522 if (input) { 523 input->value.boolean = bNewValue; 524 } 525 return vr::VRInputError_None; 526} 527 528vr::EVRInputError 529Context::CreateScalarComponent(vr::PropertyContainerHandle_t ulContainer, 530 const char *pchName, 531 vr::VRInputComponentHandle_t *pHandle, 532 vr::EVRScalarType eType, 533 vr::EVRScalarUnits eUnits) 534{ 535 std::string_view name{pchName}; 536 // Lighthouse gives thumbsticks/trackpads as x/y components, 537 // we need to combine them for Monado 538 auto end = name.back(); 539 auto second_last = name.at(name.size() - 2); 540 if (second_last == '/' && (end == 'x' || end == 'y')) { 541 Device *device = prop_container_to_device(ulContainer); 542 if (!device) { 543 return vr::VRInputError_InvalidHandle; 544 } 545 bool x = end == 'x'; 546 name.remove_suffix(2); 547 std::string n(name); 548 xrt_input *input = device->get_input_from_name(n); 549 if (!input) { 550 return vr::VRInputError_None; 551 } 552 553 // Create the component mapping if it hasn't been created yet 554 Vec2Components *components = 555 vec2_input_to_components.try_emplace(input, new Vec2Components).first->second.get(); 556 557 vr::VRInputComponentHandle_t new_handle = this->new_handle(); 558 if (x) 559 components->x = new_handle; 560 else 561 components->y = new_handle; 562 563 handle_to_input[new_handle] = input; 564 *pHandle = new_handle; 565 return vr::VRInputError_None; 566 } 567 return create_component_common(ulContainer, pchName, pHandle); 568} 569 570vr::EVRInputError 571Context::UpdateScalarComponent(vr::VRInputComponentHandle_t ulComponent, float fNewValue, double fTimeOffset) 572{ 573 if (auto h = handle_to_input.find(ulComponent); h != handle_to_input.end() && h->second) { 574 xrt_input *input = update_component_common(ulComponent, fTimeOffset); 575 if (XRT_GET_INPUT_TYPE(input->name) == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) { 576 std::unique_ptr<Vec2Components> &components = vec2_input_to_components.at(input); 577 if (components->x == ulComponent) { 578 input->value.vec2.x = fNewValue; 579 } else if (components->y == ulComponent) { 580 input->value.vec2.y = fNewValue; 581 } else { 582 CTX_WARN("Attempted to update component with handle %" PRIu64 583 " but it was neither the x nor y " 584 "component of its associated input", 585 ulComponent); 586 } 587 588 } else { 589 input->value.vec1.x = fNewValue; 590 } 591 } 592 return vr::VRInputError_None; 593} 594 595vr::EVRInputError 596Context::CreateHapticComponent(vr::PropertyContainerHandle_t ulContainer, 597 const char *pchName, 598 vr::VRInputComponentHandle_t *pHandle) 599{ 600 *pHandle = vr::k_ulInvalidInputComponentHandle; 601 Device *d = prop_container_to_device(ulContainer); 602 if (!d) { 603 return vr::VRInputError_InvalidHandle; 604 } 605 606 // Assuming HMDs won't have haptics. 607 // Maybe a wrong assumption. 608 if (d == hmd) { 609 CTX_WARN("Didn't expect HMD with haptics."); 610 return vr::VRInputError_InvalidHandle; 611 } 612 613 auto *device = static_cast<ControllerDevice *>(d); 614 vr::VRInputComponentHandle_t handle = new_handle(); 615 handle_to_input[handle] = nullptr; 616 device->set_haptic_handle(handle); 617 *pHandle = handle; 618 619 return vr::VRInputError_None; 620} 621 622vr::EVRInputError 623Context::CreateSkeletonComponent(vr::PropertyContainerHandle_t ulContainer, 624 const char *pchName, 625 const char *pchSkeletonPath, 626 const char *pchBasePosePath, 627 vr::EVRSkeletalTrackingLevel eSkeletalTrackingLevel, 628 const vr::VRBoneTransform_t *pGripLimitTransforms, 629 uint32_t unGripLimitTransformCount, 630 vr::VRInputComponentHandle_t *pHandle) 631{ 632 std::string_view path(pchSkeletonPath); // should be /skeleton/hand/left or /skeleton/hand/right 633 std::string_view skeleton_pfx("/skeleton/hand/"); 634 if (!path.starts_with(skeleton_pfx)) { 635 CTX_ERR("Got invalid skeleton path: %s", std::string(path).c_str()); 636 return vr::VRInputError_InvalidSkeleton; 637 } 638 639 if (auto ret = create_component_common(ulContainer, pchSkeletonPath, pHandle); ret != vr::VRInputError_None) { 640 return ret; 641 } 642 643 auto *device = static_cast<ControllerDevice *>(prop_container_to_device(ulContainer)); 644 path.remove_prefix(skeleton_pfx.size()); 645 xrt_hand hand; 646 if (path == "left") { 647 hand = XRT_HAND_LEFT; 648 } else if (path == "right") { 649 hand = XRT_HAND_RIGHT; 650 } else { 651 CTX_ERR("Got invalid skeleton path suffix: %s", std::string(path).c_str()); 652 return vr::VRInputError_InvalidSkeleton; 653 } 654 655 device->set_skeleton(std::span(pGripLimitTransforms, unGripLimitTransformCount), hand, 656 eSkeletalTrackingLevel == vr::VRSkeletalTracking_Estimated, pchSkeletonPath); 657 skeleton_to_controller[*pHandle] = device; 658 659 return vr::VRInputError_None; 660} 661 662vr::EVRInputError 663Context::UpdateSkeletonComponent(vr::VRInputComponentHandle_t ulComponent, 664 vr::EVRSkeletalMotionRange eMotionRange, 665 const vr::VRBoneTransform_t *pTransforms, 666 uint32_t unTransformCount) 667{ 668 if (eMotionRange != vr::VRSkeletalMotionRange_WithoutController) { 669 return vr::VRInputError_None; 670 } 671 672 if (!update_component_common(ulComponent, 0)) { 673 return vr::VRInputError_InvalidHandle; 674 } 675 676 auto *device = skeleton_to_controller[ulComponent]; 677 if (!device) { 678 CTX_ERR("Got unknown component handle %lu", ulComponent); 679 return vr::VRInputError_InvalidHandle; 680 } 681 682 device->update_skeleton_transforms(std::span(pTransforms, unTransformCount)); 683 684 return vr::VRInputError_None; 685} 686 687/***** IVRProperties methods *****/ 688 689vr::ETrackedPropertyError 690Context::ReadPropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, 691 vr::PropertyRead_t *pBatch, 692 uint32_t unBatchEntryCount) 693{ 694 Device *device = prop_container_to_device(ulContainerHandle); 695 if (!device) 696 return vr::TrackedProp_InvalidContainer; 697 if (!pBatch) 698 return vr::TrackedProp_InvalidOperation; // not verified vs steamvr 699 return device->handle_read_properties(pBatch, unBatchEntryCount); 700} 701 702vr::ETrackedPropertyError 703Context::WritePropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, 704 vr::PropertyWrite_t *pBatch, 705 uint32_t unBatchEntryCount) 706{ 707 Device *device = prop_container_to_device(ulContainerHandle); 708 if (!device) 709 return vr::TrackedProp_InvalidContainer; 710 if (!pBatch) 711 return vr::TrackedProp_InvalidOperation; // not verified vs steamvr 712 return device->handle_properties(pBatch, unBatchEntryCount); 713} 714 715const char * 716Context::GetPropErrorNameFromEnum(vr::ETrackedPropertyError error) 717{ 718 return nullptr; 719} 720 721Device * 722Context::prop_container_to_device(vr::PropertyContainerHandle_t handle) 723{ 724 switch (handle) { 725 case 1: { 726 return hmd; 727 break; 728 } 729 default: { 730 // If the handle corresponds to a controller 731 if (handle >= 2 && handle <= MAX_CONTROLLERS + 1) { 732 return controller[handle - 2]; 733 } else { 734 return nullptr; 735 } 736 break; 737 } 738 } 739} 740 741vr::PropertyContainerHandle_t 742Context::TrackedDeviceToPropertyContainer(vr::TrackedDeviceIndex_t nDevice) 743{ 744 size_t container = nDevice + 1; 745 if (nDevice == 0 && this->hmd) { 746 return container; 747 } 748 if (nDevice >= 1 && nDevice <= MAX_CONTROLLERS && this->controller[nDevice - 1]) { 749 return container; 750 } 751 752 return vr::k_ulInvalidPropertyContainer; 753} 754 755void 756Context::Log(const char *pchLogMessage) 757{ 758 CTX_TRACE("[lighthouse]: %s", pchLogMessage); 759} 760// NOLINTEND(bugprone-easily-swappable-parameters) 761 762xrt_result_t 763get_roles(struct xrt_system_devices *xsysd, struct xrt_system_roles *out_roles) 764{ 765 bool update_gen = false; 766 int head, left, right, gamepad; 767 768 u_device_assign_xdev_roles(xsysd->xdevs, xsysd->xdev_count, &head, &left, &right, &gamepad); 769 770 if (left != out_roles->left || right != out_roles->right || gamepad != out_roles->gamepad) { 771 update_gen = true; 772 } 773 774 if (update_gen) { 775 out_roles->generation_id++; 776 777 out_roles->left = left; 778 out_roles->right = right; 779 out_roles->gamepad = gamepad; 780 781 if (left != XRT_DEVICE_ROLE_UNASSIGNED) { 782 auto *left_dev = static_cast<ControllerDevice *>(xsysd->xdevs[left]); 783 left_dev->set_active_hand(XRT_HAND_LEFT); 784 } 785 786 if (right != XRT_DEVICE_ROLE_UNASSIGNED) { 787 auto *right_dev = static_cast<ControllerDevice *>(xsysd->xdevs[right]); 788 right_dev->set_active_hand(XRT_HAND_RIGHT); 789 } 790 } 791 792 return XRT_SUCCESS; 793} 794 795void 796destroy(struct xrt_system_devices *xsysd) 797{ 798 for (uint32_t i = 0; i < ARRAY_SIZE(xsysd->xdevs); i++) { 799 xrt_device_destroy(&xsysd->xdevs[i]); 800 } 801 802 svrs->ctx.reset(); 803 free(svrs); 804} 805 806extern "C" enum xrt_result 807steamvr_lh_create_devices(struct xrt_prober *xp, struct xrt_system_devices **out_xsysd) 808{ 809 u_logging_level level = debug_get_log_option_lh_log(); 810 // The driver likes to create a bunch of transient folders - 811 // let's try to make sure they're created where they normally are. 812 std::filesystem::path dir = STEAM_INSTALL_DIR + "/config/lighthouse"; 813 if (!std::filesystem::exists(dir)) { 814 U_LOG_IFL_W(level, 815 "Couldn't find lighthouse config folder (%s)- transient folders will be created in current " 816 "working directory (%s)", 817 dir.c_str(), std::filesystem::current_path().c_str()); 818 } else { 819 std::filesystem::current_path(dir); 820 } 821 822 std::string steamvr{}; 823 if (getenv("STEAMVR_PATH") != nullptr) { 824 steamvr = getenv("STEAMVR_PATH"); 825 } else { 826 steamvr = find_steamvr_install(); 827 } 828 829 if (steamvr.empty()) { 830 U_LOG_IFL_E(level, "Could not find where SteamVR is installed!"); 831 return xrt_result::XRT_ERROR_DEVICE_CREATION_FAILED; 832 } 833 834 U_LOG_IFL_I(level, "Found SteamVR install: %s", steamvr.c_str()); 835 836 std::vector<vr::IServerTrackedDeviceProvider *> drivers = {}; 837 const auto loadDriver = [&](std::string soPath, bool require) { 838 // TODO: support windows? 839 void *driver_lib = dlopen((steamvr + soPath).c_str(), RTLD_LAZY); 840 if (!driver_lib) { 841 U_LOG_IFL_E(level, "Couldn't open driver lib: %s", dlerror()); 842 return !require; 843 } 844 845 void *sym = dlsym(driver_lib, "HmdDriverFactory"); 846 if (!sym) { 847 U_LOG_IFL_E(level, "Couldn't find HmdDriverFactory in driver lib: %s", dlerror()); 848 return false; 849 } 850 using HmdDriverFactory_t = void *(*)(const char *, int *); 851 auto factory = reinterpret_cast<HmdDriverFactory_t>(sym); 852 853 vr::EVRInitError err = vr::VRInitError_None; 854 drivers.push_back(static_cast<vr::IServerTrackedDeviceProvider *>( 855 factory(vr::IServerTrackedDeviceProvider_Version, (int *)&err))); 856 if (err != vr::VRInitError_None) { 857 U_LOG_IFL_E(level, "Couldn't get tracked device driver: error %u", err); 858 return false; 859 } 860 return true; 861 }; 862 if (!loadDriver("/drivers/lighthouse/bin/linux64/driver_lighthouse.so", true)) 863 return xrt_result::XRT_ERROR_DEVICE_CREATION_FAILED; 864 if (debug_get_bool_option_lh_load_slimevr() && 865 !loadDriver("/drivers/slimevr/bin/linux64/driver_slimevr.so", false)) 866 return xrt_result::XRT_ERROR_DEVICE_CREATION_FAILED; 867 svrs->ctx = Context::create(STEAM_INSTALL_DIR, steamvr, std::move(drivers)); 868 if (svrs->ctx == nullptr) 869 return xrt_result::XRT_ERROR_DEVICE_CREATION_FAILED; 870 871 U_LOG_IFL_I(level, "Lighthouse initialization complete, giving time to setup connected devices..."); 872 // RunFrame needs to be called to detect controllers 873 using namespace std::chrono_literals; 874 auto end_time = std::chrono::steady_clock::now() + 1ms * debug_get_num_option_lh_discover_wait_ms(); 875 while (true) { 876 svrs->ctx->run_frame(); 877 auto cur_time = std::chrono::steady_clock::now(); 878 if (cur_time > end_time) { 879 break; 880 } 881 std::this_thread::sleep_for(20ms); 882 } 883 U_LOG_IFL_I(level, "Device search time complete."); 884 885 if (out_xsysd == NULL || *out_xsysd != NULL) { 886 U_LOG_IFL_E(level, "Invalid output system pointer"); 887 return xrt_result::XRT_ERROR_DEVICE_CREATION_FAILED; 888 } 889 890 struct xrt_system_devices *xsysd = NULL; 891 xsysd = &svrs->base; 892 893 xsysd->destroy = destroy; 894 xsysd->get_roles = get_roles; 895 896 // Include the HMD 897 if (svrs->ctx->hmd) { 898 if (svrs->ctx->hmd->variant == VIVE_VARIANT_PRO2 && !svrs->ctx->hmd->init_vive_pro_2(xp)) { 899 U_LOG_IFL_W(level, "Found Vive Pro 2, but failed to initialize."); 900 } 901 902 // Always have a head at index 0 and iterate dev count. 903 xsysd->xdevs[xsysd->xdev_count] = svrs->ctx->hmd; 904 xsysd->static_roles.head = xsysd->xdevs[xsysd->xdev_count++]; 905 } 906 907 // Include the controllers 908 for (size_t i = 0; i < MAX_CONTROLLERS; i++) { 909 if (svrs->ctx->controller[i]) { 910 xsysd->xdevs[xsysd->xdev_count++] = svrs->ctx->controller[i]; 911 } 912 } 913 914 *out_xsysd = xsysd; 915 916 return xrt_result::XRT_SUCCESS; 917}