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 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 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready.png b/src/xrt/targets/steamvr_drv/resources/icons/controller_status_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5be67c60e8a034ea16fed2f78a8e7128d23d5a GIT binary patch literal 4296 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 vz9grMXa9e?SXPI?y4;CS&LFfpbTlMTQyi1h{( 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 uz%civn{^c!OzUav694 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!ia0vp^MnJ5v4 zq}24xJX@vryZ0+8WTx0Eg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i={n4aiL` zNmQuF&B-gas<2f8n`;GRgM{^!6u?SKvTcwn`GuBNuFf>#!Gt)CP zF*P$Y)KM@pFf`IP03tJ8LlY}gGb`4?pZBPB7%B|o_|H#M)s)5TT^D5IB>nPO#Z;^O9P zY-wofVs7qgYG~-_WMpY+=4@$f;b`FOXkr921DjqqV<#sgH)9tUXIEE4Lsv5=a|<_T zR}(jLOBX{&M@wgzUeCPZlEl2^RG7V)KzpHjo$>0maxO|uEXgkl$G8yO;GeeeCv{0lv$RV;#QQOs{jsPt4u8R8(5f_SQ?oaTNpW8 zxS9d|ZRliX>FQ`=;b`P);b>u|1l5~DPMGNf9itCQ97u@;CIn1@ASOKN13B=_lbQ$2 zF-5?v!Qv)$f`NhYrl*TzNX4x;GtO$eB#IpC?Kc!k`ZRNWgo0U$re^0S@ra;pQ}#P| z$#PqBX9szOg^5iPQCk=pFnMN;udlJh`IYI0v5)UXe~+6x^WDn$%hk_w-~HZGe6ILj zHS^7!ZA&B;ZCT=RNPLm*sb2AE)54!9a~wM$kP)!y8p{qAb%nhf_oeBv1oDbGMBH3< zx~;F-aRcw9u$88316=Z2cDkLGlGJFJ^kVxOL6=r<^J=!h4{ZA!M9VkNefe6YsjS7K zSK`Lmh{EW^nQFAJB@sGKftC;&hFZ~Z+*$bn)3;KC=m;7C0I`zENQx8U^ z1pYNm%2Au56qA;B-}z}Y`ODo**Kwez$st z$NL+s=9ZtIT>R2{Zkb<4$yeQ7A>#gP9QA$}p8q4Yc4g@~sSbh7FG?PsdFr{*`&Z13 zIod1DS6|)gazbN;=}cjUy(Qby)~-|CZ|az2BeJiFXaDN-yI1~(r=Q>P!RM08o~|u( zuk6xlP(PAw?ZoF8v$4bCddAGCOB2nv?&tgURO#RD`1lU}BPMk$5)2Y0QH!^8`78z% No}R9LF6*2UngA+!3*rC( 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_searching.gif b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_searching.gif new file mode 100644 index 0000000000000000000000000000000000000000..85bb8134d7f1c5a3ab3b5b0ce6c8e93b72e59e08 GIT binary patch literal 20840 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 BNAmyx literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_standby.png b/src/xrt/targets/steamvr_drv/resources/icons/headset_monado_status_standby.png new file mode 100644 index 0000000000000000000000000000000000000000..ff9e40254d277799f1e24a0bbcaa0af8d16f91a8 GIT binary patch literal 1590 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5v4 zq}24xJX@vryZ0+8WTx0Eg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i={n4aiL` zNmQuF&B-gas<2f8n`;GRgM{^!6u?SKvTcwn`GuBNuFf>#!Gt)CP zF*P$Y)KM@pFf`IP03tJ8LlY}gGb`4?pZBPB7%B|o_|H#M)s)5TT^D5IB>nPO#Z;^O9P zY-wofVs7qgYG~-_WMpY+=4@$f;b`FOXkr921FK$VQzs)sM^hJPS2sgLS2IgTR~J_! zCsRi!MylcOS(cjOR+OKs01jWPOf2>rSeTet8krbd7&%+G zngRW7=wxQ;>S$u&Xyj_)Xkn%V)tf?2nCSx@qYp|PNQng|1WbV-COqi_Iq=Mrng`4= zMZl~v^Xklr3=B+Zo-U3d6}R5Z@Xio%6lg0Cy|bddL+yvs0wpOW6?V}O7Z$^~qK*n7 zMXeVHC%d;kS<-T3GW%YQt{+Yw0`lsSflY>rT5680kD~)j9&n2r>X-cG`q#`idFGDC zN6KzC^+#5v?cTj?^{e+UQ_N=jK56##c+x2WWU=OTqSSym@*Q!akn?zdyd z`d@b!d{4VH`;A55nWqeQTC-mz-0AUDbF19>&B^=#SH^>#J`ovL59(SN-}7s+xWR9> zobQI&vV2-(RObfaH2VeOaR+O}OH-8R zwCCF;`+VFIdE#ud^u|{nEuxk$GdG*>i{GysE_WdG248qgXxVgB4qcj-&=K$XmZZaJee24eYtktUcS`{v%32tOm8L64&1@hztBtZ zX=u^vr1uN2Y)#Rhob>+ERwvI`mAw`3KF^m7Dqk)W$)0^FJ$} zaX4gc%&n~{+qu5RhGwK4DE55nv-wov?(8OuMO>+UCRzKx2A#A~Nu9rRPbG8uL%%zx z?B+Z+@9d0Otugt=)U3l9wO3KB)i}#quGQaj zWxquYM?~j+K6xkqz)#OCUes?rG%@qw#2IgvGcYquyfxGQo@IL+s3iAv^>bP0l+XkK Dw#iI8 literal 0 HcmV?d00001 diff --git a/src/xrt/targets/steamvr_drv/resources/localization/localization.json b/src/xrt/targets/steamvr_drv/resources/localization/localization.json new file mode 100644 index 000000000..208091b68 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/resources/localization/localization.json @@ -0,0 +1,6 @@ +[ + { + "language_tag": "en_US", + "monado_controller" : "Monado Driver Controller" + } +] diff --git a/src/xrt/targets/steamvr_drv/resources/settings/default.vrsettings b/src/xrt/targets/steamvr_drv/resources/settings/default.vrsettings new file mode 100644 index 000000000..039cf3fa6 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/resources/settings/default.vrsettings @@ -0,0 +1,5 @@ +{ + "driver_monado" : { + "enable" : true + } +} diff --git a/src/xrt/targets/steamvr_drv/steamvr.vrsettings b/src/xrt/targets/steamvr_drv/steamvr.vrsettings new file mode 100644 index 000000000..a49a22921 --- /dev/null +++ b/src/xrt/targets/steamvr_drv/steamvr.vrsettings @@ -0,0 +1,16 @@ +{ + "jsonid" : "vrsettings", + "driver_lighthouse": { + "enable": false + }, + "driver_oculus": { + "enable": false + }, + "driver_oculus_legacy": { + "enable": false + }, + "driver_monado" : { + "enable": true + }, + "version" : "1" +}