From cb62514fd08dedfbb756add2ddf4bf135517339e Mon Sep 17 00:00:00 2001 From: Christoph Haag Date: Wed, 10 Jun 2020 14:48:43 +0100 Subject: [PATCH] steamvr: Implement basic SteamVR driver v3: targets: Add Monado-SteamVR driver target st/ovrd: Add OpenVR driver header build: Factor out sdl hack into lib_sdl2_hack and update steamvr build build: Revert lib_sdl2_refactor steamvr: Emulate Index Controller by default steamvr: Use oxr_handle_destroy instead of exposing oxr_instance_destroy steamvr: don't use oxr internals steamvr: communicate 3dof tracking to steamvr steamvr: use util functions for device assignment and tracking origin setup steamvr: Install plugin to /share/steamvr-monado steamvr: Use thread for updating poses every 1ms Makes a big difference for the Index @144Hz on the vive driver. Still somewhat choppy on survive driver - prediction should solve it. Main-author: Christoph Haag Co-author: Jakob Bornecrantz --- CMakeLists.txt | 3 + meson.build | 6 + meson_options.txt | 7 + src/external/meson.build | 2 +- src/xrt/include/meson.build | 2 + src/xrt/state_trackers/CMakeLists.txt | 4 + src/xrt/state_trackers/meson.build | 4 + .../state_trackers/steamvr_drv/CMakeLists.txt | 19 + .../state_trackers/steamvr_drv/meson.build | 20 + .../steamvr_drv/ovrd_driver.cpp | 1558 +++++++++++++++++ .../steamvr_drv/ovrd_interface.h | 29 + .../state_trackers/steamvr_drv/ovrd_log.hpp | 62 + src/xrt/targets/CMakeLists.txt | 4 + src/xrt/targets/meson.build | 3 + src/xrt/targets/steamvr_drv/CMakeLists.txt | 51 + src/xrt/targets/steamvr_drv/copy_assets.py | 31 + src/xrt/targets/steamvr_drv/copy_plugin.py | 20 + .../steamvr_drv/driver.vrdrivermanifest | 11 + src/xrt/targets/steamvr_drv/main.c | 28 + src/xrt/targets/steamvr_drv/meson.build | 65 + .../steamvr_drv/resources/driver.vrresources | 47 + .../icons/controller_status_error.png | Bin 0 -> 4590 bytes .../resources/icons/controller_status_off.png | Bin 0 -> 3213 bytes .../icons/controller_status_ready.png | Bin 0 -> 4296 bytes .../icons/controller_status_ready_alert.png | Bin 0 -> 4694 bytes .../icons/controller_status_ready_low.png | Bin 0 -> 4650 bytes .../icons/controller_status_searching.gif | Bin 0 -> 4605 bytes .../controller_status_searching_alert.gif | Bin 0 -> 4633 bytes .../icons/controller_status_standby.png | Bin 0 -> 3348 bytes .../icons/headset_monado_status_error.png | Bin 0 -> 2951 bytes .../icons/headset_monado_status_off.png | Bin 0 -> 1449 bytes .../icons/headset_monado_status_ready.png | Bin 0 -> 2400 bytes .../headset_monado_status_ready_alert.png | Bin 0 -> 2889 bytes .../icons/headset_monado_status_ready_low.png | Bin 0 -> 2909 bytes .../icons/headset_monado_status_searching.gif | Bin 0 -> 20840 bytes .../headset_monado_status_searching_alert.gif | Bin 0 -> 23550 bytes .../icons/headset_monado_status_standby.png | Bin 0 -> 1590 bytes .../resources/localization/localization.json | 6 + .../resources/settings/default.vrsettings | 5 + .../targets/steamvr_drv/steamvr.vrsettings | 16 + 40 files changed, 2002 insertions(+), 1 deletion(-) create mode 100644 src/xrt/state_trackers/steamvr_drv/CMakeLists.txt create mode 100644 src/xrt/state_trackers/steamvr_drv/meson.build create mode 100644 src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp create mode 100644 src/xrt/state_trackers/steamvr_drv/ovrd_interface.h create mode 100644 src/xrt/state_trackers/steamvr_drv/ovrd_log.hpp create mode 100644 src/xrt/targets/steamvr_drv/CMakeLists.txt create mode 100644 src/xrt/targets/steamvr_drv/copy_assets.py create mode 100644 src/xrt/targets/steamvr_drv/copy_plugin.py create mode 100644 src/xrt/targets/steamvr_drv/driver.vrdrivermanifest create mode 100644 src/xrt/targets/steamvr_drv/main.c create mode 100644 src/xrt/targets/steamvr_drv/meson.build create mode 100644 src/xrt/targets/steamvr_drv/resources/driver.vrresources create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_error.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_off.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_alert.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_low.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_searching.gif create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_searching_alert.gif create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/controller_status_standby.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_error.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_off.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready_alert.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready_low.png create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_searching.gif create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_searching_alert.gif create mode 100644 src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_standby.png create mode 100644 src/xrt/targets/steamvr_drv/resources/localization/localization.json create mode 100644 src/xrt/targets/steamvr_drv/resources/settings/default.vrsettings create mode 100644 src/xrt/targets/steamvr_drv/steamvr.vrsettings diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e9ea35a2..96c89228b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,8 @@ option(XRT_FEATURE_SERVICE "Enable separate service module for OpenXR runtime" O cmake_dependent_option(XRT_HAVE_SYSTEMD "Enable systemd support (for socket activation of service)" ON "Systemd_FOUND AND XRT_FEATURE_SERVICE" OFF) cmake_dependent_option(XRT_INSTALL_SYSTEMD_UNIT_FILES "Install user unit files for systemd socket activation on installation" ON "XRT_HAVE_SYSTEMD" OFF) cmake_dependent_option(XRT_INSTALL_ABSOLUTE_SYSTEMD_UNIT_FILES "Use an absolute path to monado-system in installed user unit files for systemd socket activation" ON "XRT_INSTALL_SYSTEMD_UNIT_FILES" OFF) +option(XRT_FEATURE_STEAMVR_PLUGIN "Build SteamVR plugin" ON) + if(NOT DEFINED XRT_FEATURE_OPENXR_LAYER_DEPTH) set(XRT_FEATURE_OPENXR_LAYER_DEPTH ON) endif() @@ -283,6 +285,7 @@ message(STATUS "# FEATURE_OPENXR_LAYER_CUBE: ${XRT_FEATURE_OPENXR_ message(STATUS "# FEATURE_OPENXR_LAYER_CYLINDER: ${XRT_FEATURE_OPENXR_LAYER_CYLINDER}") message(STATUS "# FEATURE_OPENXR_LAYER_EQUIRECT: ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT}") message(STATUS "# FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY: ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY}") +message(STATUS "# FEATURE_STEAMVR_PLUGIN: ${XRT_FEATURE_STEAMVR_PLUGIN}") message(STATUS "#") message(STATUS "# DRIVER_ANDROID: ${XRT_BUILD_DRIVER_ANDROID}") message(STATUS "# DRIVER_ARDUINO: ${XRT_BUILD_DRIVER_ARDUINO}") diff --git a/meson.build b/meson.build index 699bbecaf..8d1ae88ca 100644 --- a/meson.build +++ b/meson.build @@ -245,3 +245,9 @@ if build_tracking else message(' tracking: no') endif + +if get_option('steamvr_plugin') + message('steamvr plugin: yes') +else + message('steamvr plugin: no') +endif diff --git a/meson_options.txt b/meson_options.txt index 764468ad1..6df07004c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -107,3 +107,10 @@ option('layer_equirect2', value: true, description: 'Enable support for Equirect2 Layers' ) + +option('steamvr_plugin', + type: 'boolean', + value: true, + description: 'Enable SteamVR Plugin' +) + diff --git a/src/external/meson.build b/src/external/meson.build index 89feeefb9..ab83322cd 100644 --- a/src/external/meson.build +++ b/src/external/meson.build @@ -1,7 +1,7 @@ # Copyright 2019-2020, Collabora, Ltd. # SPDX-License-Identifier: BSL-1.0 -openvr_include = include_directories('openvr_includes') +openvr_include = include_directories('openvr_includes', is_system: true) cjson_include = include_directories('cjson') flexkalman_include = include_directories('flexkalman') glad_include = include_directories('glad/include') diff --git a/src/xrt/include/meson.build b/src/xrt/include/meson.build index fcda78e04..b58e39138 100644 --- a/src/xrt/include/meson.build +++ b/src/xrt/include/meson.build @@ -1,4 +1,6 @@ # Copyright 2020, Collabora, Ltd. # SPDX-License-Identifier: BSL-1.0 +xrt_include = include_directories('.') + subdir('xrt') diff --git a/src/xrt/state_trackers/CMakeLists.txt b/src/xrt/state_trackers/CMakeLists.txt index 698554e32..5ad98d8c1 100644 --- a/src/xrt/state_trackers/CMakeLists.txt +++ b/src/xrt/state_trackers/CMakeLists.txt @@ -6,3 +6,7 @@ if(XRT_HAVE_OPENGL) endif() add_subdirectory(oxr) add_subdirectory(prober) + +if(XRT_FEATURE_STEAMVR_PLUGIN) + add_subdirectory(steamvr_drv) +endif() diff --git a/src/xrt/state_trackers/meson.build b/src/xrt/state_trackers/meson.build index 24e413d37..449670090 100644 --- a/src/xrt/state_trackers/meson.build +++ b/src/xrt/state_trackers/meson.build @@ -6,3 +6,7 @@ st_include = include_directories('.') subdir('gui') subdir('oxr') subdir('prober') + +if get_option('steamvr_plugin') + subdir('steamvr_drv') +endif diff --git a/src/xrt/state_trackers/steamvr_drv/CMakeLists.txt b/src/xrt/state_trackers/steamvr_drv/CMakeLists.txt new file mode 100644 index 000000000..733ac21ff --- /dev/null +++ b/src/xrt/state_trackers/steamvr_drv/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +set(OVRD_SOURCE_FILES + ovrd_driver.cpp + ovrd_interface.h + ) + +add_library(st_ovrd STATIC + ${OVRD_SOURCE_FILES} + ) +target_include_directories(st_ovrd INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ) +target_link_libraries(st_ovrd PRIVATE + xrt-interfaces + xrt-external-openvr + aux_math + ) diff --git a/src/xrt/state_trackers/steamvr_drv/meson.build b/src/xrt/state_trackers/steamvr_drv/meson.build new file mode 100644 index 000000000..17aa5806b --- /dev/null +++ b/src/xrt/state_trackers/steamvr_drv/meson.build @@ -0,0 +1,20 @@ +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +st_ovrd_include = include_directories('.') + +lib_st_ovrd = static_library( + 'st_ovrd', + files( + 'ovrd_driver.cpp', + 'ovrd_interface.h' + ), + include_directories: [ + openvr_include, + st_include, # Sigh debian meson requires this. + xrt_include, + ], + dependencies: aux_util, + c_args: compile_args, + cpp_args: compile_args, +) diff --git a/src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp b/src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp new file mode 100644 index 000000000..50cf15bb7 --- /dev/null +++ b/src/xrt/state_trackers/steamvr_drv/ovrd_driver.cpp @@ -0,0 +1,1558 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Main driver code for @ref st_ovrd. + * @author Jakob Bornecrantz + * @author Christoph Haag + * @ingroup st_ovrd + */ + +#include +#include + +#include "ovrd_log.hpp" +#include "openvr_driver.h" + +extern "C" { +#include "ovrd_interface.h" + +#include + +#include +#include "os/os_time.h" +#include "util/u_debug.h" +#include "util/u_device.h" + +#include "xrt/xrt_defines.h" +#include "xrt/xrt_device.h" +#include "xrt/xrt_instance.h" +} + +//! When set, all controllers pretend to be Index controllers. Provides best +//! compatibility with legacy games due to steamvr's legacy binding for Index +//! controllers, but input mapping may be incomplete or not ideal. +DEBUG_GET_ONCE_BOOL_OPTION(emulate_index_controller, + "STEAMVR_EMULATE_INDEX_CONTROLLER", + true) + +DEBUG_GET_ONCE_NUM_OPTION(scale_percentage, + "XRT_COMPOSITOR_SCALE_PERCENTAGE", + 140) + +//#define DUMP_POSE +//#define DUMP_POSE_CONTROLLERS + + +/* + * Controller + */ + +struct MonadoInputComponent +{ + bool has_component; + bool x; + bool y; +}; + +struct SteamVRDriverControl +{ + const char *steamvr_control_path; + vr::VRInputComponentHandle_t control_handle; + enum xrt_input_type monado_input_type; + enum xrt_input_name monado_input_name; + struct MonadoInputComponent component; +}; + +static void +copy_vec3(struct xrt_vec3 *from, double *to) +{ + to[0] = from->x; + to[1] = from->y; + to[2] = from->z; +} + +static void +copy_quat(struct xrt_quat *from, vr::HmdQuaternion_t *to) +{ + to->x = from->x; + to->y = from->y; + to->z = from->z; + to->w = from->w; +} + +static void +apply_pose(struct xrt_space_relation *rel, vr::DriverPose_t *m_pose) +{ + if ((rel->relation_flags & + XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT) != 0) { + copy_quat(&rel->pose.orientation, &m_pose->qRotation); + } else { + m_pose->result = vr::TrackingResult_Running_OutOfRange; + m_pose->poseIsValid = false; + } + + if ((rel->relation_flags & XRT_SPACE_RELATION_POSITION_TRACKED_BIT) != + 0) { + copy_vec3(&rel->pose.position, m_pose->vecPosition); + } else { + } + + if ((rel->relation_flags & + XRT_SPACE_RELATION_LINEAR_VELOCITY_VALID_BIT) != 0) { + // linear velocity in world space + copy_vec3(&rel->linear_velocity, m_pose->vecVelocity); + } + + if ((rel->relation_flags & + XRT_SPACE_RELATION_ANGULAR_VELOCITY_VALID_BIT) != 0) { + // angular velocity reported by monado in world space, + // expected by steamvr to be in "controller space" + struct xrt_quat orientation_inv; + math_quat_invert(&rel->pose.orientation, &orientation_inv); + + struct xrt_vec3 vel; + math_quat_rotate_derivative(&orientation_inv, + &rel->angular_velocity, &vel); + + copy_vec3(&vel, m_pose->vecAngularVelocity); + } +} + +class CDeviceDriver_Monado_Controller : public vr::ITrackedDeviceServerDriver +{ +public: + CDeviceDriver_Monado_Controller(struct xrt_instance *xinst, + struct xrt_device *xdev, + enum xrt_hand hand) + : m_xdev(xdev), m_hand(hand) + { + ovrd_log("Creating Controller %s\n", xdev->str); + + m_handed_controller = true; + + m_emulate_index_controller = + debug_get_bool_option_emulate_index_controller(); + + if (m_emulate_index_controller) { + ovrd_log("Emulating Index Controller\n"); + } else { + ovrd_log("Using Monado Controller profile\n"); + } + + m_unObjectId = vr::k_unTrackedDeviceIndexInvalid; + m_pose = {}; + + strncpy(m_sSerialNumber, xdev->str, XRT_DEVICE_NAME_LEN); + strncpy(m_sModelNumber, xdev->str, XRT_DEVICE_NAME_LEN); + + + switch (this->m_xdev->name) { + case XRT_DEVICE_INDEX_CONTROLLER: + if (hand == XRT_HAND_LEFT) { + m_render_model = + "{indexcontroller}valve_controller_knu_1_0_" + "left"; + } + if (hand == XRT_HAND_RIGHT) { + m_render_model = + "{indexcontroller}valve_controller_knu_1_0_" + "right"; + } + break; + case XRT_DEVICE_VIVE_WAND: + m_render_model = "vr_controller_vive_1_5"; + break; + case XRT_DEVICE_VIVE_TRACKER_GEN1: + case XRT_DEVICE_VIVE_TRACKER_GEN2: + m_render_model = "{htc}vr_tracker_vive_1_0"; + break; + case XRT_DEVICE_PSMV: + case XRT_DEVICE_HYDRA: + case XRT_DEVICE_DAYDREAM: + case XRT_DEVICE_GENERIC_HMD: + default: m_render_model = "locator_one_sided"; break; + } + + ovrd_log("Render model based on Monado: %s\n", m_render_model); + } + virtual ~CDeviceDriver_Monado_Controller() {} + + void + AddControl(enum xrt_input_type monado_input_type, + const char *steamvr_control_path, + enum xrt_input_name monado_input_name, + struct MonadoInputComponent *component) + { + m_controls[m_control_count].monado_input_type = + monado_input_type; + m_controls[m_control_count].steamvr_control_path = + steamvr_control_path; + m_controls[m_control_count].monado_input_name = + monado_input_name; + if (component != NULL) { + m_controls[m_control_count].component = *component; + } else { + m_controls[m_control_count].component.has_component = + false; + } + + if (monado_input_type == XRT_INPUT_TYPE_BOOLEAN) { + vr::VRDriverInput()->CreateBooleanComponent( + m_ulPropertyContainer, steamvr_control_path, + &m_controls[m_control_count].control_handle); + + } else if (monado_input_type == + XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) { + vr::VRDriverInput()->CreateScalarComponent( + m_ulPropertyContainer, steamvr_control_path, + &m_controls[m_control_count].control_handle, + vr::VRScalarType_Absolute, + vr::VRScalarUnits_NormalizedTwoSided); + + } else if (monado_input_type == + XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE) { + vr::VRDriverInput()->CreateScalarComponent( + m_ulPropertyContainer, steamvr_control_path, + &m_controls[m_control_count].control_handle, + vr::VRScalarType_Absolute, + vr::VRScalarUnits_NormalizedOneSided); + + } else if (monado_input_type == + XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) { + vr::VRDriverInput()->CreateScalarComponent( + m_ulPropertyContainer, steamvr_control_path, + &m_controls[m_control_count].control_handle, + vr::VRScalarType_Absolute, + vr::VRScalarUnits_NormalizedTwoSided); + } + + ovrd_log("Added input %s\n", steamvr_control_path); + m_control_count++; + } + + void + AddEmulatedIndexControls() + { + switch (this->m_xdev->name) { + case XRT_DEVICE_INDEX_CONTROLLER: { + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trigger/value", + XRT_INPUT_INDEX_TRIGGER_VALUE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/click", + XRT_INPUT_INDEX_TRIGGER_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/touch", + XRT_INPUT_INDEX_TRIGGER_TOUCH, NULL); + + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/click", + XRT_INPUT_INDEX_SYSTEM_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/touch", + XRT_INPUT_INDEX_SYSTEM_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/click", + XRT_INPUT_INDEX_A_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/touch", + XRT_INPUT_INDEX_A_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/click", + XRT_INPUT_INDEX_B_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/touch", + XRT_INPUT_INDEX_B_TOUCH, NULL); + + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/grip/force", + XRT_INPUT_INDEX_SQUEEZE_FORCE, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/grip/value", + XRT_INPUT_INDEX_SQUEEZE_VALUE, NULL); + + struct MonadoInputComponent x = {true, true, false}; + struct MonadoInputComponent y = {true, false, true}; + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/thumbstick/click", + XRT_INPUT_INDEX_THUMBSTICK_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/thumbstick/touch", + XRT_INPUT_INDEX_THUMBSTICK_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/thumbstick/x", + XRT_INPUT_INDEX_THUMBSTICK, &x); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/thumbstick/y", + XRT_INPUT_INDEX_THUMBSTICK, &y); + + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trackpad/force", + XRT_INPUT_INDEX_TRACKPAD_FORCE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trackpad/touch", + XRT_INPUT_INDEX_TRACKPAD_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/x", + XRT_INPUT_INDEX_TRACKPAD, &x); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/y", + XRT_INPUT_INDEX_TRACKPAD, &y); + + vr::VRDriverInput()->CreateHapticComponent( + m_ulPropertyContainer, "/output/haptic", + &m_hapticHandle); + } + + break; + case XRT_DEVICE_VIVE_WAND: { + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trigger/value", + XRT_INPUT_VIVE_TRIGGER_VALUE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/click", + XRT_INPUT_VIVE_TRIGGER_CLICK, NULL); + + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/click", + XRT_INPUT_VIVE_SYSTEM_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/click", + XRT_INPUT_VIVE_TRACKPAD_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/click", + XRT_INPUT_VIVE_MENU_CLICK, NULL); + + struct MonadoInputComponent x = {true, true, false}; + struct MonadoInputComponent y = {true, false, true}; + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trackpad/touch", + XRT_INPUT_VIVE_TRACKPAD_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/x", XRT_INPUT_VIVE_TRACKPAD, + &x); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/y", XRT_INPUT_VIVE_TRACKPAD, + &y); + + vr::VRDriverInput()->CreateHapticComponent( + m_ulPropertyContainer, "/output/haptic", + &m_hapticHandle); + } break; + case XRT_DEVICE_VIVE_TRACKER_GEN1: + case XRT_DEVICE_VIVE_TRACKER_GEN2: break; + case XRT_DEVICE_PSMV: { + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trigger/value", + XRT_INPUT_PSMV_TRIGGER_VALUE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/click", + XRT_INPUT_PSMV_MOVE_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/click", + XRT_INPUT_PSMV_PS_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/click", + XRT_INPUT_PSMV_CROSS_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/click", + XRT_INPUT_PSMV_SQUARE_CLICK, NULL); + + vr::VRDriverInput()->CreateHapticComponent( + m_ulPropertyContainer, "/output/haptic", + &m_hapticHandle); + } + + break; + case XRT_DEVICE_HYDRA: + case XRT_DEVICE_DAYDREAM: + case XRT_DEVICE_GENERIC_HMD: + default: break; + } + } + + void + AddMonadoControls() + { + switch (this->m_xdev->name) { + case XRT_DEVICE_INDEX_CONTROLLER: { + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trigger/value", + XRT_INPUT_INDEX_TRIGGER_VALUE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/click", + XRT_INPUT_INDEX_TRIGGER_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trigger/touch", + XRT_INPUT_INDEX_TRIGGER_TOUCH, NULL); + + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/click", + XRT_INPUT_INDEX_SYSTEM_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/system/touch", + XRT_INPUT_INDEX_SYSTEM_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/click", + XRT_INPUT_INDEX_A_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/a/touch", + XRT_INPUT_INDEX_A_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/click", + XRT_INPUT_INDEX_B_CLICK, NULL); + AddControl(XRT_INPUT_TYPE_BOOLEAN, "/input/b/touch", + XRT_INPUT_INDEX_B_TOUCH, NULL); + + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/grip/force", + XRT_INPUT_INDEX_SQUEEZE_FORCE, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/grip/value", + XRT_INPUT_INDEX_SQUEEZE_VALUE, NULL); + + struct MonadoInputComponent x = {true, true, false}; + struct MonadoInputComponent y = {true, false, true}; + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/thumbstick/click", + XRT_INPUT_INDEX_THUMBSTICK_CLICK, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/thumbstick/touch", + XRT_INPUT_INDEX_THUMBSTICK_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/thumbstick/x", + XRT_INPUT_INDEX_THUMBSTICK, &x); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/thumbstick/y", + XRT_INPUT_INDEX_THUMBSTICK, &y); + + + AddControl(XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, + "/input/trackpad/force", + XRT_INPUT_INDEX_TRACKPAD_FORCE, NULL); + + AddControl(XRT_INPUT_TYPE_BOOLEAN, + "/input/trackpad/touch", + XRT_INPUT_INDEX_TRACKPAD_TOUCH, NULL); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/x", + XRT_INPUT_INDEX_TRACKPAD, &x); + + AddControl(XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE, + "/input/trackpad/y", + XRT_INPUT_INDEX_TRACKPAD, &y); + + vr::VRDriverInput()->CreateHapticComponent( + m_ulPropertyContainer, "/output/haptic", + &m_hapticHandle); + } + + break; + case XRT_DEVICE_VIVE_WAND: break; + case XRT_DEVICE_VIVE_TRACKER_GEN1: + case XRT_DEVICE_VIVE_TRACKER_GEN2: break; + case XRT_DEVICE_PSMV: + case XRT_DEVICE_HYDRA: + case XRT_DEVICE_DAYDREAM: + case XRT_DEVICE_GENERIC_HMD: + default: break; + } + } + + void + PoseUpdateThreadFunction() + { + ovrd_log("Starting controller pose update thread for %s\n", + m_xdev->str); + + while (m_poseUpdating) { + //! @todo figure out the best pose update rate + std::this_thread::sleep_for( + std::chrono::milliseconds(1)); + + if (m_unObjectId != vr::k_unTrackedDeviceIndexInvalid) { + vr::VRServerDriverHost() + ->TrackedDevicePoseUpdated( + m_unObjectId, GetPose(), + sizeof(vr::DriverPose_t)); + } + } + + ovrd_log("Stopping controller pose update thread for %s\n", + m_xdev->str); + } + + vr::EVRInitError + Activate(vr::TrackedDeviceIndex_t unObjectId) + { + ovrd_log("Activating Controller SteamVR[%d]\n", unObjectId); + + if (!m_handed_controller) { + //! @todo handle trackers etc + ovrd_log("Unhandled: %s\n", m_xdev->str); + return vr::VRInitError_Unknown; + } + + m_unObjectId = unObjectId; + + if (this->m_xdev == NULL) { + ovrd_log("Error: xdev NULL\n"); + return vr::VRInitError_Init_InterfaceNotFound; + } + + // clang-format off + + + m_ulPropertyContainer = vr::VRProperties()->TrackedDeviceToPropertyContainer(m_unObjectId); + + // return a constant that's not 0 (invalid) or 1 (reserved for Oculus) + vr::VRProperties()->SetUint64Property(m_ulPropertyContainer, vr::Prop_CurrentUniverseId_Uint64, 2); + vr::VRProperties()->SetInt32Property(m_ulPropertyContainer, vr::Prop_DeviceClass_Int32, vr::TrackedDeviceClass_Controller); + + if (m_hand == XRT_HAND_LEFT) { + ovrd_log("Left Controller\n"); + vr::VRProperties()->SetInt32Property(m_ulPropertyContainer, vr::Prop_ControllerRoleHint_Int32, vr::TrackedControllerRole_LeftHand); + } else if (m_hand == XRT_HAND_RIGHT) { + ovrd_log("Right Controller\n"); + vr::VRProperties()->SetInt32Property(m_ulPropertyContainer, vr::Prop_ControllerRoleHint_Int32, vr::TrackedControllerRole_RightHand); + } + m_pose.poseIsValid = false; + m_pose.deviceIsConnected = true; + m_pose.result = vr::TrackingResult_Uninitialized; + m_pose.willDriftInYaw = !m_xdev->position_tracking_supported; + + if (m_emulate_index_controller) { + m_input_profile = "{indexcontroller}/input/index_controller_profile.json"; + m_controller_type = "knuckles"; + if (m_hand == XRT_HAND_LEFT) { + m_render_model = "{indexcontroller}valve_controller_knu_1_0_left"; + } else if (m_hand == XRT_HAND_RIGHT) { + m_render_model = "{indexcontroller}valve_controller_knu_1_0_right"; + } + } else { + m_input_profile = "{monado}/input/monado_controller_profile.json"; + m_controller_type = "monado_controller"; + } + + ovrd_log("Using input profile %s\n", m_input_profile); + ovrd_log("Using render model%s\n", m_render_model); + vr::VRProperties()->SetStringProperty(m_ulPropertyContainer, vr::Prop_InputProfilePath_String, m_input_profile); + vr::VRProperties()->SetStringProperty(m_ulPropertyContainer, vr::Prop_RenderModelName_String, m_render_model); + vr::VRProperties()->SetStringProperty(m_ulPropertyContainer, vr::Prop_ModelNumber_String, m_xdev->str); + + // clang-format on + + m_control_count = 0; + if (m_emulate_index_controller) { + AddEmulatedIndexControls(); + } else { + AddMonadoControls(); + } + + ovrd_log("Controller %d activated\n", m_unObjectId); + + m_poseUpdateThread = new std::thread( + &CDeviceDriver_Monado_Controller::PoseUpdateThreadFunction, + this); + if (!m_poseUpdateThread) { + ovrd_log( + "Unable to create pose updated thread for %s\n", + m_xdev->str); + return vr::VRInitError_Driver_Failed; + } + + return vr::VRInitError_None; + } + + void + Deactivate() + { + ovrd_log("deactivate controller\n"); + m_poseUpdating = false; + m_poseUpdateThread->join(); + m_unObjectId = vr::k_unTrackedDeviceIndexInvalid; + } + + void + EnterStandby() + { + ovrd_log("standby controller\n"); + } + + void * + GetComponent(const char *pchComponentNameAndVersion) + { + // deprecated API + // ovrd_log("get controller component %s\n", + // pchComponentNameAndVersion); + return NULL; + } + + /** debug request from a client */ + void + DebugRequest(const char *pchRequest, + char *pchResponseBuffer, + uint32_t unResponseBufferSize) + { + if (unResponseBufferSize >= 1) + pchResponseBuffer[0] = 0; + } + + vr::DriverPose_t + GetPose() + { + m_pose.poseIsValid = true; + m_pose.result = vr::TrackingResult_Running_OK; + m_pose.deviceIsConnected = true; + + enum xrt_input_name grip_name; + + //! @todo better method to find grip name + if (m_xdev->name == XRT_DEVICE_VIVE_WAND) { + grip_name = XRT_INPUT_VIVE_GRIP_POSE; + } else if (m_xdev->name == XRT_DEVICE_INDEX_CONTROLLER) { + grip_name = XRT_INPUT_INDEX_GRIP_POSE; + } else if (m_xdev->name == XRT_DEVICE_PSMV) { + grip_name = XRT_INPUT_PSMV_GRIP_POSE; + } else if (m_xdev->name == XRT_DEVICE_DAYDREAM) { + grip_name = XRT_INPUT_DAYDREAM_POSE; + } else if (m_xdev->name == XRT_DEVICE_HYDRA) { + grip_name = XRT_INPUT_HYDRA_POSE; + } else { + ovrd_log("Unhandled device name %u\n", m_xdev->name); + grip_name = XRT_INPUT_GENERIC_HEAD_POSE; // ??? + } + + timepoint_ns now_ns = os_monotonic_get_ns(); + + struct xrt_space_relation rel; + xrt_device_get_tracked_pose(m_xdev, grip_name, now_ns, &rel); + + struct xrt_pose *offset = &m_xdev->tracking_origin->offset; + + struct xrt_space_graph graph = {}; + m_space_graph_add_relation(&graph, &rel); + m_space_graph_add_pose_if_not_identity(&graph, offset); + m_space_graph_resolve(&graph, &rel); + + apply_pose(&rel, &m_pose); + +#ifdef DUMP_POSE_CONTROLLERS + ovrd_log("get controller %d pose %f %f %f %f, %f %f %f\n", + m_unObjectId, m_pose.qRotation.x, m_pose.qRotation.y, + m_pose.qRotation.z, m_pose.qRotation.w, + m_pose.vecPosition[0], m_pose.vecPosition[1], + m_pose.vecPosition[2]); +#endif + + vr::HmdQuaternion_t identityquat{1, 0, 0, 0}; + m_pose.qWorldFromDriverRotation = identityquat; + m_pose.qDriverFromHeadRotation = identityquat; + m_pose.vecDriverFromHeadTranslation[0] = 0.f; + m_pose.vecDriverFromHeadTranslation[1] = 0.f; + m_pose.vecDriverFromHeadTranslation[2] = 0.f; + + return m_pose; + } + + void + RunFrame() + { + m_xdev->update_inputs(m_xdev); + + + for (int i = 0; i < m_control_count; i++) { + + // ovrd_log("Update %d: %s\n", i, + // m_controls[i].steamvr_control_path); + + enum xrt_input_name input_name = + m_controls[i].monado_input_name; + + struct xrt_input *input = NULL; + for (uint32_t ii = 0; ii < m_xdev->num_inputs; ii++) { + if (m_xdev->inputs[ii].name == input_name) { + input = &m_xdev->inputs[ii]; + break; + } + } + + if (input == NULL) { + ovrd_log("Input for %s not found!\n", + m_controls[i].steamvr_control_path); + continue; + } + + vr::VRInputComponentHandle_t handle = + m_controls[i].control_handle; + + if (m_controls[i].monado_input_type == + XRT_INPUT_TYPE_BOOLEAN) { + bool state = input->value.boolean; + vr::VRDriverInput()->UpdateBooleanComponent( + handle, state, 0); + // ovrd_log("Update %s: %d\n", + // m_controls[i].steamvr_control_path, state); + // printf("Update %s: %d\n", + // m_controls[i].steamvr_control_path, + // state); + } + if (m_controls[i].monado_input_type == + XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE || + m_controls[i].monado_input_type == + XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE) { + + float value; + if (m_controls[i].component.has_component && + m_controls[i].component.x) { + value = input->value.vec2.x; + } else if (m_controls[i] + .component.has_component && + m_controls[i].component.y) { + value = input->value.vec2.y; + } else { + value = input->value.vec1.x; + } + + vr::VRDriverInput()->UpdateScalarComponent( + handle, value, 0); + // ovrd_log("Update %s: %f\n", + // m_controls[i].steamvr_control_path, + // state->x); + // printf("Update %s: %f\n", + // m_controls[i].steamvr_control_path, + // value); + } + } + } + + + vr::VRControllerState_t + GetControllerState() + { + // deprecated API + vr::VRControllerState_t controllerstate; + return controllerstate; + } + + bool + TriggerHapticPulse(uint32_t unAxisId, + uint16_t usPulseDurationMicroseconds) + { + // deprecated API + return false; + } + + std::string + GetSerialNumber() const + { + ovrd_log("get controller serial number: %s\n", m_sSerialNumber); + return m_sSerialNumber; + } + + + struct xrt_device *m_xdev; + vr::DriverPose_t m_pose; + vr::TrackedDeviceIndex_t m_unObjectId; + vr::PropertyContainerHandle_t m_ulPropertyContainer; + + bool m_emulate_index_controller = false; + +private: + char m_sSerialNumber[XRT_DEVICE_NAME_LEN]; + char m_sModelNumber[XRT_DEVICE_NAME_LEN]; + + const char *m_controller_type = NULL; + + const char *m_render_model = NULL; + enum xrt_hand m_hand; + bool m_handed_controller; + + const char *m_input_profile = NULL; + + struct SteamVRDriverControl m_controls[50]; + int m_control_count; + + vr::VRInputComponentHandle_t m_hapticHandle = 0; + + bool m_poseUpdating = true; + std::thread *m_poseUpdateThread = NULL; +}; + +/* + * + * Device driver + * + */ + +class CDeviceDriver_Monado : public vr::ITrackedDeviceServerDriver, + public vr::IVRDisplayComponent +{ +public: + CDeviceDriver_Monado(struct xrt_instance *xinst, + struct xrt_device *xdev) + : m_xdev(xdev) + { + //! @todo latency + m_flSecondsFromVsyncToPhotons = 0.011; + + float ns = + (float)m_xdev->hmd->screens->nominal_frame_interval_ns; + m_flDisplayFrequency = 1. / ns * 1000. * 1000. * 1000.; + ovrd_log("display frequency from device: %f\n", + m_flDisplayFrequency); + + // steamvr can really misbehave when freq is inf or so + if (m_flDisplayFrequency < 0 || m_flDisplayFrequency > 1000) { + ovrd_log("Setting display frequency to 60 Hz!\n"); + m_flDisplayFrequency = 60.; + } + + //! @todo get ipd user setting from monado session + float ipd_meters = 0.063; + struct xrt_vec3 ipd_vec = {ipd_meters, 0, 0}; + + for (int view = 0; view < 2; view++) { + xdev->get_view_pose(xdev, &ipd_vec, view, + &m_view_pose[view]); + } + + //! @todo more versatile IPD calculation + float actual_ipd = + -m_view_pose[0].position.x + m_view_pose[1].position.x; + + m_flIPD = actual_ipd; + + ovrd_log("Seconds from Vsync to Photons: %f\n", + m_flSecondsFromVsyncToPhotons); + ovrd_log("Display Frequency: %f\n", m_flDisplayFrequency); + ovrd_log("IPD: %f\n", m_flIPD); + }; + virtual ~CDeviceDriver_Monado(){}; + + // clang-format off + + // ITrackedDeviceServerDriver + virtual vr::EVRInitError Activate(vr::TrackedDeviceIndex_t unObjectId); + virtual void Deactivate(); + virtual void EnterStandby(); + virtual void *GetComponent(const char *pchComponentNameAndVersion); + virtual void DebugRequest(const char *pchRequest, char *pchResponseBuffer, uint32_t unResponseBufferSize); + virtual vr::DriverPose_t GetPose(); + + // IVRDisplayComponent + virtual void GetWindowBounds(int32_t *pnX, int32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight); + virtual bool IsDisplayOnDesktop(); + virtual bool IsDisplayRealDisplay(); + virtual void GetRecommendedRenderTargetSize(uint32_t *pnWidth, uint32_t *pnHeight); + virtual void GetEyeOutputViewport(vr::EVREye eEye, uint32_t *pnX, uint32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight); + virtual void GetProjectionRaw(vr::EVREye eEye, float *pfLeft, float *pfRight, float *pfTop, float *pfBottom); + virtual vr::DistortionCoordinates_t ComputeDistortion(vr::EVREye eEye, float fU, float fV); + + // clang-format on + +private: + struct xrt_device *m_xdev = NULL; + + // clang-format off + + vr::TrackedDeviceIndex_t m_trackedDeviceIndex = 0; + vr::PropertyContainerHandle_t m_ulPropertyContainer = vr::k_ulInvalidPropertyContainer; + + float m_flSecondsFromVsyncToPhotons = -1; + float m_flDisplayFrequency = -1; + float m_flIPD = -1; + + struct xrt_pose m_view_pose[2]; + + bool m_poseUpdating = true; + std::thread *m_poseUpdateThread = NULL; + virtual void PoseUpdateThreadFunction(); + + // clang-format on +}; + +static void +create_translation_rotation_matrix(struct xrt_pose *pose, + struct vr::HmdMatrix34_t *res) +{ + struct xrt_vec3 t = pose->position; + struct xrt_quat r = pose->orientation; + res->m[0][0] = (1.0f - 2.0f * (r.y * r.y + r.z * r.z)); + res->m[1][0] = (r.x * r.y + r.z * r.w) * 2.0f; + res->m[2][0] = (r.x * r.z - r.y * r.w) * 2.0f; + res->m[0][1] = (r.x * r.y - r.z * r.w) * 2.0f; + res->m[1][1] = (1.0f - 2.0f * (r.x * r.x + r.z * r.z)); + res->m[2][1] = (r.y * r.z + r.x * r.w) * 2.0f; + res->m[0][2] = (r.x * r.z + r.y * r.w) * 2.0f; + res->m[1][2] = (r.y * r.z - r.x * r.w) * 2.0f; + res->m[2][2] = (1.0f - 2.0f * (r.x * r.x + r.y * r.y)); + res->m[0][3] = t.x; + res->m[1][3] = t.y; + res->m[2][3] = t.z; +} + +void +CDeviceDriver_Monado::PoseUpdateThreadFunction() +{ + ovrd_log("Starting HMD pose update thread\n"); + + while (m_poseUpdating) { + //! @todo figure out the best pose update rate + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + vr::VRServerDriverHost()->TrackedDevicePoseUpdated( + m_trackedDeviceIndex, GetPose(), sizeof(vr::DriverPose_t)); + } + ovrd_log("Stopping HMD pose update thread\n"); +} + +vr::EVRInitError +CDeviceDriver_Monado::Activate(vr::TrackedDeviceIndex_t unObjectId) +{ + ovrd_log("Activate tracked device %u: %s\n", unObjectId, m_xdev->str); + + m_trackedDeviceIndex = unObjectId; + + // clang-format off + + m_ulPropertyContainer = vr::VRProperties()->TrackedDeviceToPropertyContainer(unObjectId); + //! @todo: proper serial and model number + vr::VRProperties()->SetStringProperty(m_ulPropertyContainer, vr::Prop_ModelNumber_String, m_xdev->str); + vr::VRProperties()->SetFloatProperty(m_ulPropertyContainer, vr::Prop_UserIpdMeters_Float, m_flIPD); + vr::VRProperties()->SetFloatProperty(m_ulPropertyContainer, vr::Prop_UserHeadToEyeDepthMeters_Float, 0.f); + vr::VRProperties()->SetFloatProperty(m_ulPropertyContainer, vr::Prop_DisplayFrequency_Float, m_flDisplayFrequency); + vr::VRProperties()->SetFloatProperty( m_ulPropertyContainer, vr::Prop_SecondsFromVsyncToPhotons_Float, m_flSecondsFromVsyncToPhotons); + + // return a constant that's not 0 (invalid) or 1 (reserved for Oculus) + vr::VRProperties()->SetUint64Property( m_ulPropertyContainer, vr::Prop_CurrentUniverseId_Uint64, 2); + + // avoid "not fullscreen" warnings from vrmonitor + //vr::VRProperties()->SetBoolProperty(m_ulPropertyContainer, vr::Prop_IsOnDesktop_Bool, false); + + // clang-format on + + + //! @todo update when ipd changes + vr::HmdMatrix34_t left; + create_translation_rotation_matrix(&m_view_pose[0], &left); + vr::HmdMatrix34_t right; + create_translation_rotation_matrix(&m_view_pose[1], &right); + vr::VRServerDriverHost()->TrackedDeviceDisplayTransformUpdated( + m_trackedDeviceIndex, left, right); + + + m_poseUpdateThread = new std::thread( + &CDeviceDriver_Monado::PoseUpdateThreadFunction, this); + if (!m_poseUpdateThread) { + ovrd_log("Unable to create pose updated thread for %s\n", + m_xdev->str); + return vr::VRInitError_Driver_Failed; + } + + return vr::VRInitError_None; +} + +void +CDeviceDriver_Monado::Deactivate() +{ + m_poseUpdating = false; + m_poseUpdateThread->join(); + ovrd_log("Deactivate\n"); +} + +void +CDeviceDriver_Monado::EnterStandby() +{ + ovrd_log("Enter Standby\n"); +} + +void * +CDeviceDriver_Monado::GetComponent(const char *pchComponentNameAndVersion) +{ + // clang-format off + if (strcmp(pchComponentNameAndVersion, vr::IVRDisplayComponent_Version) == 0) { + return (vr::IVRDisplayComponent *)this; + } + // clang-format on + + return NULL; +} + +void +CDeviceDriver_Monado::DebugRequest(const char *pchRequest, + char *pchResponseBuffer, + uint32_t unResponseBufferSize) +{ + //! @todo +} + +static inline vr::HmdQuaternion_t +HmdQuaternion_Init(double w, double x, double y, double z) +{ + vr::HmdQuaternion_t quat; + quat.w = w; + quat.x = x; + quat.y = y; + quat.z = z; + return quat; +} + +vr::DriverPose_t +CDeviceDriver_Monado::GetPose() +{ + + struct xrt_space_relation rel; + xrt_device_get_tracked_pose(m_xdev, XRT_INPUT_GENERIC_HEAD_POSE, 1, + &rel); + + struct xrt_pose *offset = &m_xdev->tracking_origin->offset; + + struct xrt_space_graph graph = {}; + m_space_graph_add_relation(&graph, &rel); + m_space_graph_add_pose_if_not_identity(&graph, offset); + m_space_graph_resolve(&graph, &rel); + + vr::DriverPose_t t = {}; + + //! @todo: Monado head model? + t.shouldApplyHeadModel = !m_xdev->position_tracking_supported; + t.willDriftInYaw = !m_xdev->position_tracking_supported; + + t.qWorldFromDriverRotation = HmdQuaternion_Init(1, 0, 0, 0); + t.qDriverFromHeadRotation = HmdQuaternion_Init(1, 0, 0, 0); + + t.poseIsValid = + rel.relation_flags & XRT_SPACE_RELATION_ORIENTATION_VALID_BIT; + t.result = vr::TrackingResult_Running_OK; + t.deviceIsConnected = true; + + apply_pose(&rel, &t); + +#ifdef DUMP_POSE + ovrd_log("get hmd pose %f %f %f %f, %f %f %f\n", t.qRotation.x, + t.qRotation.y, t.qRotation.z, t.qRotation.w, t.vecPosition[0], + t.vecPosition[1], t.vecPosition[2]); +#endif + + //! @todo + // copy_vec3(&rel.angular_velocity, t.vecAngularVelocity); + // copy_vec3(&rel.angular_acceleration, t.vecAngularAcceleration); + + // ovrd_log("Vel: %f %f %f\n", t.vecAngularVelocity[0], + // t.vecAngularVelocity[1], t.vecAngularVelocity[2]); ovrd_log("Accel: + // %f %f %f\n", t.vecAngularAcceleration[0], + // t.vecAngularAcceleration[1], t.vecAngularAcceleration[2]); + + return t; +} + +void +CDeviceDriver_Monado::GetWindowBounds(int32_t *pnX, + int32_t *pnY, + uint32_t *pnWidth, + uint32_t *pnHeight) +{ + // offset in extended mode, e.g. to the right of a 1920x1080 monitor + *pnX = 1920; + *pnY = 0; + + *pnWidth = m_xdev->hmd->views[0].viewport.w_pixels + + m_xdev->hmd->views[1].viewport.w_pixels; + + uint32_t max_height = m_xdev->hmd->views[0].viewport.h_pixels > + m_xdev->hmd->views[1].viewport.h_pixels + ? m_xdev->hmd->views[0].viewport.h_pixels + : m_xdev->hmd->views[1].viewport.h_pixels; + *pnHeight = max_height; + + ovrd_log("Window Bounds: %dx%d\n", *pnWidth, *pnHeight); +} + +bool +CDeviceDriver_Monado::IsDisplayOnDesktop() +{ + return false; +} + +bool +CDeviceDriver_Monado::IsDisplayRealDisplay() +{ + return true; +} + +void +CDeviceDriver_Monado::GetRecommendedRenderTargetSize(uint32_t *pnWidth, + uint32_t *pnHeight) +{ + int scale = debug_get_num_option_scale_percentage(); + float fscale = (float)scale / 100.f; + + + + *pnWidth = m_xdev->hmd->screens[0].w_pixels * fscale; + *pnHeight = m_xdev->hmd->screens[0].h_pixels * fscale; + + ovrd_log("Render Target Size: %dx%d (%fx)\n", *pnWidth, *pnHeight, + fscale); +} + +void +CDeviceDriver_Monado::GetEyeOutputViewport(vr::EVREye eEye, + uint32_t *pnX, + uint32_t *pnY, + uint32_t *pnWidth, + uint32_t *pnHeight) +{ + *pnWidth = m_xdev->hmd->views[eEye].viewport.w_pixels; + *pnHeight = m_xdev->hmd->views[eEye].viewport.h_pixels; + + *pnY = 0; + if (eEye == vr::Eye_Left) { + *pnX = 0; + } else { + *pnX = m_xdev->hmd->views[0].viewport.w_pixels; + } + + ovrd_log("Output Viewport for eye %d: %dx%d offset %dx%d\n", eEye, + *pnWidth, *pnHeight, *pnX, *pnY); +} + +void +CDeviceDriver_Monado::GetProjectionRaw(vr::EVREye eEye, + float *pfLeft, + float *pfRight, + float *pfTop, + float *pfBottom) +{ + *pfLeft = tanf(m_xdev->hmd->views[eEye].fov.angle_left); + *pfRight = tanf(m_xdev->hmd->views[eEye].fov.angle_right); + *pfTop = tanf(-m_xdev->hmd->views[eEye].fov.angle_up); + *pfBottom = tanf(-m_xdev->hmd->views[eEye].fov.angle_down); + ovrd_log("Projection Raw: L%f R%f T%f B%f\n", *pfLeft, *pfRight, *pfTop, + *pfBottom); +} + +vr::DistortionCoordinates_t +CDeviceDriver_Monado::ComputeDistortion(vr::EVREye eEye, float fU, float fV) +{ + /** Used to return the post-distortion UVs for each color channel. + * UVs range from 0 to 1 with 0,0 in the upper left corner of the + * source render target. The 0,0 to 1,1 range covers a single eye. */ + + struct xrt_uv_triplet d; + + if (!m_xdev->compute_distortion(m_xdev, eEye, fU, fV, &d)) { + ovrd_log("Failed to compute distortion for view %d at %f,%f!\n", + eEye, fU, fV); + + vr::DistortionCoordinates_t coordinates; + coordinates.rfBlue[0] = fU; + coordinates.rfBlue[1] = fV; + coordinates.rfGreen[0] = fU; + coordinates.rfGreen[1] = fV; + coordinates.rfRed[0] = fU; + coordinates.rfRed[1] = fV; + return coordinates; + } + + vr::DistortionCoordinates_t coordinates; + coordinates.rfRed[0] = d.r.x; + coordinates.rfRed[1] = d.r.y; + coordinates.rfGreen[0] = d.g.x; + coordinates.rfGreen[1] = d.g.y; + coordinates.rfBlue[0] = d.b.x; + coordinates.rfBlue[1] = d.b.y; + + // ovrd_log("Computed distortion for view %d at %f,%f -> %f,%f | %f,%f | + // %f,%f!\n", eEye, fU, fV, d.r.x, d.r.y, d.g.x, d.g.y, d.b.x, d.b.y); + + return coordinates; +} + + +/* + * + * Device driver server + * + */ + +class CServerDriver_Monado : public vr::IServerTrackedDeviceProvider +{ +public: + CServerDriver_Monado() : m_MonadoDeviceDriver(NULL) {} + + // clang-format off + virtual ~CServerDriver_Monado() {}; + virtual vr::EVRInitError Init(vr::IVRDriverContext *pDriverContext); + virtual void Cleanup(); + virtual const char *const * GetInterfaceVersions() { return vr::k_InterfaceVersions; } + virtual void RunFrame(); + virtual bool ShouldBlockStandbyMode() { return false; } + virtual void EnterStandby() {} + virtual void LeaveStandby() {} + virtual void HandleHapticEvent(vr::VREvent_t *event); + // clang-format on + +private: + struct xrt_instance *m_xinst = NULL; + struct xrt_device *m_xhmd = NULL; + + CDeviceDriver_Monado *m_MonadoDeviceDriver = NULL; + CDeviceDriver_Monado_Controller *m_left = NULL; + CDeviceDriver_Monado_Controller *m_right = NULL; +}; + +CServerDriver_Monado g_serverDriverMonado; + +#define NUM_XDEVS 16 + +vr::EVRInitError +CServerDriver_Monado::Init(vr::IVRDriverContext *pDriverContext) +{ + + VR_INIT_SERVER_DRIVER_CONTEXT(pDriverContext); + ovrd_log_init(vr::VRDriverLog()); + + ovrd_log("Initializing Monado driver\n"); + + //! @todo instance initialization is difficult to replicate + + int ret; + ret = xrt_instance_create(NULL, &m_xinst); + if (ret < 0) { + ovrd_log("Failed to create instance\n"); + return vr::VRInitError_Init_HmdNotFound; + } + + struct xrt_device *xdevs[NUM_XDEVS] = {0}; + + ret = xrt_instance_select(m_xinst, xdevs, NUM_XDEVS); + + int head, left, right; + + u_device_assign_xdev_roles(xdevs, NUM_XDEVS, &head, &left, &right); + + if (ret < 0 || head == XRT_DEVICE_ROLE_UNASSIGNED) { + ovrd_log("Failed to select HMD\n"); + xrt_instance_destroy(&m_xinst); + return vr::VRInitError_Init_HmdNotFound; + } + + m_xhmd = xdevs[head]; + + ovrd_log("Selected HMD %s\n", m_xhmd->str); + m_MonadoDeviceDriver = new CDeviceDriver_Monado(m_xinst, m_xhmd); + //! @todo provide a serial number + vr::VRServerDriverHost()->TrackedDeviceAdded( + m_xhmd->str, vr::TrackedDeviceClass_HMD, m_MonadoDeviceDriver); + + struct xrt_device *left_xdev = NULL; + if (left != XRT_DEVICE_ROLE_UNASSIGNED) { + left_xdev = xdevs[left]; + } + struct xrt_device *right_xdev = NULL; + if (right != XRT_DEVICE_ROLE_UNASSIGNED) { + right_xdev = xdevs[right]; + } + + ovrd_log("Left Controller: %s\n", left_xdev ? left_xdev->str : ""); + ovrd_log("Right Controller: %s\n", right_xdev ? right_xdev->str : ""); + + u_device_setup_tracking_origins(m_xhmd, left_xdev, right_xdev); + + if (left) { + m_left = new CDeviceDriver_Monado_Controller(m_xinst, left_xdev, + XRT_HAND_LEFT); + vr::VRServerDriverHost()->TrackedDeviceAdded( + m_left->GetSerialNumber().c_str(), + vr::TrackedDeviceClass_Controller, m_left); + } + if (right) { + m_right = new CDeviceDriver_Monado_Controller( + m_xinst, right_xdev, XRT_HAND_RIGHT); + vr::VRServerDriverHost()->TrackedDeviceAdded( + m_right->GetSerialNumber().c_str(), + vr::TrackedDeviceClass_Controller, m_right); + } + + return vr::VRInitError_None; +} + +void +CServerDriver_Monado::Cleanup() +{ + if (m_MonadoDeviceDriver != NULL) { + delete m_MonadoDeviceDriver; + m_MonadoDeviceDriver = NULL; + } + + if (m_xhmd) { + xrt_device_destroy(&m_xhmd); + } + + if (m_left) { + xrt_device_destroy(&m_left->m_xdev); + } + if (m_right) { + xrt_device_destroy(&m_right->m_xdev); + } + + if (m_xinst) { + xrt_instance_destroy(&m_xinst); + } + + return; +} + +void +CServerDriver_Monado::HandleHapticEvent(vr::VREvent_t *event) +{ + float freq = event->data.hapticVibration.fFrequency; + float amp = event->data.hapticVibration.fAmplitude; + float duration = event->data.hapticVibration.fDurationSeconds; + + + ovrd_log("Haptic vibration %fs %fHz %famp\n", duration, freq, amp); + + struct xrt_device *xdev = NULL; + if (m_left && m_left->m_ulPropertyContainer == + event->data.hapticVibration.containerHandle) { + xdev = m_left->m_xdev; + ovrd_log("Haptic vibration left\n"); + } else if (m_right && m_right->m_ulPropertyContainer == + event->data.hapticVibration.containerHandle) { + xdev = m_right->m_xdev; + ovrd_log("Haptic vibration right\n"); + } else { + ovrd_log("Haptic vibration ignored\n"); + } + + union xrt_output_value out; + out.vibration.amplitude = amp; + if (duration > 0.00001) { + out.vibration.duration = duration * 1000. * 1000. * 1000.; + } else { + out.vibration.duration = XRT_MIN_HAPTIC_DURATION; + } + out.vibration.frequency = freq; + if (xdev != NULL) { + enum xrt_output_name name; + switch (xdev->name) { + case XRT_DEVICE_INDEX_CONTROLLER: + name = XRT_OUTPUT_NAME_INDEX_HAPTIC; + break; + case XRT_DEVICE_VIVE_WAND: + name = XRT_OUTPUT_NAME_VIVE_HAPTIC; + break; + case XRT_DEVICE_PSMV: + name = XRT_OUTPUT_NAME_PSMV_RUMBLE_VIBRATION; + break; + default: + //! @todo + name = XRT_OUTPUT_NAME_PSMV_RUMBLE_VIBRATION; + break; + } + + xdev->set_output(xdev, name, &out); + } +} + +void +CServerDriver_Monado::RunFrame() +{ + if (m_left) { + m_left->RunFrame(); + } + if (m_right) { + m_right->RunFrame(); + } + + // https://github.com/ValveSoftware/openvr/issues/719#issuecomment-358038640 + struct vr::VREvent_t event; + while (vr::VRServerDriverHost()->PollNextEvent( + &event, sizeof(struct vr::VREvent_t))) { + switch (event.eventType) { + case vr::VREvent_Input_HapticVibration: + HandleHapticEvent(&event); + break; + case vr::VREvent_PropertyChanged: + // ovrd_log("Property changed\n"); + break; + case vr::VREvent_TrackedDeviceActivated: + ovrd_log("Device activated %d\n", + event.trackedDeviceIndex); + break; + case vr::VREvent_TrackedDeviceUserInteractionStarted: + ovrd_log("Device interaction started %d\n", + event.trackedDeviceIndex); + break; + case vr::VREvent_IpdChanged: + ovrd_log("ipd changed to %fm\n", + event.data.ipd.ipdMeters); + break; + case vr::VREvent_ActionBindingReloaded: + ovrd_log("action binding reloaded\n"); + break; + case vr::VREvent_StatusUpdate: + ovrd_log("EVRState: %d\n", + event.data.status.statusState); + break; + + case vr::VREvent_TrackedDeviceRoleChanged: + // device roles are for legacy input + case vr::VREvent_ChaperoneUniverseHasChanged: + case vr::VREvent_ProcessQuit: + case vr::VREvent_QuitAcknowledged: + case vr::VREvent_ProcessDisconnected: + case vr::VREvent_ProcessConnected: + case vr::VREvent_DashboardActivated: + case vr::VREvent_DashboardDeactivated: + case vr::VREvent_Compositor_ChaperoneBoundsShown: + case vr::VREvent_Compositor_ChaperoneBoundsHidden: break; + + default: ovrd_log("Unhandled Event: %d\n", event.eventType); + } + } +} + + +/* + * + * Whatchdog code + * + */ + +class CWatchdogDriver_Monado : public vr::IVRWatchdogProvider +{ +public: + CWatchdogDriver_Monado() + { + ovrd_log("Created watchdog object\n"); + m_pWatchdogThread = nullptr; + } + + // clang-format off + virtual ~CWatchdogDriver_Monado() {}; + virtual vr::EVRInitError Init(vr::IVRDriverContext *pDriverContext); + virtual void Cleanup(); + // clang-format on + +private: + std::thread *m_pWatchdogThread; +}; + +CWatchdogDriver_Monado g_watchdogDriverMonado; +bool g_bExiting = false; + +void +WatchdogThreadFunction() +{ + while (!g_bExiting) { +#if defined(_WINDOWS) + // on windows send the event when the Y key is pressed. + if ((0x01 & GetAsyncKeyState('Y')) != 0) { + // Y key was pressed. + vr::VRWatchdogHost()->WatchdogWakeUp(); + } + std::this_thread::sleep_for(std::chrono::microseconds(500)); +#else + ovrd_log("Watchdog wakeup\n"); + // for the other platforms, just send one every five seconds + std::this_thread::sleep_for(std::chrono::seconds(1)); + vr::VRWatchdogHost()->WatchdogWakeUp(); +#endif + } + + ovrd_log("Watchdog exit\n"); +} + +vr::EVRInitError +CWatchdogDriver_Monado::Init(vr::IVRDriverContext *pDriverContext) +{ + VR_INIT_WATCHDOG_DRIVER_CONTEXT(pDriverContext); + ovrd_log_init(vr::VRDriverLog()); + + // Watchdog mode on Windows starts a thread that listens for the 'Y' key + // on the keyboard to be pressed. A real driver should wait for a system + // button event or something else from the the hardware that signals + // that the VR system should start up. + g_bExiting = false; + + ovrd_log("starting watchdog thread\n"); + + m_pWatchdogThread = new std::thread(WatchdogThreadFunction); + if (!m_pWatchdogThread) { + ovrd_log("Unable to create watchdog thread\n"); + return vr::VRInitError_Driver_Failed; + } + + return vr::VRInitError_None; +} + +void +CWatchdogDriver_Monado::Cleanup() +{ + g_bExiting = true; + if (m_pWatchdogThread) { + m_pWatchdogThread->join(); + delete m_pWatchdogThread; + m_pWatchdogThread = nullptr; + } + + ovrd_log_cleanup(); +} + + +/* + * + * 'Exported' functions. + * + */ + +void * +ovrd_hmd_driver_impl(const char *pInterfaceName, int *pReturnCode) +{ + // clang-format off + if (0 == strcmp(vr::IServerTrackedDeviceProvider_Version, pInterfaceName)) { + return &g_serverDriverMonado; + } + if (0 == strcmp(vr::IVRWatchdogProvider_Version, pInterfaceName)) { + return &g_watchdogDriverMonado; + } + // clang-format on + + ovrd_log("Unimplemented interface: %s\n", pInterfaceName); + + if (pReturnCode) { + *pReturnCode = vr::VRInitError_Init_InterfaceNotFound; + } + + return NULL; +} diff --git a/src/xrt/state_trackers/steamvr_drv/ovrd_interface.h b/src/xrt/state_trackers/steamvr_drv/ovrd_interface.h new file mode 100644 index 000000000..d6a13c3a9 --- /dev/null +++ b/src/xrt/state_trackers/steamvr_drv/ovrd_interface.h @@ -0,0 +1,29 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Interface to the Monado SteamVR Driver exporter. + * @author Jakob Bornecrantz + * @ingroup st_ovrd + */ + +#pragma once + +#include "xrt/xrt_defines.h" + +/*! + * @defgroup st_ovrd SteamVR driver provider + * + * Wraps a @ref xrt_instance and one or more @ref xrt_device and exposes those + * to SteamVR via the OpenVR driver interface. + * + * @ingroup xrt + */ + +/*! + * Implementation of the HmdDriverFactory function. + * + * @ingroup st_ovrd + */ +void * +ovrd_hmd_driver_impl(const char *pInterfaceName, int *pReturnCode); diff --git a/src/xrt/state_trackers/steamvr_drv/ovrd_log.hpp b/src/xrt/state_trackers/steamvr_drv/ovrd_log.hpp new file mode 100644 index 000000000..683dffbad --- /dev/null +++ b/src/xrt/state_trackers/steamvr_drv/ovrd_log.hpp @@ -0,0 +1,62 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Logger code. + * @author Jakob Bornecrantz + * @ingroup st_ovrd + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "xrt/xrt_compiler.h" + +#ifdef __cplusplus +} +#endif + +#include "openvr_driver.h" + +static vr::IVRDriverLog *s_pLogFile = NULL; + +static inline void +ovrd_log_init(vr::IVRDriverLog *pDriverLog) +{ + // Noop + s_pLogFile = vr::VRDriverLog(); +} + +// Can not use the XRT_PRINTF_FORMAT macro on a function definition. +static inline void +ovrd_log(const char *fmt, ...) XRT_PRINTF_FORMAT(1, 2); + +static inline void +ovrd_log(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + char buf[1024]; +#if defined(WIN32) + vsprintf_s(buf, pMsgFormat, args); +#else + vsnprintf(buf, sizeof(buf), fmt, args); +#endif + + if (s_pLogFile) + s_pLogFile->Log(buf); + + va_end(args); +} + +static inline void +ovrd_log_cleanup() +{ + s_pLogFile = nullptr; +} diff --git a/src/xrt/targets/CMakeLists.txt b/src/xrt/targets/CMakeLists.txt index 5a805fcda..58a518f91 100644 --- a/src/xrt/targets/CMakeLists.txt +++ b/src/xrt/targets/CMakeLists.txt @@ -29,3 +29,7 @@ if(XRT_FEATURE_SERVICE AND XRT_FEATURE_OPENXR) add_subdirectory(service) endif() endif() + +if(XRT_FEATURE_STEAMVR_PLUGIN) + add_subdirectory(steamvr_drv) +endif() diff --git a/src/xrt/targets/meson.build b/src/xrt/targets/meson.build index e7b07947e..32899dc54 100644 --- a/src/xrt/targets/meson.build +++ b/src/xrt/targets/meson.build @@ -78,3 +78,6 @@ if get_option('service') subdir('ctl') endif +if get_option('steamvr_plugin') + subdir('steamvr_drv') +endif diff --git a/src/xrt/targets/steamvr_drv/CMakeLists.txt b/src/xrt/targets/steamvr_drv/CMakeLists.txt new file mode 100644 index 000000000..85d34bfb3 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + + +add_library(driver_monado MODULE + main.c + ) + +target_link_libraries(driver_monado PRIVATE + xrt-external-openvr + aux_util + st_ovrd + st_prober + target_lists + target_instance_no_comp + ) + + +# meta data that the steamvr plugin needs in the base directory of the steamvr plugin +file(COPY driver.vrdrivermanifest DESTINATION ${CMAKE_BINARY_DIR}/steamvr-monado) +file(COPY resources DESTINATION ${CMAKE_BINARY_DIR}/steamvr-monado) + +#determine the output directory for the steamvr plugin +if (WIN32) + # FIXME need to account for different architectures + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/win64 CACHE TYPE INTERNAL) + else() + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/win32 CACHE TYPE INTERNAL) + endif() +elseif(APPLE) + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/osx64 CACHE STRING INTERNAL) + else() + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/osx32 CACHE STRING INTERNAL) + endif() +elseif(NOT ANDROID) + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/linux64 CACHE STRING INTERNAL) + else() + set(PLUGINDIR ${CMAKE_BINARY_DIR}/steamvr-monado/bin/linux32 CACHE STRING INTERNAL) + endif() +endif() + +MESSAGE("SteamVR plugin path: ${PLUGINDIR}") +set_target_properties(driver_monado PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PLUGINDIR}") + +# don't add lib prefix to driver_monado.so +SET_TARGET_PROPERTIES(driver_monado PROPERTIES PREFIX "") + +install(DIRECTORY "${PROJECT_BINARY_DIR}/steamvr-monado" DESTINATION "${CMAKE_INSTALL_PREFIX}/share") diff --git a/src/xrt/targets/steamvr_drv/copy_assets.py b/src/xrt/targets/steamvr_drv/copy_assets.py new file mode 100644 index 000000000..1681f3e4c --- /dev/null +++ b/src/xrt/targets/steamvr_drv/copy_assets.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +import os, sys, shutil + +print(sys.argv[1], sys.argv[2], sys.argv[3]) + +is_file = sys.argv[1] == "FILE" +is_dir = sys.argv[1] == "DIRECTORY" + +# get absolute input and output paths +input_path = os.path.join( + os.getenv('MESON_SOURCE_ROOT'), + os.getenv('MESON_SUBDIR'), + sys.argv[2]) + +output_path = os.path.join( + os.getenv('MESON_BUILD_ROOT'), + sys.argv[3]) + +# make sure destination directory exists +os.makedirs(os.path.dirname(output_path), exist_ok=True) + +if is_file: + shutil.copyfile(input_path, output_path) +elif is_dir: + shutil.copytree(input_path, output_path) + +print("Copying asset " + str(input_path) + " to " + str(output_path)) diff --git a/src/xrt/targets/steamvr_drv/copy_plugin.py b/src/xrt/targets/steamvr_drv/copy_plugin.py new file mode 100644 index 000000000..e1719d8eb --- /dev/null +++ b/src/xrt/targets/steamvr_drv/copy_plugin.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +import os, sys, shutil + +# get absolute input and output paths +input_path = os.path.join( + sys.argv[1]) + +output_path = os.path.join( + sys.argv[2]) + +# make sure destination directory exists +os.makedirs(os.path.dirname(output_path), exist_ok=True) + +shutil.copyfile(input_path, output_path) + +print("Copying plugin " + str(input_path) + " to " + str(output_path)) diff --git a/src/xrt/targets/steamvr_drv/driver.vrdrivermanifest b/src/xrt/targets/steamvr_drv/driver.vrdrivermanifest new file mode 100644 index 000000000..e572158e6 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/driver.vrdrivermanifest @@ -0,0 +1,11 @@ +{ + "alwaysActivate": false, + "name" : "monado", + "directory" : "", + "resourceOnly" : false, + + "hmd_presence" : + [ + "*.*" + ] +} diff --git a/src/xrt/targets/steamvr_drv/main.c b/src/xrt/targets/steamvr_drv/main.c new file mode 100644 index 000000000..016574ff0 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/main.c @@ -0,0 +1,28 @@ +// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Very simple target main file to export the right symbol. + * @author Jakob Bornecrantz + * @author Christoph Haag + * @ingroup st_ovrd + */ + +#include "ovrd_interface.h" + +#if defined(_WIN32) +#define HMD_DLL_EXPORT __declspec(dllexport) +#define HMD_DLL_IMPORT __declspec(dllimport) +#elif defined(__GNUC__) || defined(COMPILER_GCC) || defined(__APPLE__) +#define HMD_DLL_EXPORT __attribute__((visibility("default"))) +#define HMD_DLL_IMPORT +#else +#error "Unsupported Platform." +#endif + + +HMD_DLL_EXPORT void * +HmdDriverFactory(const char *pInterfaceName, int *pReturnCode) +{ + return ovrd_hmd_driver_impl(pInterfaceName, pReturnCode); +} diff --git a/src/xrt/targets/steamvr_drv/meson.build b/src/xrt/targets/steamvr_drv/meson.build new file mode 100644 index 000000000..a7d94136d --- /dev/null +++ b/src/xrt/targets/steamvr_drv/meson.build @@ -0,0 +1,65 @@ +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 + +plugin_dir = 'steamvr-monado' + +driver_monado = shared_library( + 'driver_monado', + files( + 'main.c' + ), + link_whole: [ + lib_target_instance_no_comp, + lib_st_ovrd, + ], + include_directories: [ + openvr_include, + st_ovrd_include, + openvr_include, + aux_include, + xrt_include, + ], + link_with : driver_libs, + dependencies : [pthreads, libjpeg], + # build 'driver_monado.so' instead of 'libdriver_monado.so' + name_prefix: '', +) + +copy_asset = find_program('copy_assets.py') +copy_plugin = find_program('copy_plugin.py') + + +run_command(copy_asset, 'FILE', 'driver.vrdrivermanifest', join_paths(plugin_dir, 'driver.vrdrivermanifest')) +run_command(copy_asset, 'DIRECTORY', 'resources', join_paths(plugin_dir, 'resources')) + +plugin_archdir = '' + +if host_machine.system() == 'windows' + if host_machine.cpu_family() == 'x86' + plugin_archdir = 'win32' + elif host_machine.cpu_family() == 'x86_64' + plugin_archdir = 'win64' + endif +elif host_machine.system() == 'linux' + if host_machine.cpu_family() == 'x86' + plugin_archdir = 'linux32' + elif host_machine.cpu_family() == 'x86_64' + plugin_archdir = 'linux64' + endif +endif + +custom_target( + 'plugin_copy', + depends : driver_monado, + input : driver_monado, + output : 'fake_plugin', + command : [copy_plugin, '@INPUT@', join_paths(plugin_dir, 'bin', plugin_archdir, '@PLAINNAME@')], + build_by_default : true +) + +if meson.version().version_compare('<0.56') + build_root = meson.build_root() +else + build_root = meson.project_build_root() +endif +install_subdir(join_paths(build_root, plugin_dir), install_dir: join_paths(get_option('prefix'), 'share')) diff --git a/src/xrt/targets/steamvr_drv/resources/driver.vrresources b/src/xrt/targets/steamvr_drv/resources/driver.vrresources new file mode 100644 index 000000000..f7e588667 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/resources/driver.vrresources @@ -0,0 +1,47 @@ +{ + "jsonid" : "vrresources", + "statusicons" : { + "HMD" : { + "Prop_NamedIconPathDeviceOff_String" : "{monado}/icons/headset_monado_status_off.png", + "Prop_NamedIconPathDeviceSearching_String" : "{monado}/icons/headset_monado_status_searching.gif", + "Prop_NamedIconPathDeviceSearchingAlert_String" : "{monado}/icons/headset_monado_status_searching_alert.gif", + "Prop_NamedIconPathDeviceReady_String" : "{monado}/icons/headset_monado_status_ready.png", + "Prop_NamedIconPathDeviceReadyAlert_String" : "{monado}/icons/headset_monado_status_ready_alert.png", + "Prop_NamedIconPathDeviceNotReady_String" : "{monado}/icons/headset_monado_status_error.png", + "Prop_NamedIconPathDeviceStandby_String" : "{monado}/icons/headset_monado_status_standby.png", + "Prop_NamedIconPathDeviceAlertLow_String" : "{monado}/icons/headset_monado_status_ready_low.png" + }, + + "Model-v Defaults" : { + "Prop_NamedIconPathDeviceOff_String" : "{monado}/icons/headset_monado_status_off.png", + "Prop_NamedIconPathDeviceSearching_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceSearchingAlert_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceReady_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceReadyAlert_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceNotReady_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceStandby_String" : "Prop_NamedIconPathDeviceOff_String", + "Prop_NamedIconPathDeviceAlertLow_String" : "Prop_NamedIconPathDeviceOff_String" + }, + + "Model-v1.0" : { + "Alias" : "Model-v Defaults", + "Prop_NamedIconPathDeviceAlertLow_String" : "{monado}/icons/headset_model1_alertlow.png" + }, + + "Model-v2.0" : { + "Alias" : "Model-v1.0", + "Prop_NamedIconPathDeviceAlertLow_String" : "{monado}/icons/headset_model2_alertlow.png" + }, + + "Controller" : { + "Prop_NamedIconPathDeviceOff_String" : "{monado}/icons/controller_status_off.png", + "Prop_NamedIconPathDeviceSearching_String" : "{monado}/icons/controller_status_searching.gif", + "Prop_NamedIconPathDeviceSearchingAlert_String" : "{monado}/icons/controller_status_searching_alert.gif", + "Prop_NamedIconPathDeviceReady_String" : "{monado}/icons/controller_status_ready.png", + "Prop_NamedIconPathDeviceReadyAlert_String" : "{monado}/icons/controller_status_ready_alert.png", + "Prop_NamedIconPathDeviceNotReady_String" : "{monado}/icons/controller_status_error.png", + "Prop_NamedIconPathDeviceStandby_String" : "{monado}/icons/controller_status_standby.png", + "Prop_NamedIconPathDeviceAlertLow_String" : "{monado}/icons/controller_status_ready_low.png" + } + } +} diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_error.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_error.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc1636ebaff0f25550cdba0ca3a696d7284db68 GIT binary patch literal 4590 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_off.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_off.png new file mode 100644 index 0000000000000000000000000000000000000000..4403086be6af9fd5c1234c1a7b355eba57caf307 GIT binary patch literal 3213 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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�PV*|l=S%D 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_alert.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..fdba490778229fbf8a7af256061e93bcc8b063ed GIT binary patch literal 4694 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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 B+`s?; literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_low.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready_low.png new file mode 100644 index 0000000000000000000000000000000000000000..671e0cee6a37b859a802c3bfcd66877aedacd3e5 GIT binary patch literal 4650 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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 C=_7ss literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_searching_alert.gif b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_searching_alert.gif new file mode 100644 index 0000000000000000000000000000000000000000..a75e4a62789af44e65766aabeaa05eae0322bde7 GIT binary patch literal 4633 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 PeSw3x`T`7yzJN6VXO~9y literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_standby.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_standby.png new file mode 100644 index 0000000000000000000000000000000000000000..f4007b034415ea439ff3298021f2d2a64daffb76 GIT binary patch literal 3348 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_error.png b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_error.png new file mode 100644 index 0000000000000000000000000000000000000000..c9401de23d265dda051b398ae867b83117a45043 GIT binary patch literal 2951 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 Mr>mdKI;Vst0OoQTcmMzZ literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_off.png b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_off.png new file mode 100644 index 0000000000000000000000000000000000000000..22e884f822ec834537030c852a5176707fa97209 GIT binary patch literal 1449 zcmeAS@N?(olHy`uVBq!ia0y~yU@&4}U{K&-V_;wiP}EjrU|^6eag8Vm&QB{TPb^Ah za7@WhN>%X8O-xS>N=;0uEIgTN!@$6tmKhRJ65;D(m7Jfemk3g$SCL!500K7l6$OdO z*{LN8NvY|XdA3ULckfqH$V{%1*XSQL?vFu&J=B z$SufCElE_U$j!+swyLmI0-I}l#hD=EpgRf0Gw!Z$#{Ilm}X!Bo#!H`&0@ zP{GVh&(Orw%*;?n!N|bSNZ-In-@r`Q(8S8r%*x1I0Sc7t6l{u8(yW49+@N+9rKH&^ zWt5Z@Sn2DRmzV368|&p4rRy77T3Uk4Ff!6DN=ef#uFNY*tkBIXR)(42l3JWxlvz-c znV+WsGBGhJzqG_wNeOCfMQ(wwFWlg~VrW1m=jZBIBo^o!>KW+g=7RhM_Hc1YP%6S1 zSXCE?R2HP_2c;J0mlh?b2BoGcBUy#tB9OuYUtcT#qRjNnyu=)r{N&Qy)VvZ;7h9!@ z+ycFn%oHnQ6BjpUV@pF*7jtu0Q$s^XCnHNsGiOU<3r7QIM-wBM8QAo?89O-{xf#2- zIJ>$U8oHV}nOnFyyPCL}Te=uJI$Aoz^m^tMmn7yTr^4*b%uKO@>UGAe*UGslHL)bW zC?r2W2bKZ?GV)9Ei!<^I6r6+26g0v!^HTE5i#0*f0}gvDm(-%nveXo}qWoM1aQIqf zVzJ-A!oS#t1Oo%(O-~ockcwMxW}MY_NfbHO+ixh8 z^l9e$2nDkgP0h|v;t@gHrtEj_lI6DM&JOYl3lp0pqP8$HVDiiwUteR1^DEO0V;|p( z{vJ1X=DU^gm#d%WzWcqW_+0V5YUZ0c+m=Wy+OovskoY3qQ@!HTriDLI<~VjhAR}PY zHI^ML>I!=|?n~2S3FH-Xh`71zbX#Au;|AVIVJl772Ds$4>~uRVC8^Oc>BaUnf-bGz z=GAP0AK3Oeh?Z}j`|`C)Q(22euf&bz9sB2OV)tnZTl;Nxf})%6vzq4U4@-9T%QT0~ zs}7M`SK1)x|GVRGg+WjB0h`v21JwmwX*T&Ce2*_1_!*sDqUKcM;vaJ{S26d2Uiu%t zvKK~o7xeS$F8RC0bn1Djryh(-3H)oCl%qC9DJCuNzVp*)@|Ve*$JwZfV{ciOPkM}oN%`HDax%j2^+%mt8lCQeELd5;oIO_c{JpV^( z?aI<~QXK-DUz9vN^VD;r_pg{6bF^2QufDp~<%Grx)0x5ydrP*ZtzDvWjJUw0gT-G@yGywoAc?;qI literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready.png b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe9fe20ccca28e0b6b75bc69d9eabd37f060a8a GIT binary patch literal 2400 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready_low.png b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_ready_low.png new file mode 100644 index 0000000000000000000000000000000000000000..bbaf61d53ccfc37fababfb493d90085b50699906 GIT binary patch literal 2909 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_searching_alert.gif b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_searching_alert.gif new file mode 100644 index 0000000000000000000000000000000000000000..7c23fae09c37d16e88952bf33f5e169a0fbf323b GIT binary patch literal 23550 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