diff --git a/CMakeLists.txt b/CMakeLists.txt index cfe823a3c..dd4397f00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,6 +215,11 @@ set(ILLIXR_PATH # This one is named differently because that's what CTest uses option(BUILD_TESTING "Enable building of the test suite?" ON) +# Check if Steam's root folder exists +if(EXISTS "$ENV{HOME}/.steam/root") + set(XRT_HAVE_STEAM YES) +endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(XRT_HAVE_INTERNAL_HID ON) endif() @@ -336,6 +341,7 @@ option_with_deps(XRT_BUILD_DRIVER_QWERTY "Enable Qwerty driver" DEPENDS XRT_HAVE option_with_deps(XRT_BUILD_DRIVER_REALSENSE "Enable RealSense device driver" DEPENDS XRT_HAVE_REALSENSE) option_with_deps(XRT_BUILD_DRIVER_REMOTE "Enable remote debugging driver" DEPENDS "XRT_HAVE_LINUX OR ANDROID OR WIN32") option_with_deps(XRT_BUILD_DRIVER_RIFT_S "Enable Oculus Rift S device driver" DEPENDS XRT_HAVE_HIDAPI XRT_HAVE_V4L2) +option_with_deps(XRT_BUILD_DRIVER_STEAMVR_LIGHTHOUSE "Enable SteamVR Lighthouse driver" DEPENDS XRT_HAVE_LINUX XRT_HAVE_STEAM XRT_MODULE_AUX_VIVE) option_with_deps(XRT_BUILD_DRIVER_SURVIVE "Enable libsurvive driver" DEPENDS SURVIVE_FOUND XRT_MODULE_AUX_VIVE) option_with_deps(XRT_BUILD_DRIVER_ULV2 "Enable Ultraleap v2 driver" DEPENDS LeapV2_FOUND) option_with_deps(XRT_BUILD_DRIVER_VF "Build video frame driver (for video file support, uses gstreamer)" DEPENDS XRT_HAVE_GST) @@ -404,6 +410,7 @@ list( "EUROC" "SIMULAVR" "TWRAP" + "STEAMVR_LIGHTHOUSE" ) # Package name needs to be known by the native code itself. @@ -566,33 +573,34 @@ message(STATUS "# FEATURE_STEAMVR_PLUGIN: ${XRT_FEATURE_STEAMVR message(STATUS "# FEATURE_TRACING: ${XRT_FEATURE_TRACING}") message(STATUS "# FEATURE_WINDOW_PEEK: ${XRT_FEATURE_WINDOW_PEEK}") message(STATUS "#") -message(STATUS "# DRIVER_ANDROID: ${XRT_BUILD_DRIVER_ANDROID}") -message(STATUS "# DRIVER_ARDUINO: ${XRT_BUILD_DRIVER_ARDUINO}") -message(STATUS "# DRIVER_DAYDREAM: ${XRT_BUILD_DRIVER_DAYDREAM}") -message(STATUS "# DRIVER_DEPTHAI: ${XRT_BUILD_DRIVER_DEPTHAI}") -message(STATUS "# DRIVER_EUROC: ${XRT_BUILD_DRIVER_EUROC}") -message(STATUS "# DRIVER_HANDTRACKING: ${XRT_BUILD_DRIVER_HANDTRACKING}") -message(STATUS "# DRIVER_HDK: ${XRT_BUILD_DRIVER_HDK}") -message(STATUS "# DRIVER_HYDRA: ${XRT_BUILD_DRIVER_HYDRA}") -message(STATUS "# DRIVER_ILLIXR: ${XRT_BUILD_DRIVER_ILLIXR}") -message(STATUS "# DRIVER_NS: ${XRT_BUILD_DRIVER_NS}") -message(STATUS "# DRIVER_OHMD: ${XRT_BUILD_DRIVER_OHMD}") -message(STATUS "# DRIVER_OPENGLOVES: ${XRT_BUILD_DRIVER_OPENGLOVES}") -message(STATUS "# DRIVER_PSMV: ${XRT_BUILD_DRIVER_PSMV}") -message(STATUS "# DRIVER_PSSENSE: ${XRT_BUILD_DRIVER_PSSENSE}") -message(STATUS "# DRIVER_PSVR: ${XRT_BUILD_DRIVER_PSVR}") -message(STATUS "# DRIVER_QWERTY: ${XRT_BUILD_DRIVER_QWERTY}") -message(STATUS "# DRIVER_REALSENSE: ${XRT_BUILD_DRIVER_REALSENSE}") -message(STATUS "# DRIVER_REMOTE: ${XRT_BUILD_DRIVER_REMOTE}") -message(STATUS "# DRIVER_RIFT_S: ${XRT_BUILD_DRIVER_RIFT_S}") -message(STATUS "# DRIVER_SIMULATED: ${XRT_BUILD_DRIVER_SIMULATED}") -message(STATUS "# DRIVER_SIMULAVR: ${XRT_BUILD_DRIVER_SIMULAVR}") -message(STATUS "# DRIVER_SURVIVE: ${XRT_BUILD_DRIVER_SURVIVE}") -message(STATUS "# DRIVER_TWRAP: ${XRT_BUILD_DRIVER_TWRAP}") -message(STATUS "# DRIVER_ULV2: ${XRT_BUILD_DRIVER_ULV2}") -message(STATUS "# DRIVER_VF: ${XRT_BUILD_DRIVER_VF}") -message(STATUS "# DRIVER_VIVE: ${XRT_BUILD_DRIVER_VIVE}") -message(STATUS "# DRIVER_WMR: ${XRT_BUILD_DRIVER_WMR}") +message(STATUS "# DRIVER_ANDROID: ${XRT_BUILD_DRIVER_ANDROID}") +message(STATUS "# DRIVER_ARDUINO: ${XRT_BUILD_DRIVER_ARDUINO}") +message(STATUS "# DRIVER_DAYDREAM: ${XRT_BUILD_DRIVER_DAYDREAM}") +message(STATUS "# DRIVER_DEPTHAI: ${XRT_BUILD_DRIVER_DEPTHAI}") +message(STATUS "# DRIVER_EUROC: ${XRT_BUILD_DRIVER_EUROC}") +message(STATUS "# DRIVER_HANDTRACKING: ${XRT_BUILD_DRIVER_HANDTRACKING}") +message(STATUS "# DRIVER_HDK: ${XRT_BUILD_DRIVER_HDK}") +message(STATUS "# DRIVER_HYDRA: ${XRT_BUILD_DRIVER_HYDRA}") +message(STATUS "# DRIVER_ILLIXR: ${XRT_BUILD_DRIVER_ILLIXR}") +message(STATUS "# DRIVER_NS: ${XRT_BUILD_DRIVER_NS}") +message(STATUS "# DRIVER_OHMD: ${XRT_BUILD_DRIVER_OHMD}") +message(STATUS "# DRIVER_OPENGLOVES: ${XRT_BUILD_DRIVER_OPENGLOVES}") +message(STATUS "# DRIVER_PSMV: ${XRT_BUILD_DRIVER_PSMV}") +message(STATUS "# DRIVER_PSSENSE: ${XRT_BUILD_DRIVER_PSSENSE}") +message(STATUS "# DRIVER_PSVR: ${XRT_BUILD_DRIVER_PSVR}") +message(STATUS "# DRIVER_QWERTY: ${XRT_BUILD_DRIVER_QWERTY}") +message(STATUS "# DRIVER_REALSENSE: ${XRT_BUILD_DRIVER_REALSENSE}") +message(STATUS "# DRIVER_REMOTE: ${XRT_BUILD_DRIVER_REMOTE}") +message(STATUS "# DRIVER_RIFT_S: ${XRT_BUILD_DRIVER_RIFT_S}") +message(STATUS "# DRIVER_SIMULATED: ${XRT_BUILD_DRIVER_SIMULATED}") +message(STATUS "# DRIVER_SIMULAVR: ${XRT_BUILD_DRIVER_SIMULAVR}") +message(STATUS "# DRIVER_SURVIVE: ${XRT_BUILD_DRIVER_SURVIVE}") +message(STATUS "# DRIVER_TWRAP: ${XRT_BUILD_DRIVER_TWRAP}") +message(STATUS "# DRIVER_ULV2: ${XRT_BUILD_DRIVER_ULV2}") +message(STATUS "# DRIVER_VF: ${XRT_BUILD_DRIVER_VF}") +message(STATUS "# DRIVER_VIVE: ${XRT_BUILD_DRIVER_VIVE}") +message(STATUS "# DRIVER_WMR: ${XRT_BUILD_DRIVER_WMR}") +message(STATUS "# DRIVER_STEAMVR_LIGHTHOUSE: ${XRT_BUILD_DRIVER_STEAMVR_LIGHTHOUSE}") message(STATUS "#####----- Config -----#####") if(XRT_FEATURE_SERVICE AND NOT XRT_FEATURE_OPENXR) diff --git a/src/xrt/drivers/CMakeLists.txt b/src/xrt/drivers/CMakeLists.txt index d199ca11d..fa551acfd 100644 --- a/src/xrt/drivers/CMakeLists.txt +++ b/src/xrt/drivers/CMakeLists.txt @@ -432,6 +432,32 @@ if(XRT_BUILD_DRIVER_SIMULAVR) list(APPEND ENABLED_HEADSET_DRIVERS svr) endif() +if(XRT_BUILD_DRIVER_STEAMVR_LIGHTHOUSE) + add_library( + drv_steamvr_lh STATIC + steamvr_lh/steamvr_lh.cpp + steamvr_lh/device.cpp + steamvr_lh/interfaces/driver_manager.cpp + steamvr_lh/interfaces/iobuffer.cpp + steamvr_lh/interfaces/resources.cpp + steamvr_lh/interfaces/settings.cpp + steamvr_lh/interfaces/blockqueue.cpp + steamvr_lh/interfaces/paths.cpp + ) + target_link_libraries( + drv_steamvr_lh + PRIVATE + aux_util + aux_math + xrt-interfaces + xrt-external-openvr + xrt-external-cjson + xrt-external-vdf + ${CMAKE_DL_LIBS} + ) + list(APPEND ENABLED_HEADSET_DRIVERS steamvr_lh) +endif() + if(XRT_BUILD_SAMPLES) # We build the sample driver to make sure it stays valid, # but it never gets linked into a final target. diff --git a/src/xrt/drivers/steamvr_lh/device.cpp b/src/xrt/drivers/steamvr_lh/device.cpp new file mode 100644 index 000000000..a8eeae07b --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/device.cpp @@ -0,0 +1,435 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief SteamVR driver device implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include <functional> +#include <cstring> +#include <thread> +#include <algorithm> + +#include "math/m_api.h" +#include "device.hpp" +#include "interfaces/context.hpp" +#include "util/u_device.h" +#include "util/u_logging.h" +#include "util/u_json.hpp" + +#define DEV_ERR(...) U_LOG_IFL_E(ctx->log_level, __VA_ARGS__) +#define DEV_WARN(...) U_LOG_IFL_W(ctx->log_level, __VA_ARGS__) +#define DEV_INFO(...) U_LOG_IFL_I(ctx->log_level, __VA_ARGS__) +#define DEV_DEBUG(...) U_LOG_IFL_D(ctx->log_level, __VA_ARGS__) + +// Each device will have its own input class. +struct InputClass +{ + xrt_device_name name; + std::string description; + const std::vector<xrt_input_name> poses; + const std::unordered_map<std::string_view, xrt_input_name> non_poses; +}; + +namespace { +InputClass hmd_class{{}, "Generic HMD", {XRT_INPUT_GENERIC_HEAD_POSE}, {}}; + +// Adding support for a new controller is a simple as adding it here. +// The key for the map needs to be the name of input profile as indicated by the lighthouse driver. +const std::unordered_map<std::string_view, InputClass> controller_classes{ + { + "vive_controller", + InputClass{ + XRT_DEVICE_VIVE_WAND, + "Vive Wand", + { + XRT_INPUT_VIVE_GRIP_POSE, + XRT_INPUT_VIVE_AIM_POSE, + }, + { + {"/input/application_menu/click", XRT_INPUT_VIVE_MENU_CLICK}, + {"/input/trackpad/click", XRT_INPUT_VIVE_TRACKPAD_CLICK}, + {"/input/trackpad/touch", XRT_INPUT_VIVE_TRACKPAD_TOUCH}, + {"/input/system/click", XRT_INPUT_VIVE_SYSTEM_CLICK}, + {"/input/trigger/click", XRT_INPUT_VIVE_TRIGGER_CLICK}, + {"/input/trigger/value", XRT_INPUT_VIVE_TRIGGER_VALUE}, + {"/input/grip/click", XRT_INPUT_VIVE_SQUEEZE_CLICK}, + {"/input/trackpad", XRT_INPUT_VIVE_TRACKPAD}, + }, + }, + }, +}; + +// Template for calling a member function of Device from a free function +template <typename DeviceType, auto Func, typename Ret, typename... Args> +inline Ret +device_bouncer(struct xrt_device *xdev, Args... args) +{ + auto *dev = static_cast<DeviceType *>(xdev); + return std::invoke(Func, dev, args...); +} +} // namespace + +HmdDevice::HmdDevice(const DeviceBuilder &builder) : Device(builder) +{ + this->name = XRT_DEVICE_GENERIC_HMD; + this->device_type = XRT_DEVICE_TYPE_HMD; + this->container_handle = 0; + + set_input_class(&hmd_class); + +#define SETUP_MEMBER_FUNC(name) this->xrt_device::name = &device_bouncer<HmdDevice, &HmdDevice::name> + SETUP_MEMBER_FUNC(get_view_poses); + SETUP_MEMBER_FUNC(compute_distortion); +#undef SETUP_MEMBER_FUNC +} + +ControllerDevice::ControllerDevice(vr::PropertyContainerHandle_t handle, const DeviceBuilder &builder) : Device(builder) +{ + this->device_type = XRT_DEVICE_TYPE_ANY_HAND_CONTROLLER; + this->container_handle = handle; + this->xrt_device::set_output = &device_bouncer<ControllerDevice, &ControllerDevice::set_output>; +} + +Device::Device(const DeviceBuilder &builder) : xrt_device({}), ctx(builder.ctx), driver(builder.driver) +{ + std::strncpy(this->serial, builder.serial, XRT_DEVICE_NAME_LEN); + this->tracking_origin = ctx.get(); + this->orientation_tracking_supported = true; + this->position_tracking_supported = true; + this->hand_tracking_supported = false; + this->force_feedback_supported = false; + this->form_factor_check_supported = false; + +#define SETUP_MEMBER_FUNC(name) this->xrt_device::name = &device_bouncer<Device, &Device::name> + SETUP_MEMBER_FUNC(update_inputs); + SETUP_MEMBER_FUNC(get_tracked_pose); +#undef SETUP_MEMBER_FUNC + + this->xrt_device::destroy = [](xrt_device *xdev) { + auto *dev = static_cast<Device *>(xdev); + dev->driver->Deactivate(); + delete dev; + }; + + init_chaperone(builder.steam_install); +} + +void +Device::set_input_class(const InputClass *input_class) +{ + // this should only be called once + assert(inputs_vec.empty()); + this->input_class = input_class; + + // reserve to ensure our pointers don't get invalidated + inputs_vec.reserve(input_class->poses.size() + input_class->non_poses.size()); + for (xrt_input_name input : input_class->poses) { + inputs_vec.push_back({true, 0, input, {}}); + } + for (const auto &[path, input] : input_class->non_poses) { + assert(inputs_vec.capacity() >= inputs_vec.size() + 1); + inputs_vec.push_back({true, 0, input, {}}); + inputs_map.insert({path, &inputs_vec.back()}); + } + + this->inputs = inputs_vec.data(); + this->input_count = inputs_vec.size(); +} + +xrt_input * +Device::get_input_from_name(const std::string_view name) +{ + auto input = inputs_map.find(name); + if (input == inputs_map.end()) { + DEV_WARN("requested unknown input name %s for device %s", std::string(name).c_str(), serial); + return nullptr; + } + return input->second; +} + +void +ControllerDevice::set_haptic_handle(vr::VRInputComponentHandle_t handle) +{ + // this should only be set once + assert(output == nullptr); + DEV_DEBUG("setting haptic handle for %lu", handle); + haptic_handle = handle; + xrt_output_name name; + switch (this->name) { + case XRT_DEVICE_VIVE_WAND: { + name = XRT_OUTPUT_NAME_VIVE_HAPTIC; + break; + } + default: { + DEV_WARN("Unknown device name (%u), haptics will not work", this->name); + return; + } + } + output = std::make_unique<xrt_output>(xrt_output{name}); + this->output_count = 1; + this->outputs = output.get(); +} + +void +Device::update_inputs() +{ + ctx->maybe_run_frame(++current_frame); +} + +void +Device::get_tracked_pose(xrt_input_name name, uint64_t at_timestamp_ns, xrt_space_relation *out_relation) +{ + *out_relation = this->relation; +} + +void +ControllerDevice::set_output(xrt_output_name name, const xrt_output_value *value) + +{ + const auto &vib = value->vibration; + if (vib.amplitude == 0.0) + return; + vr::VREvent_HapticVibration_t event; + event.containerHandle = container_handle; + event.componentHandle = haptic_handle; + event.fDurationSeconds = (float)vib.duration_ns / 1e9f; + // 0.0f in OpenXR means let the driver determine a frequency, but + // in OpenVR means no haptic. + event.fFrequency = std::max(vib.frequency, 1.0f); + event.fAmplitude = vib.amplitude; + + ctx->add_haptic_event(event); +} + +void +HmdDevice::get_view_poses(const xrt_vec3 *default_eye_relation, + uint64_t at_timestamp_ns, + uint32_t view_count, + xrt_space_relation *out_head_relation, + xrt_fov *out_fovs, + xrt_pose *out_poses) +{ + u_device_get_view_poses(this, default_eye_relation, at_timestamp_ns, view_count, out_head_relation, out_fovs, + out_poses); +} + +bool +HmdDevice::compute_distortion(uint32_t view, float u, float v, xrt_uv_triplet *out_result) +{ + vr::EVREye eye = (view == 0) ? vr::Eye_Left : vr::Eye_Right; + vr::DistortionCoordinates_t coords = this->hmd_parts->display->ComputeDistortion(eye, u, v); + out_result->r = {coords.rfRed[0], coords.rfRed[1]}; + out_result->g = {coords.rfGreen[0], coords.rfGreen[1]}; + out_result->b = {coords.rfBlue[0], coords.rfBlue[1]}; + return true; +} + +void +HmdDevice::set_hmd_parts(std::unique_ptr<Parts> parts) +{ + { + std::lock_guard lk(hmd_parts_mut); + hmd_parts = std::move(parts); + this->hmd = &hmd_parts->base; + } + hmd_parts_cv.notify_all(); +} + +namespace { +xrt_quat +copy_quat(const vr::HmdQuaternion_t &quat) +{ + return xrt_quat{(float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w}; +} + +xrt_vec3 +copy_vec3(const double (&vec)[3]) +{ + return xrt_vec3{(float)vec[0], (float)vec[1], (float)vec[2]}; +} +} // namespace + +void +Device::init_chaperone(const std::string &steam_install) +{ + static bool initialized = false; + if (initialized) + return; + + initialized = true; + + // Lighthouse driver seems to create a lighthousedb.json and a chaperone_info.vrchap (which is json) + // We will use the known_universes from the lighthousedb.json to match to a universe from chaperone_info.vrchap + + using xrt::auxiliary::util::json::JSONNode; + auto lighthousedb = JSONNode::loadFromFile(steam_install + "/config/lighthouse/lighthousedb.json"); + if (lighthousedb.isInvalid()) { + DEV_ERR("Couldn't load lighthousedb file, playspace center will be off - was Room Setup run?"); + return; + } + auto chap_info = JSONNode::loadFromFile(steam_install + "/config/chaperone_info.vrchap"); + if (chap_info.isInvalid()) { + DEV_ERR("Couldn't load chaperone info, playspace center will be off - was Room Setup run?"); + return; + } + + // XXX: This may be broken if there are multiple known universes - how do we determine which to use then? + JSONNode universe = lighthousedb["known_universes"][0]; + std::string id = universe["id"].asString(); + JSONNode info; + for (const JSONNode &u : chap_info["universes"].asArray()) { + if (u["universeID"].asString() == id) { + DEV_INFO("Found info for universe %s", id.c_str()); + info = u; + break; + } + } + + if (info.isInvalid()) { + DEV_ERR("Couldn't find chaperone info for universe %s, playspace center will be off", id.c_str()); + return; + } + + std::vector<JSONNode> translation_arr = info["standing"]["translation"].asArray(); + double yaw = info["standing"]["yaw"].asDouble(); + chaperone_center = {static_cast<float>(translation_arr[0].asDouble()), + static_cast<float>(translation_arr[1].asDouble()), + static_cast<float>(translation_arr[2].asDouble())}; + const xrt_vec3 yaw_axis{0.0, -1.0, 0.0}; + math_quat_from_angle_vector(yaw, &yaw_axis, &chaperone_yaw); + DEV_INFO("Initialized chaperone data."); +} + +void +Device::update_pose(const vr::DriverPose_t &newPose) +{ + xrt_space_relation relation; + if (newPose.poseIsValid) { + relation.relation_flags = XRT_SPACE_RELATION_BITMASK_ALL; + + const xrt_vec3 to_local_pos = copy_vec3(newPose.vecDriverFromHeadTranslation); + const xrt_quat to_local_rot = copy_quat(newPose.qDriverFromHeadRotation); + const xrt_vec3 to_world_pos = copy_vec3(newPose.vecWorldFromDriverTranslation); + const xrt_quat to_world_rot = copy_quat(newPose.qWorldFromDriverRotation); + + xrt_pose &pose = relation.pose; + pose.position = copy_vec3(newPose.vecPosition); + pose.orientation = copy_quat(newPose.qRotation); + relation.linear_velocity = copy_vec3(newPose.vecVelocity); + relation.angular_velocity = copy_vec3(newPose.vecAngularVelocity); + + // apply world transform + auto world_transform = [&](xrt_vec3 &vec) { + math_quat_rotate_vec3(&to_world_rot, &vec, &vec); + math_vec3_accum(&to_world_pos, &vec); + }; + world_transform(pose.position); + world_transform(relation.linear_velocity); + math_quat_rotate(&to_world_rot, &pose.orientation, &pose.orientation); + math_quat_rotate_vec3(&pose.orientation, &relation.angular_velocity, &relation.angular_velocity); + + // apply local transform + xrt_vec3 local_rotated; + math_quat_rotate_vec3(&pose.orientation, &to_local_pos, &local_rotated); + math_vec3_accum(&local_rotated, &pose.position); + math_vec3_accum(&local_rotated, &relation.linear_velocity); + math_quat_rotate(&pose.orientation, &to_local_rot, &pose.orientation); + + // apply chaperone transform + auto chap_transform = [&](xrt_vec3 &vec) { + math_vec3_accum(&chaperone_center, &vec); + math_quat_rotate_vec3(&chaperone_yaw, &vec, &vec); + }; + chap_transform(pose.position); + chap_transform(relation.linear_velocity); + math_quat_rotate(&chaperone_yaw, &pose.orientation, &pose.orientation); + } else { + relation.relation_flags = XRT_SPACE_RELATION_BITMASK_NONE; + } + this->relation = relation; +} + +void +Device::handle_properties(const vr::PropertyWrite_t *batch, uint32_t count) +{ + for (uint32_t i = 0; i < count; ++i) { + handle_property_write(batch[i]); + } +} + +void +HmdDevice::set_nominal_frame_interval(uint64_t interval_ns) +{ + auto set = [this, interval_ns] { hmd_parts->base.screens[0].nominal_frame_interval_ns = interval_ns; }; + + if (hmd_parts) { + set(); + } else { + std::thread t([this, set] { + std::unique_lock lk(hmd_parts_mut); + hmd_parts_cv.wait(lk, [this] { return hmd_parts != nullptr; }); + set(); + }); + t.detach(); + } +} + +namespace { +// From openvr driver documentation +// (https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md#Input-Profiles): +// "Input profiles are expected to be a valid JSON file, +// and should be located: <driver_name>/resources/input/<device_name>_profile.json" +// So we will just parse the file name to get the device name. +std::string_view +parse_profile(std::string_view path) +{ + size_t name_start_idx = path.find_last_of('/') + 1; + size_t name_end_idx = path.find_last_of('_'); + return path.substr(name_start_idx, name_end_idx - name_start_idx); +} +} // namespace + +void +HmdDevice::handle_property_write(const vr::PropertyWrite_t &prop) +{ + switch (prop.prop) { + case vr::Prop_DisplayFrequency_Float: { + assert(prop.unBufferSize == sizeof(float)); + float freq = *static_cast<float *>(prop.pvBuffer); + set_nominal_frame_interval((1.f / freq) * 1e9f); + break; + } + case vr::Prop_InputProfilePath_String: { + std::string_view profile = + parse_profile(std::string_view(static_cast<char *>(prop.pvBuffer), prop.unBufferSize)); + if (profile == "vive") { + std::strcpy(this->str, "Vive HMD"); + } + } + default: break; + } +} + +void +ControllerDevice::handle_property_write(const vr::PropertyWrite_t &prop) +{ + switch (prop.prop) { + case vr::Prop_InputProfilePath_String: { + std::string_view profile = + parse_profile(std::string_view(static_cast<char *>(prop.pvBuffer), prop.unBufferSize)); + auto input_class = controller_classes.find(profile); + if (input_class == controller_classes.end()) { + DEV_ERR("Could not find input class for controller profile %s", std::string(profile).c_str()); + } else { + std::strcpy(this->str, input_class->second.description.c_str()); + this->name = input_class->second.name; + set_input_class(&input_class->second); + } + break; + } + default: break; + } +} diff --git a/src/xrt/drivers/steamvr_lh/device.hpp b/src/xrt/drivers/steamvr_lh/device.hpp new file mode 100644 index 000000000..1aa56b5da --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/device.hpp @@ -0,0 +1,140 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief SteamVR driver device header - inherits xrt_device. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include <string> +#include <vector> +#include <memory> +#include <unordered_map> + +#include <condition_variable> +#include <mutex> + +#include "xrt/xrt_device.h" +#include "openvr_driver.h" + +class Context; +struct InputClass; + +struct DeviceBuilder +{ + std::shared_ptr<Context> ctx; + vr::ITrackedDeviceServerDriver *driver; + const char *serial; + const std::string &steam_install; + + // no copies! + DeviceBuilder(const DeviceBuilder &) = delete; + DeviceBuilder + operator=(const DeviceBuilder &) = delete; +}; + +class Device : public xrt_device +{ + +public: + xrt_space_relation relation = XRT_SPACE_RELATION_ZERO; + + virtual ~Device() = default; + + xrt_input * + get_input_from_name(std::string_view name); + + void + update_inputs(); + + void + update_pose(const vr::DriverPose_t &newPose); + + void + get_tracked_pose(xrt_input_name name, uint64_t at_timestamp_ns, xrt_space_relation *out_relation); + + void + handle_properties(const vr::PropertyWrite_t *batch, uint32_t count); + +protected: + Device(const DeviceBuilder &builder); + std::shared_ptr<Context> ctx; + vr::PropertyContainerHandle_t container_handle{0}; + + virtual void + handle_property_write(const vr::PropertyWrite_t &prop) = 0; + + void + set_input_class(const InputClass *input_class); + +private: + vr::ITrackedDeviceServerDriver *driver; + const InputClass *input_class; + std::vector<xrt_binding_profile> binding_profiles_vec; + std::unordered_map<std::string_view, xrt_input *> inputs_map; + std::vector<xrt_input> inputs_vec; + uint64_t current_frame{0}; + + void + init_chaperone(const std::string &steam_install); + inline static xrt_vec3 chaperone_center{}; + inline static xrt_quat chaperone_yaw = XRT_QUAT_IDENTITY; +}; + +class HmdDevice : public Device +{ +public: + struct Parts + { + xrt_hmd_parts base; + vr::IVRDisplayComponent *display; + }; + + HmdDevice(const DeviceBuilder &builder); + + void + get_view_poses(const xrt_vec3 *default_eye_relation, + uint64_t at_timestamp_ns, + uint32_t view_count, + xrt_space_relation *out_head_relation, + xrt_fov *out_fovs, + xrt_pose *out_poses); + + bool + compute_distortion(uint32_t view, float u, float v, xrt_uv_triplet *out_result); + + void + set_hmd_parts(std::unique_ptr<Parts> parts); + +private: + std::unique_ptr<Parts> hmd_parts{nullptr}; + + void + handle_property_write(const vr::PropertyWrite_t &prop) override; + + void + set_nominal_frame_interval(uint64_t interval_ns); + + std::condition_variable hmd_parts_cv; + std::mutex hmd_parts_mut; +}; + +class ControllerDevice : public Device +{ +public: + ControllerDevice(vr::PropertyContainerHandle_t container_handle, const DeviceBuilder &builder); + + void + set_output(xrt_output_name name, const xrt_output_value *value); + + void + set_haptic_handle(vr::VRInputComponentHandle_t handle); + +private: + vr::VRInputComponentHandle_t haptic_handle{0}; + std::unique_ptr<xrt_output> output{nullptr}; + + void + handle_property_write(const vr::PropertyWrite_t &prop) override; +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.cpp b/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.cpp new file mode 100644 index 000000000..d9cf0f3cc --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.cpp @@ -0,0 +1,84 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRBlockQueue interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include "blockqueue.hpp" + + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +vr::EBlockQueueError +BlockQueue::Create(vr::PropertyContainerHandle_t *pulQueueHandle, + char *pchPath, + uint32_t unBlockDataSize, + uint32_t unBlockHeaderSize, + uint32_t unBlockCount, + uint32_t unFlags) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::Connect(vr::PropertyContainerHandle_t *pulQueueHandle, char *pchPath) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::Destroy(vr::PropertyContainerHandle_t ulQueueHandle) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::AcquireWriteOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::ReleaseWriteOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t ulBlockHandle) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::WaitAndAcquireReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer, + vr::EBlockQueueReadType eReadType, + uint32_t unTimeoutMs) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::AcquireReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer, + vr::EBlockQueueReadType eReadType) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::ReleaseReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t ulBlockHandle) +{ + return vr::EBlockQueueError_BlockQueueError_None; +} + +vr::EBlockQueueError +BlockQueue::QueueHasReader(vr::PropertyContainerHandle_t ulQueueHandle, bool *pbHasReaders) + +{ + return vr::EBlockQueueError_BlockQueueError_None; +} +// NOLINTEND(bugprone-easily-swappable-parameters) diff --git a/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.hpp b/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.hpp new file mode 100644 index 000000000..f1c1c866e --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/blockqueue.hpp @@ -0,0 +1,77 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRBlockQueue interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" + +/** Definitions missing from C++ header, present in C */ +namespace vr { +inline const char *IVRBlockQueue_Version = "IVRBlockQueue_005"; + +typedef enum EBlockQueueError +{ + EBlockQueueError_BlockQueueError_None = 0, + EBlockQueueError_BlockQueueError_QueueAlreadyExists = 1, + EBlockQueueError_BlockQueueError_QueueNotFound = 2, + EBlockQueueError_BlockQueueError_BlockNotAvailable = 3, + EBlockQueueError_BlockQueueError_InvalidHandle = 4, + EBlockQueueError_BlockQueueError_InvalidParam = 5, + EBlockQueueError_BlockQueueError_ParamMismatch = 6, + EBlockQueueError_BlockQueueError_InternalError = 7, + EBlockQueueError_BlockQueueError_AlreadyInitialized = 8, + EBlockQueueError_BlockQueueError_OperationIsServerOnly = 9, + EBlockQueueError_BlockQueueError_TooManyConnections = 10, +} EBlockQueueError; + +typedef enum EBlockQueueReadType +{ + EBlockQueueReadType_BlockQueueRead_Latest = 0, + EBlockQueueReadType_BlockQueueRead_New = 1, + EBlockQueueReadType_BlockQueueRead_Next = 2, +} EBlockQueueReadType; +} // namespace vr + +/** This interface is missing in the C++ header but present in the C one, and the lighthouse driver requires it. */ +class BlockQueue +{ +public: + virtual vr::EBlockQueueError + Create(vr::PropertyContainerHandle_t *pulQueueHandle, + char *pchPath, + uint32_t unBlockDataSize, + uint32_t unBlockHeaderSize, + uint32_t unBlockCount, + uint32_t unFlags); + virtual vr::EBlockQueueError + Connect(vr::PropertyContainerHandle_t *pulQueueHandle, char *pchPath); + virtual vr::EBlockQueueError + Destroy(vr::PropertyContainerHandle_t ulQueueHandle); + virtual vr::EBlockQueueError + AcquireWriteOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer); + virtual vr::EBlockQueueError + ReleaseWriteOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, vr::PropertyContainerHandle_t ulBlockHandle); + virtual vr::EBlockQueueError + WaitAndAcquireReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer, + vr::EBlockQueueReadType eReadType, + uint32_t unTimeoutMs); + virtual vr::EBlockQueueError + AcquireReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, + vr::PropertyContainerHandle_t *pulBlockHandle, + void **ppvBuffer, + vr::EBlockQueueReadType eReadType); + virtual vr::EBlockQueueError + ReleaseReadOnlyBlock(vr::PropertyContainerHandle_t ulQueueHandle, vr::PropertyContainerHandle_t ulBlockHandle); + virtual vr::EBlockQueueError + QueueHasReader(vr::PropertyContainerHandle_t ulQueueHandle, bool *pbHasReaders); +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/context.hpp b/src/xrt/drivers/steamvr_lh/interfaces/context.hpp new file mode 100644 index 000000000..830dafb4d --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/context.hpp @@ -0,0 +1,233 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief SteamVR driver context - implements xrt_tracking_origin and IVRDriverContext. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include <unordered_map> +#include <memory> +#include <optional> +#include <chrono> +#include <deque> +#include <mutex> + +#include "openvr_driver.h" + +#include "settings.hpp" +#include "resources.hpp" +#include "iobuffer.hpp" +#include "driver_manager.hpp" +#include "server.hpp" +#include "blockqueue.hpp" +#include "paths.hpp" + +#include "xrt/xrt_tracking.h" + +struct xrt_input; +class Device; +class Context final : public xrt_tracking_origin, + public vr::IVRDriverContext, + public vr::IVRServerDriverHost, + public vr::IVRDriverInput, + public vr::IVRProperties, + public vr::IVRDriverLog, + public std::enable_shared_from_this<Context> + +{ + Settings settings; + Resources resources; + IOBuffer iobuf; + DriverManager man; + Server server; + BlockQueue blockqueue; + Paths paths; + + uint64_t current_frame{0}; + + std::unordered_map<vr::VRInputComponentHandle_t, xrt_input *> handle_to_input; + struct Vec2Components + { + vr::VRInputComponentHandle_t x; + vr::VRInputComponentHandle_t y; + }; + std::unordered_map<vr::VRInputComponentHandle_t, Vec2Components *> vec2_inputs; + std::unordered_map<xrt_input *, std::unique_ptr<Vec2Components>> vec2_input_to_components; + + struct Event + { + std::chrono::steady_clock::time_point insert_time; + vr::VREvent_t inner; + }; + std::deque<Event> events; + std::mutex event_queue_mut; + + Device * + prop_container_to_device(vr::PropertyContainerHandle_t handle); + + vr::EVRInputError + create_component_common(vr::PropertyContainerHandle_t container, + const char *name, + vr::VRInputComponentHandle_t *handle); + + xrt_input * + update_component_common(vr::VRInputComponentHandle_t handle, + double offset, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + bool + setup_hmd(const char *serial, vr::ITrackedDeviceServerDriver *driver); + + bool + setup_controller(const char *serial, vr::ITrackedDeviceServerDriver *driver); + vr::IServerTrackedDeviceProvider *provider; + +protected: + Context(const std::string &steam_install, const std::string &steamvr_install, u_logging_level level); + +public: + // These are owned by monado, context is destroyed when these are destroyed + class HmdDevice *hmd{nullptr}; + class ControllerDevice *controller[2]{nullptr, nullptr}; + const u_logging_level log_level; + + ~Context(); + + [[nodiscard]] static std::shared_ptr<Context> + create(const std::string &steam_install, + const std::string &steamvr_install, + vr::IServerTrackedDeviceProvider *p); + + void + maybe_run_frame(uint64_t new_frame); + + void + add_haptic_event(vr::VREvent_HapticVibration_t event); + + void + Log(const char *pchLogMessage) override; + + /***** IVRDriverContext methods *****/ + + void * + GetGenericInterface(const char *pchInterfaceVersion, vr::EVRInitError *peError) override; + + vr::DriverHandle_t + GetDriverHandle() override; + + /***** IVRServerDriverHost methods *****/ + bool + TrackedDeviceAdded(const char *pchDeviceSerialNumber, + vr::ETrackedDeviceClass eDeviceClass, + vr::ITrackedDeviceServerDriver *pDriver) override; + + void + TrackedDevicePoseUpdated(uint32_t unWhichDevice, + const vr::DriverPose_t &newPose, + uint32_t unPoseStructSize) override; + + void + VsyncEvent(double vsyncTimeOffsetSeconds) override; + + void + VendorSpecificEvent(uint32_t unWhichDevice, + vr::EVREventType eventType, + const vr::VREvent_Data_t &eventData, + double eventTimeOffset) override; + + bool + IsExiting() override; + + bool + PollNextEvent(vr::VREvent_t *pEvent, uint32_t uncbVREvent) override; + + void + GetRawTrackedDevicePoses(float fPredictedSecondsFromNow, + vr::TrackedDevicePose_t *pTrackedDevicePoseArray, + uint32_t unTrackedDevicePoseArrayCount) override; + + void + RequestRestart(const char *pchLocalizedReason, + const char *pchExecutableToStart, + const char *pchArguments, + const char *pchWorkingDirectory) override; + + uint32_t + GetFrameTimings(vr::Compositor_FrameTiming *pTiming, uint32_t nFrames) override; + + void + SetDisplayEyeToHead(uint32_t unWhichDevice, + const vr::HmdMatrix34_t &eyeToHeadLeft, + const vr::HmdMatrix34_t &eyeToHeadRight) override; + + void + SetDisplayProjectionRaw(uint32_t unWhichDevice, + const vr::HmdRect2_t &eyeLeft, + const vr::HmdRect2_t &eyeRight) override; + + void + SetRecommendedRenderTargetSize(uint32_t unWhichDevice, uint32_t nWidth, uint32_t nHeight) override; + + /***** IVRDriverInput methods *****/ + + vr::EVRInputError + CreateBooleanComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle) override; + + vr::EVRInputError + UpdateBooleanComponent(vr::VRInputComponentHandle_t ulComponent, bool bNewValue, double fTimeOffset) override; + + vr::EVRInputError + CreateScalarComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle, + vr::EVRScalarType eType, + vr::EVRScalarUnits eUnits) override; + + vr::EVRInputError + UpdateScalarComponent(vr::VRInputComponentHandle_t ulComponent, float fNewValue, double fTimeOffset) override; + + vr::EVRInputError + CreateHapticComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle) override; + + vr::EVRInputError + CreateSkeletonComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + const char *pchSkeletonPath, + const char *pchBasePosePath, + vr::EVRSkeletalTrackingLevel eSkeletalTrackingLevel, + const vr::VRBoneTransform_t *pGripLimitTransforms, + uint32_t unGripLimitTransformCount, + vr::VRInputComponentHandle_t *pHandle) override; + + vr::EVRInputError + UpdateSkeletonComponent(vr::VRInputComponentHandle_t ulComponent, + vr::EVRSkeletalMotionRange eMotionRange, + const vr::VRBoneTransform_t *pTransforms, + uint32_t unTransformCount) override; + + /***** IVRProperties methods *****/ + + vr::ETrackedPropertyError + ReadPropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, + vr::PropertyRead_t *pBatch, + uint32_t unBatchEntryCount) override; + + vr::ETrackedPropertyError + WritePropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, + vr::PropertyWrite_t *pBatch, + uint32_t unBatchEntryCount) override; + + const char * + GetPropErrorNameFromEnum(vr::ETrackedPropertyError error) override; + + vr::PropertyContainerHandle_t + TrackedDeviceToPropertyContainer(vr::TrackedDeviceIndex_t nDevice) override; +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.cpp b/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.cpp new file mode 100644 index 000000000..959c19ac6 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.cpp @@ -0,0 +1,34 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRDriverManager interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include "driver_manager.hpp" + +uint32_t +DriverManager::GetDriverCount() const +{ + return 1; +} + +uint32_t +DriverManager::GetDriverName(vr::DriverId_t nDriver, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize) +{ + return 0; +} + +vr::DriverHandle_t +DriverManager::GetDriverHandle(const char *pchDriverName) +{ + return 1; +} + +bool +DriverManager::IsEnabled(vr::DriverId_t nDriver) const +{ + return nDriver == 1; +} diff --git a/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.hpp b/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.hpp new file mode 100644 index 000000000..77797fcd1 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/driver_manager.hpp @@ -0,0 +1,28 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRDriverManager interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" + +class DriverManager : public vr::IVRDriverManager +{ +public: + uint32_t + GetDriverCount() const override; + + uint32_t + GetDriverName(vr::DriverId_t nDriver, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize) override; + + vr::DriverHandle_t + GetDriverHandle(const char *pchDriverName) override; + + bool + IsEnabled(vr::DriverId_t nDriver) const override; +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.cpp b/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.cpp new file mode 100644 index 000000000..eec7e0bfd --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.cpp @@ -0,0 +1,52 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRIOBuffer interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include "iobuffer.hpp" + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +vr::EIOBufferError +IOBuffer::Open(const char *pchPath, + vr::EIOBufferMode mode, + uint32_t unElementSize, + uint32_t unElements, + vr::IOBufferHandle_t *pulBuffer) +{ + return vr::IOBuffer_Success; +} + +vr::EIOBufferError +IOBuffer::Close(vr::IOBufferHandle_t ulBuffer) +{ + return vr::IOBuffer_Success; +} + +vr::EIOBufferError +IOBuffer::Read(vr::IOBufferHandle_t ulBuffer, void *pDst, uint32_t unBytes, uint32_t *punRead) +{ + return vr::IOBuffer_Success; +} + +vr::EIOBufferError +IOBuffer::Write(vr::IOBufferHandle_t ulBuffer, void *pSrc, uint32_t unBytes) +{ + return vr::IOBuffer_Success; +} + +vr::PropertyContainerHandle_t +IOBuffer::PropertyContainer(vr::IOBufferHandle_t ulBuffer) +{ + return 1; +} + +bool +IOBuffer::HasReaders(vr::IOBufferHandle_t ulBuffer) +{ + return false; +} +// NOLINTEND(bugprone-easily-swappable-parameters) diff --git a/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.hpp b/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.hpp new file mode 100644 index 000000000..92c8cae19 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/iobuffer.hpp @@ -0,0 +1,44 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRIOBuffer interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" + +class IOBuffer : public vr::IVRIOBuffer +{ +public: + /** opens an existing or creates a new IOBuffer of unSize bytes */ + vr::EIOBufferError + Open(const char *pchPath, + vr::EIOBufferMode mode, + uint32_t unElementSize, + uint32_t unElements, + vr::IOBufferHandle_t *pulBuffer) override; + + /** closes a previously opened or created buffer */ + vr::EIOBufferError + Close(vr::IOBufferHandle_t ulBuffer) override; + + /** reads up to unBytes from buffer into *pDst, returning number of bytes read in *punRead */ + vr::EIOBufferError + Read(vr::IOBufferHandle_t ulBuffer, void *pDst, uint32_t unBytes, uint32_t *punRead) override; + + /** writes unBytes of data from *pSrc into a buffer. */ + vr::EIOBufferError + Write(vr::IOBufferHandle_t ulBuffer, void *pSrc, uint32_t unBytes) override; + + /** retrieves the property container of an buffer. */ + vr::PropertyContainerHandle_t + PropertyContainer(vr::IOBufferHandle_t ulBuffer) override; + + /** inexpensively checks for readers to allow writers to fast-fail potentially expensive copies and writes. */ + bool + HasReaders(vr::IOBufferHandle_t ulBuffer) override; +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/paths.cpp b/src/xrt/drivers/steamvr_lh/interfaces/paths.cpp new file mode 100644 index 000000000..38dd4d315 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/paths.cpp @@ -0,0 +1,36 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRPaths interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include "paths.hpp" + +vr::ETrackedPropertyError +Paths::ReadPathBatch(vr::PropertyContainerHandle_t ulRootHandle, struct PathRead_t *pBatch, uint32_t unBatchEntryCount) +{ + return vr::TrackedProp_Success; +} + +vr::ETrackedPropertyError +Paths::WritePathBatch(vr::PropertyContainerHandle_t ulRootHandle, + struct PathWrite_t *pBatch, + uint32_t unBatchEntryCount) +{ + return vr::TrackedProp_Success; +} + +vr::ETrackedPropertyError +Paths::StringToHandle(vr::PathHandle_t *pHandle, char *pchPath) +{ + return vr::TrackedProp_Success; +} + +vr::ETrackedPropertyError +Paths::HandleToString(vr::PathHandle_t pHandle, char *pchBuffer, uint32_t unBufferSize, uint32_t *punBufferSizeUsed) +{ + return vr::TrackedProp_Success; +} diff --git a/src/xrt/drivers/steamvr_lh/interfaces/paths.hpp b/src/xrt/drivers/steamvr_lh/interfaces/paths.hpp new file mode 100644 index 000000000..5ed1fb4c2 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/paths.hpp @@ -0,0 +1,34 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRPaths interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" + +namespace vr { +inline const char *IVRPaths_Version = "IVRPaths_001"; +typedef uint64_t PathHandle_t; +}; // namespace vr + +/** This interface is missing in the C++ header but present in the C one, and the lighthouse driver requires it. */ +class Paths +{ + virtual vr::ETrackedPropertyError + ReadPathBatch(vr::PropertyContainerHandle_t ulRootHandle, + struct PathRead_t *pBatch, + uint32_t unBatchEntryCount); + virtual vr::ETrackedPropertyError + WritePathBatch(vr::PropertyContainerHandle_t ulRootHandle, + struct PathWrite_t *pBatch, + uint32_t unBatchEntryCount); + virtual vr::ETrackedPropertyError + StringToHandle(vr::PathHandle_t *pHandle, char *pchPath); + virtual vr::ETrackedPropertyError + HandleToString(vr::PathHandle_t pHandle, char *pchBuffer, uint32_t unBufferSize, uint32_t *punBufferSizeUsed); +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/resources.cpp b/src/xrt/drivers/steamvr_lh/interfaces/resources.cpp new file mode 100644 index 000000000..076d5d85f --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/resources.cpp @@ -0,0 +1,62 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRResources interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include "resources.hpp" +#include "util/u_logging.h" +#include <cstring> + +#define RES_ERR(...) U_LOG_IFL_E(log_level, __VA_ARGS__) +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +uint32_t +Resources::LoadSharedResource(const char *pchResourceName, char *pchBuffer, uint32_t unBufferLen) +{ + return 0; +} + +uint32_t +Resources::GetResourceFullPath(const char *pchResourceName, + const char *pchResourceTypeDirectory, + char *pchPathBuffer, + uint32_t unBufferLen) +{ + std::string resource(pchResourceName); + + auto return_str = [&](const std::string &str) { + const auto len = str.size() + 1; + if (unBufferLen >= len) { + std::strncpy(pchPathBuffer, str.c_str(), len); + } + return len; + }; + // loading resource from driver folder (i.e. htc) + if (resource[0] == '{') { + const size_t idx = resource.find('}'); + if (idx == std::string::npos) { + RES_ERR("malformed resource name: %s", resource.c_str()); + return 0; + } + const std::string driver = resource.substr(1, idx - 1); + std::string path = steamvr_install + "/drivers/" + driver + "/resources/"; + if (pchResourceTypeDirectory) + path += pchResourceTypeDirectory + std::string("/"); + + // for some reason sometimes it gives the paths like {driver}resource.file instead of + // {driver}/resource.file + path += resource.substr(idx + 1); + return return_str(path); + } + + // loading from shared folder? + std::string path = steamvr_install + "/resources/"; + path += pchResourceTypeDirectory; + path += "/"; + path += pchResourceName; + return return_str(path); +} +// NOLINTEND(bugprone-easily-swappable-parameters) diff --git a/src/xrt/drivers/steamvr_lh/interfaces/resources.hpp b/src/xrt/drivers/steamvr_lh/interfaces/resources.hpp new file mode 100644 index 000000000..a359068d1 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/resources.hpp @@ -0,0 +1,40 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRResources interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" +#include "util/u_logging.h" + +class Resources : public vr::IVRResources +{ + const u_logging_level log_level; + const std::string steamvr_install; + +public: + Resources(u_logging_level l, const std::string &steamvr_install) + : log_level(l), steamvr_install(steamvr_install){}; + // ------------------------------------ + // Shared Resource Methods + // ------------------------------------ + + /** Loads the specified resource into the provided buffer if large enough. + * Returns the size in bytes of the buffer required to hold the specified resource. */ + uint32_t + LoadSharedResource(const char *pchResourceName, char *pchBuffer, uint32_t unBufferLen) override; + + /** Provides the full path to the specified resource. Resource names can include named directories for + * drivers and other things, and this resolves all of those and returns the actual physical path. + * pchResourceTypeDirectory is the subdirectory of resources to look in. */ + uint32_t + GetResourceFullPath(const char *pchResourceName, + const char *pchResourceTypeDirectory, + VR_OUT_STRING() char *pchPathBuffer, + uint32_t unBufferLen) override; +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/server.hpp b/src/xrt/drivers/steamvr_lh/interfaces/server.hpp new file mode 100644 index 000000000..7e1bbc28a --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/server.hpp @@ -0,0 +1,118 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRServer internal interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +/** An internal interface utilized by the lighthouse driver. Who knows what it does... */ + +class Server +{ + virtual void + UnknownFunc001() + {} + virtual void + UnknownFunc002() + {} + virtual void + UnknownFunc003() + {} + virtual void + UnknownFunc004() + {} + virtual void + UnknownFunc005() + {} + virtual void + UnknownFunc006() + {} + virtual void + UnknownFunc007() + {} + virtual void + UnknownFunc008() + {} + virtual void + UnknownFunc009() + {} + virtual void + UnknownFunc010() + {} + virtual void + UnknownFunc011() + {} + virtual void + UnknownFunc012() + {} + virtual void + UnknownFunc013() + {} + virtual void + UnknownFunc014() + {} + virtual void + UnknownFunc015() + {} + virtual void + UnknownFunc016() + {} + virtual void + UnknownFunc017() + {} + virtual void + UnknownFunc018() + {} + virtual void + UnknownFunc019() + {} + virtual void + UnknownFunc020() + {} + virtual void + UnknownFunc021() + {} + virtual void + UnknownFunc022() + {} + virtual void + UnknownFunc023() + {} + virtual void + UnknownFunc024() + {} + virtual void + UnknownFunc025() + {} + virtual void + UnknownFunc026() + {} + virtual void + UnknownFunc027() + {} + virtual void + UnknownFunc028() + {} + virtual void + UnknownFunc029() + {} + virtual void + UnknownFunc030() + {} + virtual void + UnknownFunc031() + {} + virtual void + UnknownFunc032() + {} + virtual void + UnknownFunc033() + {} + virtual void + UnknownFunc034() + {} +}; diff --git a/src/xrt/drivers/steamvr_lh/interfaces/settings.cpp b/src/xrt/drivers/steamvr_lh/interfaces/settings.cpp new file mode 100644 index 000000000..69918cc05 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/settings.cpp @@ -0,0 +1,109 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRSettings interface implementation. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include <optional> +#include <cstring> + +#include "settings.hpp" +#include "util/u_json.hpp" + +using xrt::auxiliary::util::json::JSONNode; + +Settings::Settings(const std::string &steam_install, const std::string &steamvr_install) + : steamvr_settings(JSONNode::loadFromFile(steam_install + "/config/steamvr.vrsettings")), + driver_defaults( + JSONNode::loadFromFile(steamvr_install + "/drivers/lighthouse/resources/settings/default.vrsettings")) +{} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +const char * +Settings::GetSettingsErrorNameFromEnum(vr::EVRSettingsError eError) +{ + return nullptr; +} + +void +Settings::SetBool(const char *pchSection, const char *pchSettingsKey, bool bValue, vr::EVRSettingsError *peError) +{} + +void +Settings::SetInt32(const char *pchSection, const char *pchSettingsKey, int32_t nValue, vr::EVRSettingsError *peError) +{} + +void +Settings::SetFloat(const char *pchSection, const char *pchSettingsKey, float flValue, vr::EVRSettingsError *peError) +{} + +void +Settings::SetString(const char *pchSection, + const char *pchSettingsKey, + const char *pchValue, + vr::EVRSettingsError *peError) +{} + +bool +Settings::GetBool(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError) +{ + return false; +} + +int32_t +Settings::GetInt32(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError) +{ + return 0; +} + +float +Settings::GetFloat(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError) +{ + return 0.0; +} + +// Driver requires a few string settings to initialize properly +void +Settings::GetString(const char *pchSection, + const char *pchSettingsKey, + char *pchValue, + uint32_t unValueLen, + vr::EVRSettingsError *peError) +{ + if (peError) + *peError = vr::VRSettingsError_None; + + auto get_string = [pchSection, pchSettingsKey](const JSONNode &root) -> std::optional<std::string> { + JSONNode section = root[pchSection]; + if (!section.isValid()) + return std::nullopt; + + JSONNode value = section[pchSettingsKey]; + if (!value.isValid() || !value.isString()) + return std::nullopt; + + return std::optional(value.asString()); + }; + + std::optional value = get_string(driver_defaults); + if (!value.has_value()) + value = get_string(steamvr_settings); + + if (value.has_value()) { + if (unValueLen > value->size()) + std::strncpy(pchValue, value->c_str(), value->size() + 1); + } else if (peError) + *peError = vr::VRSettingsError_ReadFailed; +} + +void +Settings::RemoveSection(const char *pchSection, vr::EVRSettingsError *peError) +{} + +void +Settings::RemoveKeyInSection(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError) +{} +// NOLINTEND(bugprone-easily-swappable-parameters) diff --git a/src/xrt/drivers/steamvr_lh/interfaces/settings.hpp b/src/xrt/drivers/steamvr_lh/interfaces/settings.hpp new file mode 100644 index 000000000..76cf331fa --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/interfaces/settings.hpp @@ -0,0 +1,70 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief OpenVR IVRSettings interface header. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + +#include "openvr_driver.h" +#include "util/u_json.hpp" + +class Settings : public vr::IVRSettings +{ +private: + const xrt::auxiliary::util::json::JSONNode steamvr_settings; + const xrt::auxiliary::util::json::JSONNode driver_defaults; + +public: + Settings(const std::string &steam_install, const std::string &steamvr_install); + + const char * + GetSettingsErrorNameFromEnum(vr::EVRSettingsError eError) override; + + void + SetBool(const char *pchSection, + const char *pchSettingsKey, + bool bValue, + vr::EVRSettingsError *peError = nullptr) override; + void + SetInt32(const char *pchSection, + const char *pchSettingsKey, + int32_t nValue, + vr::EVRSettingsError *peError = nullptr) override; + void + SetFloat(const char *pchSection, + const char *pchSettingsKey, + float flValue, + vr::EVRSettingsError *peError = nullptr) override; + void + SetString(const char *pchSection, + const char *pchSettingsKey, + const char *pchValue, + vr::EVRSettingsError *peError = nullptr) override; + + // Users of the system need to provide a proper default in default.vrsettings in the resources/settings/ + // directory of either the runtime or the driver_xxx directory. Otherwise the default will be false, 0, 0.0 or + // "" + bool + GetBool(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError = nullptr) override; + int32_t + GetInt32(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError = nullptr) override; + float + GetFloat(const char *pchSection, const char *pchSettingsKey, vr::EVRSettingsError *peError = nullptr) override; + void + GetString(const char *pchSection, + const char *pchSettingsKey, + VR_OUT_STRING() char *pchValue, + uint32_t unValueLen, + vr::EVRSettingsError *peError = nullptr) override; + + void + RemoveSection(const char *pchSection, vr::EVRSettingsError *peError = nullptr) override; + void + RemoveKeyInSection(const char *pchSection, + const char *pchSettingsKey, + vr::EVRSettingsError *peError = nullptr) override; +}; diff --git a/src/xrt/drivers/steamvr_lh/steamvr_lh.cpp b/src/xrt/drivers/steamvr_lh/steamvr_lh.cpp new file mode 100644 index 000000000..175aef989 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/steamvr_lh.cpp @@ -0,0 +1,644 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief SteamVR driver context implementation and entrypoint. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#include <cstring> +#include <dlfcn.h> +#include <memory> +#include <cmath> +#include <unordered_map> +#include <string_view> +#include <filesystem> +#include <istream> + +#include "openvr_driver.h" +#include "vdf_parser.hpp" +#include "steamvr_lh_interface.h" +#include "interfaces/context.hpp" +#include "device.hpp" +#include "util/u_device.h" + +namespace { +DEBUG_GET_ONCE_LOG_OPTION(lh_log, "LIGHTHOUSE_LOG", U_LOGGING_INFO); + +// ~/.steam/root is a symlink to where the Steam root is +const std::string STEAM_INSTALL_DIR = std::string(getenv("HOME")) + "/.steam/root"; +constexpr auto STEAMVR_APPID = "250820"; + +// Parse libraryfolder.vdf to find where SteamVR is installed +std::string +find_steamvr_install() +{ + using namespace tyti; + std::ifstream file(STEAM_INSTALL_DIR + "/steamapps/libraryfolders.vdf"); + auto root = vdf::read(file); + assert(root.name == "libraryfolders"); + for (auto &[_, child] : root.children) { + U_LOG_D("Found library folder %s", child->attribs["path"].c_str()); + std::shared_ptr<vdf::object> apps = child->children["apps"]; + for (auto &[appid, _] : apps->attribs) { + if (appid == STEAMVR_APPID) { + return child->attribs["path"] + "/steamapps/common/SteamVR"; + } + } + } + return std::string(); +} + +} // namespace + +#define CTX_ERR(...) U_LOG_IFL_E(log_level, __VA_ARGS__) +#define CTX_WARN(...) U_LOG_IFL_E(log_level, __VA_ARGS__) +#define CTX_INFO(...) U_LOG_IFL_I(log_level, __VA_ARGS__) +#define CTX_TRACE(...) U_LOG_IFL_T(log_level, __VA_ARGS__) +#define CTX_DEBUG(...) U_LOG_IFL_D(log_level, __VA_ARGS__) + +/** + * Since only the devices will live after our get_devices function is called, we make our Context + * a shared ptr that is owned by the devices that exist, so that it is also cleaned up by the + * devices that exist when they are all destroyed. + */ +std::shared_ptr<Context> +Context::create(const std::string &steam_install, + const std::string &steamvr_install, + vr::IServerTrackedDeviceProvider *p) +{ + // xrt_tracking_origin initialization + Context *c = new Context(steam_install, steamvr_install, debug_get_log_option_lh_log()); + c->provider = p; + std::strncpy(c->name, "SteamVR Lighthouse Tracking", XRT_TRACKING_NAME_LEN); + c->type = XRT_TRACKING_TYPE_LIGHTHOUSE; + c->offset = XRT_POSE_IDENTITY; + return std::shared_ptr<Context>(c); +} + +Context::Context(const std::string &steam_install, const std::string &steamvr_install, u_logging_level level) + : settings(steam_install, steamvr_install), resources(level, steamvr_install), log_level(level) +{} + +Context::~Context() +{ + provider->Cleanup(); +} + +/***** IVRDriverContext methods *****/ + +void * +Context::GetGenericInterface(const char *pchInterfaceVersion, vr::EVRInitError *peError) +{ +#define MATCH_INTERFACE(version, interface) \ + if (std::strcmp(pchInterfaceVersion, version) == 0) { \ + return interface; \ + } +#define MATCH_INTERFACE_THIS(interface) MATCH_INTERFACE(interface##_Version, static_cast<interface *>(this)) + + // Known interfaces + MATCH_INTERFACE_THIS(vr::IVRServerDriverHost); + MATCH_INTERFACE_THIS(vr::IVRDriverInput); + MATCH_INTERFACE_THIS(vr::IVRProperties); + MATCH_INTERFACE_THIS(vr::IVRDriverLog); + MATCH_INTERFACE(vr::IVRSettings_Version, &settings); + MATCH_INTERFACE(vr::IVRResources_Version, &resources); + MATCH_INTERFACE(vr::IVRIOBuffer_Version, &iobuf); + MATCH_INTERFACE(vr::IVRDriverManager_Version, &man); + MATCH_INTERFACE(vr::IVRBlockQueue_Version, &blockqueue); + MATCH_INTERFACE(vr::IVRPaths_Version, &paths); + + // Internal interfaces + MATCH_INTERFACE("IVRServer_XXX", &server); + return nullptr; +} + +vr::DriverHandle_t +Context::GetDriverHandle() +{ + return 1; +} + + +/***** IVRServerDriverHost methods *****/ + +bool +Context::setup_hmd(const char *serial, vr::ITrackedDeviceServerDriver *driver) +{ + this->hmd = new HmdDevice(DeviceBuilder{this->shared_from_this(), driver, serial, STEAM_INSTALL_DIR}); +#define VERIFY(expr, msg) \ + if (!(expr)) { \ + CTX_ERR("Activating HMD failed: %s", msg); \ + delete this->hmd; \ + this->hmd = nullptr; \ + return false; \ + } + vr::EVRInitError err = driver->Activate(0); + VERIFY(err == vr::VRInitError_None, std::to_string(err).c_str()); + + auto *display = static_cast<vr::IVRDisplayComponent *>(driver->GetComponent(vr::IVRDisplayComponent_Version)); + VERIFY(display, "IVRDisplayComponent is null"); +#undef VERIFY + + auto hmd_parts = std::make_unique<HmdDevice::Parts>(); + for (size_t idx = 0; idx < 2; ++idx) { + vr::EVREye eye = (idx == 0) ? vr::Eye_Left : vr::Eye_Right; + xrt_view &view = hmd_parts->base.views[idx]; + + display->GetEyeOutputViewport(eye, &view.viewport.x_pixels, &view.viewport.y_pixels, + &view.viewport.w_pixels, &view.viewport.h_pixels); + + view.display.w_pixels = view.viewport.w_pixels; + view.display.h_pixels = view.viewport.h_pixels; + view.rot = u_device_rotation_ident; + } + + hmd_parts->base.screens[0].w_pixels = + hmd_parts->base.views[0].display.w_pixels + hmd_parts->base.views[1].display.w_pixels; + hmd_parts->base.screens[0].h_pixels = hmd_parts->base.views[0].display.h_pixels; + // nominal frame interval will be set when lighthouse gives us the display frequency + // see HmdDevice::handle_property_write + + hmd_parts->base.blend_modes[0] = XRT_BLEND_MODE_OPAQUE; + hmd_parts->base.blend_mode_count = 1; + + auto &distortion = hmd_parts->base.distortion; + distortion.models = XRT_DISTORTION_MODEL_COMPUTE; + distortion.preferred = XRT_DISTORTION_MODEL_COMPUTE; + for (size_t idx = 0; idx < 2; ++idx) { + xrt_fov &fov = distortion.fov[idx]; + float tan_left, tan_right, tan_top, tan_bottom; + display->GetProjectionRaw((vr::EVREye)idx, &tan_left, &tan_right, &tan_top, &tan_bottom); + fov.angle_left = atanf(tan_left); + fov.angle_right = atanf(tan_right); + fov.angle_up = atanf(tan_bottom); + fov.angle_down = atanf(tan_top); + } + + hmd_parts->display = display; + hmd->set_hmd_parts(std::move(hmd_parts)); + return true; +} + +bool +Context::setup_controller(const char *serial, vr::ITrackedDeviceServerDriver *driver) +{ + if (controller[0] && controller[1]) { + CTX_WARN("Attempted to activate more than two controllers - this is unsupported"); + return false; + } + size_t device_idx = (controller[0]) ? 2 : 1; + auto &dev = controller[device_idx - 1]; + dev = new ControllerDevice(device_idx + 1, + DeviceBuilder{this->shared_from_this(), driver, serial, STEAM_INSTALL_DIR}); + + vr::EVRInitError err = driver->Activate(device_idx); + if (err != vr::VRInitError_None) { + CTX_ERR("Activating controller failed: error %u", err); + return false; + } + + return true; +} + +void +Context::maybe_run_frame(uint64_t new_frame) +{ + if (new_frame > current_frame) { + ++current_frame; + provider->RunFrame(); + } +} +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +bool +Context::TrackedDeviceAdded(const char *pchDeviceSerialNumber, + vr::ETrackedDeviceClass eDeviceClass, + vr::ITrackedDeviceServerDriver *pDriver) +{ + CTX_INFO("New device added: %s", pchDeviceSerialNumber); + switch (eDeviceClass) { + case vr::TrackedDeviceClass_HMD: { + return setup_hmd(pchDeviceSerialNumber, pDriver); + break; + } + case vr::TrackedDeviceClass_Controller: { + return setup_controller(pchDeviceSerialNumber, pDriver); + break; + } + default: { + CTX_WARN("Attempted to add unsupported device class: %u", eDeviceClass); + return false; + } + } +} + +void +Context::TrackedDevicePoseUpdated(uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize) +{ + assert(sizeof(newPose) == unPoseStructSize); + if (unWhichDevice > 2) + return; + Device *dev = (unWhichDevice == 0) ? static_cast<Device *>(this->hmd) + : static_cast<Device *>(this->controller[unWhichDevice - 1]); + assert(dev); + dev->update_pose(newPose); +} + +void +Context::VsyncEvent(double vsyncTimeOffsetSeconds) +{} + +void +Context::VendorSpecificEvent(uint32_t unWhichDevice, + vr::EVREventType eventType, + const vr::VREvent_Data_t &eventData, + double eventTimeOffset) +{} + +bool +Context::IsExiting() +{ + return false; +} + +void +Context::add_haptic_event(vr::VREvent_HapticVibration_t event) +{ + vr::VREvent_t e; + e.eventType = vr::EVREventType::VREvent_Input_HapticVibration; + e.trackedDeviceIndex = event.containerHandle - 1; + vr::VREvent_Data_t d; + d.hapticVibration = event; + e.data = d; + + std::lock_guard lk(event_queue_mut); + events.push_back({std::chrono::steady_clock::now(), e}); +} + +bool +Context::PollNextEvent(vr::VREvent_t *pEvent, uint32_t uncbVREvent) +{ + if (!events.empty()) { + assert(sizeof(vr::VREvent_t) == uncbVREvent); + Event e; + { + std::lock_guard lk(event_queue_mut); + e = events.front(); + events.pop_front(); + } + *pEvent = e.inner; + using float_sec = std::chrono::duration<float>; + float_sec event_age = std::chrono::steady_clock::now() - e.insert_time; + pEvent->eventAgeSeconds = event_age.count(); + return true; + } + return false; +} + +void +Context::GetRawTrackedDevicePoses(float fPredictedSecondsFromNow, + vr::TrackedDevicePose_t *pTrackedDevicePoseArray, + uint32_t unTrackedDevicePoseArrayCount) +{} + +void +Context::RequestRestart(const char *pchLocalizedReason, + const char *pchExecutableToStart, + const char *pchArguments, + const char *pchWorkingDirectory) +{} + +uint32_t +Context::GetFrameTimings(vr::Compositor_FrameTiming *pTiming, uint32_t nFrames) +{ + return 0; +} + +void +Context::SetDisplayEyeToHead(uint32_t unWhichDevice, + const vr::HmdMatrix34_t &eyeToHeadLeft, + const vr::HmdMatrix34_t &eyeToHeadRight) +{} + +void +Context::SetDisplayProjectionRaw(uint32_t unWhichDevice, const vr::HmdRect2_t &eyeLeft, const vr::HmdRect2_t &eyeRight) +{} + +void +Context::SetRecommendedRenderTargetSize(uint32_t unWhichDevice, uint32_t nWidth, uint32_t nHeight) +{} + +/***** IVRDriverInput methods *****/ + + +vr::EVRInputError +Context::create_component_common(vr::PropertyContainerHandle_t container, + const char *name, + vr::VRInputComponentHandle_t *pHandle) +{ + *pHandle = vr::k_ulInvalidInputComponentHandle; + Device *device = prop_container_to_device(container); + if (!device) { + return vr::VRInputError_InvalidHandle; + } + if (xrt_input *input = device->get_input_from_name(name); input) { + CTX_DEBUG("creating component %s", name); + vr::VRInputComponentHandle_t handle = handle_to_input.size() + 1; + handle_to_input[handle] = input; + *pHandle = handle; + } + return vr::VRInputError_None; +} + +xrt_input * +Context::update_component_common(vr::VRInputComponentHandle_t handle, + double offset, + std::chrono::steady_clock::time_point now) +{ + xrt_input *input{nullptr}; + if (handle != vr::k_ulInvalidInputComponentHandle) { + input = handle_to_input[handle]; + std::chrono::duration<double, std::chrono::seconds::period> offset_dur(offset); + std::chrono::duration offset = (now + offset_dur).time_since_epoch(); + int64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(offset).count(); + input->active = true; + input->timestamp = timestamp; + } + return input; +} + +vr::EVRInputError +Context::CreateBooleanComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle) +{ + return create_component_common(ulContainer, pchName, pHandle); +} + +vr::EVRInputError +Context::UpdateBooleanComponent(vr::VRInputComponentHandle_t ulComponent, bool bNewValue, double fTimeOffset) +{ + xrt_input *input = update_component_common(ulComponent, fTimeOffset); + if (input) { + input->value.boolean = bNewValue; + } + return vr::VRInputError_None; +} + +vr::EVRInputError +Context::CreateScalarComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle, + vr::EVRScalarType eType, + vr::EVRScalarUnits eUnits) +{ + std::string_view name{pchName}; + // Lighthouse gives thumbsticks/trackpads as x/y components, + // we need to combine them for Monado + auto end = name.back(); + if (end == 'x' || end == 'y') { + Device *device = prop_container_to_device(ulContainer); + if (!device) { + return vr::VRInputError_InvalidHandle; + } + bool x = end == 'x'; + name.remove_suffix(2); + std::string n(name); + xrt_input *input = device->get_input_from_name(n); + if (!input) { + return vr::VRInputError_None; + } + + // Create the component mapping if it hasn't been created yet + Vec2Components *components = + vec2_input_to_components.try_emplace(input, new Vec2Components).first->second.get(); + + vr::VRInputComponentHandle_t new_handle = handle_to_input.size() + 1; + if (x) + components->x = new_handle; + else + components->y = new_handle; + + handle_to_input[new_handle] = input; + *pHandle = new_handle; + return vr::VRInputError_None; + } + return create_component_common(ulContainer, pchName, pHandle); +} + +vr::EVRInputError +Context::UpdateScalarComponent(vr::VRInputComponentHandle_t ulComponent, float fNewValue, double fTimeOffset) +{ + xrt_input *input = update_component_common(ulComponent, fTimeOffset); + if (input) { + if (XRT_GET_INPUT_TYPE(input->name) == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) { + std::unique_ptr<Vec2Components> &components = vec2_input_to_components.at(input); + if (components->x == ulComponent) { + input->value.vec2.x = fNewValue; + } else if (components->y == ulComponent) { + input->value.vec2.y = fNewValue; + } else { + CTX_WARN( + "Attempted to update component with handle %lu" + " but it was neither the x nor y " + "component of its associated input", + ulComponent); + } + + } else { + input->value.vec1.x = fNewValue; + } + } + return vr::VRInputError_None; +} + +vr::EVRInputError +Context::CreateHapticComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + vr::VRInputComponentHandle_t *pHandle) +{ + *pHandle = vr::k_ulInvalidInputComponentHandle; + Device *d = prop_container_to_device(ulContainer); + if (!d) { + return vr::VRInputError_InvalidHandle; + } + + // Assuming HMDs won't have haptics. + // Maybe a wrong assumption. + if (d == hmd) { + CTX_WARN("Didn't expect HMD with haptics."); + return vr::VRInputError_InvalidHandle; + } + + auto *device = static_cast<ControllerDevice *>(d); + vr::VRInputComponentHandle_t handle = handle_to_input.size() + 1; + handle_to_input[handle] = nullptr; + device->set_haptic_handle(handle); + *pHandle = handle; + + return vr::VRInputError_None; +} + +vr::EVRInputError +Context::CreateSkeletonComponent(vr::PropertyContainerHandle_t ulContainer, + const char *pchName, + const char *pchSkeletonPath, + const char *pchBasePosePath, + vr::EVRSkeletalTrackingLevel eSkeletalTrackingLevel, + const vr::VRBoneTransform_t *pGripLimitTransforms, + uint32_t unGripLimitTransformCount, + vr::VRInputComponentHandle_t *pHandle) +{ + return vr::VRInputError_None; +} + +vr::EVRInputError +Context::UpdateSkeletonComponent(vr::VRInputComponentHandle_t ulComponent, + vr::EVRSkeletalMotionRange eMotionRange, + const vr::VRBoneTransform_t *pTransforms, + uint32_t unTransformCount) +{ + return vr::VRInputError_None; +} + +/***** IVRProperties methods *****/ + +vr::ETrackedPropertyError +Context::ReadPropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, + vr::PropertyRead_t *pBatch, + uint32_t unBatchEntryCount) +{ + return vr::TrackedProp_Success; +} + +vr::ETrackedPropertyError +Context::WritePropertyBatch(vr::PropertyContainerHandle_t ulContainerHandle, + vr::PropertyWrite_t *pBatch, + uint32_t unBatchEntryCount) +{ + Device *device = prop_container_to_device(ulContainerHandle); + if (!device) + return vr::TrackedProp_InvalidContainer; + if (!pBatch) + return vr::TrackedProp_InvalidOperation; // not verified vs steamvr + device->handle_properties(pBatch, unBatchEntryCount); + return vr::TrackedProp_Success; +} + +const char * +Context::GetPropErrorNameFromEnum(vr::ETrackedPropertyError error) +{ + return nullptr; +} + +Device * +Context::prop_container_to_device(vr::PropertyContainerHandle_t handle) +{ + switch (handle) { + case 1: { + return hmd; + break; + } + case 2: + case 3: { + return controller[handle - 2]; + break; + } + default: { + return nullptr; + } + } +} + +vr::PropertyContainerHandle_t +Context::TrackedDeviceToPropertyContainer(vr::TrackedDeviceIndex_t nDevice) +{ + size_t container = nDevice + 1; + if (nDevice == 0 && this->hmd) + return container; + if ((nDevice == 1 || nDevice == 2) && this->controller[nDevice - 1]) { + return container; + } + + return vr::k_ulInvalidPropertyContainer; +} + +void +Context::Log(const char *pchLogMessage) +{ + CTX_TRACE("[lighthouse]: %s", pchLogMessage); +} +// NOLINTEND(bugprone-easily-swappable-parameters) + + +extern "C" int +steamvr_lh_get_devices(struct xrt_device **out_xdevs) +{ + u_logging_level level = debug_get_log_option_lh_log(); + // The driver likes to create a bunch of transient folder - lets make sure they're created where they normally + // are. + std::filesystem::current_path(STEAM_INSTALL_DIR + "/config/lighthouse"); + std::string steamvr = find_steamvr_install(); + if (steamvr.empty()) { + U_LOG_IFL_E(level, "Could not find where SteamVR is installed!"); + return 0; + } + + U_LOG_IFL_I(level, "Found SteamVR install: %s", steamvr.c_str()); + + // TODO: support windows? + auto driver_so = steamvr + "/drivers/lighthouse/bin/linux64/driver_lighthouse.so"; + + void *lighthouse_lib = dlopen(driver_so.c_str(), RTLD_LAZY); + if (!lighthouse_lib) { + U_LOG_IFL_E(level, "Couldn't open lighthouse lib: %s", dlerror()); + return 0; + } + + void *sym = dlsym(lighthouse_lib, "HmdDriverFactory"); + if (!sym) { + U_LOG_IFL_E(level, "Couldn't find HmdDriverFactory in lighthouse lib: %s", dlerror()); + return 0; + } + using HmdDriverFactory_t = void *(*)(const char *, int *); + auto factory = reinterpret_cast<HmdDriverFactory_t>(sym); + + vr::EVRInitError err = vr::VRInitError_None; + auto *driver = static_cast<vr::IServerTrackedDeviceProvider *>( + factory(vr::IServerTrackedDeviceProvider_Version, (int *)&err)); + if (err != vr::VRInitError_None) { + U_LOG_IFL_E(level, "Couldn't get tracked device driver: error %u", err); + return 0; + } + + std::shared_ptr ctx = Context::create(STEAM_INSTALL_DIR, steamvr, driver); + + err = driver->Init(ctx.get()); + if (err != vr::VRInitError_None) { + U_LOG_IFL_E(level, "Lighthouse driver initialization failed: error %u", err); + return 0; + } + + U_LOG_IFL_I(level, "Lighthouse initialization complete, giving time to setup connected devices..."); + // RunFrame needs to be called to detect controllers + using namespace std::chrono_literals; + auto start_time = std::chrono::steady_clock::now(); + while (true) { + driver->RunFrame(); + auto cur_time = std::chrono::steady_clock::now(); + if (cur_time - start_time >= 1s) { + break; + } + } + U_LOG_IFL_I(level, "Device search time complete."); + + int devices = 0; + Device *devs[] = {ctx->hmd, ctx->controller[0], ctx->controller[1]}; + for (Device *dev : devs) { + if (dev) { + out_xdevs[devices++] = dev; + } + } + return devices; +} diff --git a/src/xrt/drivers/steamvr_lh/steamvr_lh_interface.h b/src/xrt/drivers/steamvr_lh/steamvr_lh_interface.h new file mode 100644 index 000000000..4ef85b207 --- /dev/null +++ b/src/xrt/drivers/steamvr_lh/steamvr_lh_interface.h @@ -0,0 +1,44 @@ +// Copyright 2023, Shawn Wallace +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief SteamVR driver device interface. + * @author Shawn Wallace <yungwallace@live.com> + * @ingroup drv_steamvr_lh + */ + +#pragma once + + +#ifdef __cplusplus +extern "C" { +#endif + + +struct xrt_device; + +/*! + * @defgroup drv_steamvr_lh Wrapper for the SteamVR Lighthouse driver. + * @ingroup drv + * + * @brief Wrapper driver around the SteamVR Lighthouse driver. + */ + +/*! + * @dir drivers/steamvr_lh + * + * @brief @ref drv_steamvr_lh files. + */ + +/*! + * Create devices. + * + * @ingroup drv_steamvr_lh + */ +int +steamvr_lh_get_devices(struct xrt_device **out_xdevs); + + +#ifdef __cplusplus +} +#endif