Skip to content

File oxr_relay.hpp

File List > openxr_interface > oxr_relay.hpp

Go to the documentation of this file

#pragma once
#ifndef USING_OPENXR
#    define USING_OPENXR
#endif

#define DOUBLE_INCLUDE
#include "illixr/data_format/latency_data.hpp"
#ifdef ILLIXR_ENABLE_BOBA
#    include "illixr/data_format/openxr_view_frame.hpp"
#endif
#include "illixr/data_format/poses/combined_pose.hpp"
#ifdef ILLIXR_ENABLE_QUEST_CONTROLLERS
#    include "illixr/data_format/quest_controller.hpp"
#endif
#ifdef ILLIXR_ENABLE_BOBA
#    include "illixr/data_format/serialization/openxr_view_frame.hpp"
#    include "illixr/data_format/serialization/quest_controller.hpp"
#endif
#include "illixr/switchboard.hpp"
#include "illixr/threadloop.hpp"
#undef DOUBLE_INCLUDE

#include 
#include 
#include 
#include 
#include 

#define OXR_CheckErrors(cmd, pfunc)                                                                                     \
    do {                                                                                                                \
        XrResult res = cmd;                                                                                             \
        if (XR_FAILED(res)) {                                                                                           \
            spdlog::get("illixr")->error("OpenXR error {} at {}:{} from {}", static_cast(res), __FILE__, __LINE__, \
                                         #pfunc);                                                                       \
            throw std::runtime_error("Call failed");                                                                    \
        }                                                                                                               \
    } while (0)
#define OXR(func) OXR_CheckErrors(func, #func);

namespace ILLIXR {

class oxr_interface;

struct pose_history_entry {
    data_format::pose::xrt_space_relation pose;           
    time_point                            generated_time; 
    XrTime                                xr_time;        
};

class oxr_relay : public threadloop {
public:
    oxr_relay(const std::string& name, phonebook* pb);

    ~oxr_relay() override;

    bool get_pose_history(uint64_t id, pose_history_entry& out_entry) const;

#ifdef ILLIXR_ENABLE_BOBA
    void publish_boba_input(XrTime predicted_time, XrDuration predicted_period, XrBool32 should_render,
                            XrViewStateFlags view_flags, const XrView views[2], const XrViewConfigurationView view_configs[2]);

#endif

protected:
    skip_option _p_should_skip() override;

    void _p_one_iteration() override;

private:
    friend oxr_interface;

    void initialize(XrInstance instance, XrSession session, XrSpace local, XrSpace view);

    void destroy();

    void update_time(XrTime time) {
        predicted_time_.store(time);
    }

    // Pose tracking
    XrResult get_head_pose(XrTime time, data_format::pose::head_pose_type* out_pose);
    // ==================== Time offset calibration ====================

    void calibrate_time_offsets();
    // ==================== Hand Tracking ====================

    bool init_hand_tracking();

    void destroy_hand_tracking();

    void update_hand_tracking(XrTime predicted_time);

    // ==================== Hand Interaction ====================

    bool init_hand_interaction();

    void destroy_hand_interaction();

    void update_hand_interaction(XrTime predicted_time);

#ifdef ILLIXR_ENABLE_QUEST_CONTROLLERS
    bool create_controller_actions();

    bool suggest_controller_bindings();

    void refresh_controller_profiles();

    bool query_controller_hand(std::size_t hand_index, XrTime sample_time, data_format::quest_hand_controller* hand);

    bool query_controller_pose(XrAction action, XrSpace space, XrPath hand_path, XrTime sample_time,
                               data_format::quest_controller_pose* pose);

    bool query_controller_boolean(XrAction action, XrPath hand_path, data_format::quest_controller_button* button);

    bool query_controller_float(XrAction action, XrPath hand_path, float threshold,
                                data_format::quest_controller_button* button);

    bool query_controller_axis(XrAction action, XrPath hand_path, data_format::quest_controller_axis2d* axis);

    static void merge_controller_button(data_format::quest_controller_button*       destination,
                                        const data_format::quest_controller_button& source);

#endif

    bool sync_actions();

    void push_poses(XrTime predicted_time);

    // ================== Member Variables ====================
    const std::shared_ptr<switchboard>    switchboard_;
    const std::shared_ptr<relative_clock> clock_;

    switchboard::network_writer<data_format::pose::combined_pose> combined_pose_writer_;
#ifdef ILLIXR_ENABLE_BOBA
    switchboard::network_writer<data_format::quest_controller_input> quest_controller_writer_;
    switchboard::network_writer<data_format::openxr_view_frame>      openxr_view_writer_;

#endif

    switchboard::reader<data_format::network_latency_result> latency_reader_;

    data_format::pose::head_pose_type              current_head_pose_;
    data_format::pose::hand_joint_poses_pair       current_hand_poses_;
    data_format::pose::hand_interaction_poses_pair current_hand_interactions_;
    data_format::pose::palm_poses_pair             current_palm_poses_;

    XrInstance          instance_       = XR_NULL_HANDLE;
    XrSession           session_        = XR_NULL_HANDLE;
    XrSpace             local_space_    = XR_NULL_HANDLE;
    XrSpace             view_space_     = XR_NULL_HANDLE;
    std::atomic<XrTime> predicted_time_ = 0;

    XrHandTrackerEXT left_hand_tracker_{XR_NULL_HANDLE};

    XrHandTrackerEXT right_hand_tracker_{XR_NULL_HANDLE};

    PFN_xrCreateHandTrackerEXT xr_create_hand_tracker_{nullptr};

    PFN_xrDestroyHandTrackerEXT xr_destroy_hand_tracker_{nullptr};

    PFN_xrLocateHandJointsEXT xr_locate_hand_joints_{nullptr};

    // ==================== Hand Interaction State ====================

    bool hand_interaction_supported_{false};

    // ==================== Hand Tracking State ====================

    bool hand_tracking_supported_{false};

    XrActionSet hand_interaction_action_set_{XR_NULL_HANDLE};

    XrPath hand_subaction_paths_[2]{XR_NULL_PATH, XR_NULL_PATH};

    XrAction interaction_pose_actions_[data_format::pose::NUM_INTERACTION_POSES]{};

    XrSpace interaction_pose_spaces_[2][data_format::pose::NUM_INTERACTION_POSES]{};

    XrAction interaction_value_actions_[3]{};

    XrAction interaction_ready_actions_[3]{};

    XrAction palm_pose_action_{XR_NULL_HANDLE};

    XrSpace palm_pose_spaces_[2]{XR_NULL_HANDLE, XR_NULL_HANDLE};

#ifdef ILLIXR_ENABLE_QUEST_CONTROLLERS
    // Quest Touch inputs share hand_interaction_action_set_ so the session has
    // exactly one xrAttachSessionActionSets call.
    XrAction                                             controller_trigger_click_action_{XR_NULL_HANDLE};
    XrAction                                             controller_trigger_value_action_{XR_NULL_HANDLE};
    XrAction                                             controller_squeeze_value_action_{XR_NULL_HANDLE};
    XrAction                                             controller_primary_click_action_{XR_NULL_HANDLE};
    XrAction                                             controller_secondary_click_action_{XR_NULL_HANDLE};
    XrAction                                             controller_thumbstick_click_action_{XR_NULL_HANDLE};
    XrAction                                             controller_thumbstick_axis_action_{XR_NULL_HANDLE};
    std::array<data_format::quest_controller_profile, 2> controller_profiles_{data_format::quest_controller_profile::none,
                                                                              data_format::quest_controller_profile::none};
    bool                                                 controller_actions_initialized_{false};

#endif

    std::mutex actions_mutex_;
#ifdef ILLIXR_ENABLE_BOBA
    std::atomic<std::uint64_t> boba_input_sequence_{0};
#endif

    bool                                  initialized_{false};
    bool                                  xr_time_verified_{false};
    bool                                  time_offsets_calibrated_{false};
    std::chrono::steady_clock::time_point last_offset_calibration_{};
    XrTime                                monotonic_to_system_offset_ns_{0};
    XrTime                                xr_to_monotonic_offset_ns_{0};
    uint64_t                              counter_{0};
    data_format::pose::head_pose_type     last_pose_{};

    // Map from combined_pose id to the pose that was sent with that id.
    std::map<uint64_t, pose_history_entry> pose_history_;
    mutable std::mutex                     pose_history_mutex_;
    static constexpr size_t                MAX_POSE_HISTORY = 240;
};

} // namespace ILLIXR