d/steamvr_lh: Add SteamVR Lighthouse driver wrapper

Implement support for SteamVR's Lighthouse driver (on Linux).
Only tested/works with the OG Vive and Vive wands, but adding new
device support should be simple.
This commit is contained in:
Shawn Wallace 2023-04-14 20:38:48 -04:00 committed by Jakob Bornecrantz
parent 71fa4fd3b4
commit 4a9f92e151
20 changed files with 2345 additions and 27 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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;
};

View file

@ -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)

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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)

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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);
};

View file

@ -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)

View file

@ -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;
};

View file

@ -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()
{}
};

View file

@ -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)

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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