diff options
-rw-r--r-- | doc/classes/XRPositionalTracker.xml | 9 | ||||
-rw-r--r-- | modules/openxr/doc_classes/OpenXRInterface.xml | 27 | ||||
-rw-r--r-- | modules/openxr/openxr_api.cpp | 456 | ||||
-rw-r--r-- | modules/openxr/openxr_api.h | 83 | ||||
-rw-r--r-- | modules/openxr/openxr_interface.cpp | 223 | ||||
-rw-r--r-- | modules/openxr/openxr_interface.h | 48 | ||||
-rw-r--r-- | modules/openxr/openxr_util.cpp | 14 | ||||
-rw-r--r-- | modules/openxr/openxr_util.h | 1 | ||||
-rw-r--r-- | modules/openxr/register_types.cpp | 13 | ||||
-rw-r--r-- | servers/xr/xr_positional_tracker.cpp | 17 | ||||
-rw-r--r-- | servers/xr/xr_positional_tracker.h | 5 |
11 files changed, 698 insertions, 198 deletions
diff --git a/doc/classes/XRPositionalTracker.xml b/doc/classes/XRPositionalTracker.xml index da378759d8..d15558c9e8 100644 --- a/doc/classes/XRPositionalTracker.xml +++ b/doc/classes/XRPositionalTracker.xml @@ -72,6 +72,9 @@ - [code]left_hand[/code] identifies the controller held in the players left hand - [code]right_hand[/code] identifies the controller held in the players right hand </member> + <member name="profile" type="String" setter="set_tracker_profile" getter="get_tracker_profile" default=""""> + The profile associated with this tracker, interface dependent but will indicate the type of controller being tracked. + </member> <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128"> The type of tracker. </member> @@ -109,6 +112,12 @@ Emitted when the state of a pose tracked by this tracker changes. </description> </signal> + <signal name="profile_changed"> + <argument index="0" name="role" type="String" /> + <description> + Emitted when the profile of our tracker changes. + </description> + </signal> </signals> <constants> <constant name="TRACKER_HAND_UNKNOWN" value="0" enum="TrackerHand"> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 1160061e04..74f708bc95 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -10,4 +10,31 @@ <tutorials> <link title="OpenXR documentation">$DOCS_URL/tutorials/vr/openxr/index.html</link> </tutorials> + <signals> + <signal name="pose_recentered"> + <description> + Informs the user queued a recenter of the player position. + </description> + </signal> + <signal name="session_begun"> + <description> + Informs our OpenXR session has been started. + </description> + </signal> + <signal name="session_focussed"> + <description> + Informs our OpenXR session now has focus. + </description> + </signal> + <signal name="session_stopping"> + <description> + Informs our OpenXR session is stopping. + </description> + </signal> + <signal name="session_visible"> + <description> + Informs our OpenXR session is now visible (output is being sent to the HMD). + </description> + </signal> + </signals> </class> diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index e3da214cc8..4d533337f3 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -48,6 +48,8 @@ #include "extensions/openxr_vulkan_extension.h" #endif +#include "modules/openxr/openxr_interface.h" + OpenXRAPI *OpenXRAPI::singleton = nullptr; void OpenXRAPI::setup_global_defs() { @@ -877,7 +879,9 @@ bool OpenXRAPI::on_state_ready() { wrapper->on_state_ready(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_ready(); + } // TODO Tell android @@ -889,6 +893,13 @@ bool OpenXRAPI::on_state_synchronized() { print_line("On state synchronized"); #endif + // Just in case, see if we already have active trackers... + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i]); + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_synchronized(); } @@ -905,7 +916,9 @@ bool OpenXRAPI::on_state_visible() { wrapper->on_state_visible(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_visible(); + } return true; } @@ -919,7 +932,9 @@ bool OpenXRAPI::on_state_focused() { wrapper->on_state_focused(); } - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_focused(); + } return true; } @@ -929,7 +944,9 @@ bool OpenXRAPI::on_state_stopping() { print_line("On state stopping"); #endif - // TODO emit signal + if (xr_interface) { + xr_interface->on_state_stopping(); + } for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_stopping(); @@ -1081,6 +1098,10 @@ void OpenXRAPI::finish() { destroy_instance(); } +void OpenXRAPI::set_xr_interface(OpenXRInterface *p_xr_interface) { + xr_interface = p_xr_interface; +} + void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { registered_extension_wrappers.push_back(p_extension_wrapper); } @@ -1204,20 +1225,38 @@ bool OpenXRAPI::poll_events() { handled |= wrapper->on_event_polled(runtimeEvent); } switch (runtimeEvent.type) { - // case XR_TYPE_EVENT_DATA_EVENTS_LOST: { - // } break; - // case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { - // } break; - // case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { - // } break; + case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + XrEventDataEventsLost *event = (XrEventDataEventsLost *)&runtimeEvent; + + // We probably didn't poll fast enough, just output warning + WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!"); + } break; + case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { + // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent; + + // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask, + // this will allow us to prevent rendering the part of our view which is never displayed giving us + // a decent performance improvement. + + print_verbose("OpenXR EVENT: STUB: visibility mask changed"); + } break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent; + + // TODO We get this event if we're about to loose our OpenXR instance. + // We should queue exiting Godot at this point. + + print_verbose("OpenXR EVENT: instance loss pending at " + itos(event->lossTime)); + return false; + } break; case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent; session_state = event->state; if (session_state >= XR_SESSION_STATE_MAX_ENUM) { - print_line("OpenXR EVENT: session state changed to UNKNOWN -", session_state); + print_verbose("OpenXR EVENT: session state changed to UNKNOWN - " + itos(session_state)); } else { - print_line("OpenXR EVENT: session state changed to", OpenXRUtil::get_session_state_name(session_state)); + print_verbose("OpenXR EVENT: session state changed to " + OpenXRUtil::get_session_state_name(session_state)); switch (session_state) { case XR_SESSION_STATE_IDLE: @@ -1249,13 +1288,29 @@ bool OpenXRAPI::poll_events() { } } } break; - // case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { - // } break; - // case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { - // } break; + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; + + print_verbose("OpenXR EVENT: reference space type " + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + if (event->poseValid && xr_interface) { + xr_interface->on_pose_recentered(); + } + } break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + print_verbose("OpenXR EVENT: interaction profile changed!"); + + XrEventDataInteractionProfileChanged *event = (XrEventDataInteractionProfileChanged *)&runtimeEvent; + + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i], event->session); + } + + } break; default: if (!handled) { - print_line("OpenXR Unhandled event type", OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + print_verbose("OpenXR Unhandled event type " + OpenXRUtil::get_structure_type_name(runtimeEvent.type)); } break; } @@ -1348,9 +1403,21 @@ void OpenXRAPI::pre_render() { XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); if (XR_FAILED(result)) { print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); + + // reset just in case + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + frame_state.shouldRender = false; + return; } + if (frame_state.predictedDisplayPeriod > 500000000) { + // display period more then 0.5 seconds? must be wrong data + print_verbose("OpenXR resetting invalid display period " + rtos(frame_state.predictedDisplayPeriod)); + frame_state.predictedDisplayPeriod = 0; + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_pre_render(); } @@ -1691,38 +1758,97 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l } } -RID OpenXRAPI::path_create(const String p_name) { - ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); +RID OpenXRAPI::get_tracker_rid(XrPath p_path) { + List<RID> current; + tracker_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(current[i]); + if (tracker && tracker->toplevel_path == p_path) { + return current[i]; + } + } - // Encoding our path as a RID is probably overkill but it does future proof this - // Note that we only do this for XrPaths that we access from outside of this class! + return RID(); +} - Path new_path; +RID OpenXRAPI::tracker_create(const String p_name) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); - print_line("Parsing path ", p_name); + Tracker new_tracker; + new_tracker.name = p_name; + new_tracker.toplevel_path = XR_NULL_PATH; + new_tracker.active_profile_rid = RID(); - XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_path.path); + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_tracker.toplevel_path); if (XR_FAILED(result)) { print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); return RID(); } - return xr_path_owner.make_rid(new_path); + return tracker_owner.make_rid(new_tracker); } -void OpenXRAPI::path_free(RID p_path) { - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL(path); +String OpenXRAPI::tracker_get_name(RID p_tracker) { + if (p_tracker.is_null()) { + return String("None"); + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, String()); + + return tracker->name; +} + +void OpenXRAPI::tracker_check_profile(RID p_tracker, XrSession p_session) { + if (p_session == XR_NULL_HANDLE) { + p_session = session; + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); + + if (tracker->toplevel_path == XR_NULL_PATH) { + // no path, how was this even created? + return; + } + + XrInteractionProfileState profile_state = { + XR_TYPE_INTERACTION_PROFILE_STATE, // type + nullptr, // next + XR_NULL_PATH // interactionProfile + }; + + XrResult result = xrGetCurrentInteractionProfile(p_session, tracker->toplevel_path, &profile_state); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get interaction profile for", itos(tracker->toplevel_path), "[", get_error_string(result), "]"); + return; + } + + XrPath new_profile = profile_state.interactionProfile; + XrPath was_profile = get_interaction_profile_path(tracker->active_profile_rid); + if (was_profile != new_profile) { + tracker->active_profile_rid = get_interaction_profile_rid(new_profile); + + if (xr_interface) { + xr_interface->tracker_profile_changed(p_tracker, tracker->active_profile_rid); + } + } +} + +void OpenXRAPI::tracker_free(RID p_tracker) { + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); // there is nothing to free here - xr_path_owner.free(p_path); + tracker_owner.free(p_tracker); } RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); ActionSet action_set; + action_set.name = p_name; action_set.is_attached = false; // create our action set... @@ -1737,7 +1863,7 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE); - print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); + // print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle); if (XR_FAILED(result)) { @@ -1748,6 +1874,17 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n return action_set_owner.make_rid(action_set); } +String OpenXRAPI::action_set_get_name(RID p_action_set) { + if (p_action_set.is_null()) { + return String("None"); + } + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, String()); + + return action_set->name; +} + bool OpenXRAPI::action_set_attach(RID p_action_set) { ActionSet *action_set = action_set_owner.get_or_null(p_action_set); ERR_FAIL_NULL_V(action_set, false); @@ -1776,6 +1913,24 @@ bool OpenXRAPI::action_set_attach(RID p_action_set) { action_set->is_attached = true; + /* For debugging: + print_verbose("Attached set " + action_set->name); + List<RID> action_rids; + action_owner.get_owned_list(&action_rids); + for (int i = 0; i < action_rids.size(); i++) { + Action * action = action_owner.get_or_null(action_rids[i]); + if (action && action->action_set_rid == p_action_set) { + print_verbose(" - Action " + action->name + ": " + OpenXRUtil::get_action_type_name(action->action_type)); + for (int j = 0; j < action->trackers.size(); j++) { + Tracker * tracker = tracker_owner.get_or_null(action->trackers[j].tracker_rid); + if (tracker) { + print_verbose(" - " + tracker->name); + } + } + } + } + */ + return true; } @@ -1790,14 +1945,29 @@ void OpenXRAPI::action_set_free(RID p_action_set) { action_set_owner.free(p_action_set); } -RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths) { +RID OpenXRAPI::get_action_rid(XrAction p_action) { + List<RID> current; + action_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Action *action = action_owner.get_or_null(current[i]); + if (action && action->handle == p_action) { + return current[i]; + } + } + + return RID(); +} + +RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers) { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); Action action; + action.name = p_name; ActionSet *action_set = action_set_owner.get_or_null(p_action_set); ERR_FAIL_NULL_V(action_set, RID()); ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID()); + action.action_set_rid = p_action_set; switch (p_action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: @@ -1821,17 +1991,17 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String } Vector<XrPath> toplevel_paths; - for (int i = 0; i < p_toplevel_paths.size(); i++) { - Path *xr_path = xr_path_owner.get_or_null(p_toplevel_paths[i]); - if (xr_path != nullptr && xr_path->path != XR_NULL_PATH) { - PathWithSpace path_with_space = { - xr_path->path, // toplevel_path + for (int i = 0; i < p_trackers.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(p_trackers[i]); + if (tracker != nullptr && tracker->toplevel_path != XR_NULL_PATH) { + ActionTracker action_tracker = { + p_trackers[i], // tracker XR_NULL_HANDLE, // space false // was_location_valid }; - action.toplevel_paths.push_back(path_with_space); + action.trackers.push_back(action_tracker); - toplevel_paths.push_back(xr_path->path); + toplevel_paths.push_back(tracker->toplevel_path); } } @@ -1848,7 +2018,7 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE); - print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); + // print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle); if (XR_FAILED(result)) { @@ -1859,6 +2029,17 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String return action_owner.make_rid(action); } +String OpenXRAPI::action_get_name(RID p_action) { + if (p_action.is_null()) { + return String("None"); + } + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, String()); + + return action->name; +} + void OpenXRAPI::action_free(RID p_action) { Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL(action); @@ -1870,55 +2051,139 @@ void OpenXRAPI::action_free(RID p_action) { action_owner.free(p_action); } -bool OpenXRAPI::suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings) { - ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); +RID OpenXRAPI::get_interaction_profile_rid(XrPath p_path) { + List<RID> current; + interaction_profile_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(current[i]); + if (ip && ip->path == p_path) { + return current[i]; + } + } + + return RID(); +} + +XrPath OpenXRAPI::get_interaction_profile_path(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return XR_NULL_PATH; + } + + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, XR_NULL_PATH); - XrPath interaction_profile; - Vector<XrActionSuggestedBinding> bindings; + return ip->path; +} - XrResult result = xrStringToPath(instance, p_interaction_profile.utf8().get_data(), &interaction_profile); +RID OpenXRAPI::interaction_profile_create(const String p_name) { + InteractionProfile new_interaction_profile; + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_interaction_profile.path); if (XR_FAILED(result)) { - print_line("OpenXR: failed to get path for ", p_interaction_profile, "! [", get_error_string(result), "]"); - return false; + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); } - for (int i = 0; i < p_bindings.size(); i++) { - XrActionSuggestedBinding binding; + RID existing_ip = get_interaction_profile_rid(new_interaction_profile.path); + if (existing_ip.is_valid()) { + return existing_ip; + } - Action *action = action_owner.get_or_null(p_bindings[i].action); - if (action == nullptr || action->handle == XR_NULL_HANDLE) { - // just skip it - continue; - } + new_interaction_profile.name = p_name; + return interaction_profile_owner.make_rid(new_interaction_profile); +} - binding.action = action->handle; +String OpenXRAPI::interaction_profile_get_name(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return String("None"); + } - result = xrStringToPath(instance, p_bindings[i].path.utf8().get_data(), &binding.binding); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to get path for ", p_bindings[i].path, "! [", get_error_string(result), "]"); - continue; - } + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, String()); + + return ip->name; +} + +void OpenXRAPI::interaction_profile_clear_bindings(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); + + ip->bindings.clear(); +} + +bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + + XrActionSuggestedBinding binding; + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_COND_V(action == nullptr || action->handle == XR_NULL_HANDLE, false); - bindings.push_back(binding); + binding.action = action->handle; + + XrResult result = xrStringToPath(instance, p_path.utf8().get_data(), &binding.binding); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_path, "! [", get_error_string(result), "]"); + return false; } + ip->bindings.push_back(binding); + + return true; +} + +bool OpenXRAPI::interaction_profile_suggest_bindings(RID p_interaction_profile) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + const XrInteractionProfileSuggestedBinding suggested_bindings = { XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type nullptr, // next - interaction_profile, // interactionProfile - uint32_t(bindings.size()), // countSuggestedBindings - bindings.ptr() // suggestedBindings + ip->path, // interactionProfile + uint32_t(ip->bindings.size()), // countSuggestedBindings + ip->bindings.ptr() // suggestedBindings }; - result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to suggest bindings for ", p_interaction_profile, "! [", get_error_string(result), "]"); + XrResult result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); + if (result == XR_ERROR_PATH_UNSUPPORTED) { + // this is fine, not all runtimes support all devices. + print_verbose("OpenXR Interaction profile " + ip->name + " is not supported on this runtime"); + } else if (XR_FAILED(result)) { + print_line("OpenXR: failed to suggest bindings for ", ip->name, "! [", get_error_string(result), "]"); // reporting is enough... } + /* For debugging: + print_verbose("Suggested bindings for " + ip->name); + for (int i = 0; i < ip->bindings.size(); i++) { + uint32_t strlen; + char path[XR_MAX_PATH_LENGTH]; + + String action_name = action_get_name(get_action_rid(ip->bindings[i].action)); + + XrResult result = xrPathToString(instance, ip->bindings[i].binding, XR_MAX_PATH_LENGTH, &strlen, path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to retrieve bindings for ", action_name, "! [", get_error_string(result), "]"); + } + print_verbose(" - " + action_name + " => " + String(path)); + } + */ + return true; } +void OpenXRAPI::interaction_profile_free(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); + + ip->bindings.clear(); + + interaction_profile_owner.free(p_interaction_profile); +} + bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -1955,12 +2220,12 @@ bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { return true; } -bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { +bool OpenXRAPI::get_action_bool(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, false); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); if (!running) { return false; @@ -1972,7 +2237,7 @@ bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateBoolean result_state; @@ -1987,12 +2252,12 @@ bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { return result_state.isActive && result_state.currentState; } -float OpenXRAPI::get_action_float(RID p_action, RID p_path) { +float OpenXRAPI::get_action_float(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, 0.0); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, 0.0); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, 0.0); if (!running) { return 0.0; @@ -2004,7 +2269,7 @@ float OpenXRAPI::get_action_float(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateFloat result_state; @@ -2019,12 +2284,12 @@ float OpenXRAPI::get_action_float(RID p_action, RID p_path) { return result_state.isActive ? result_state.currentState : 0.0; } -Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { +Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_tracker) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2()); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, Vector2()); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, Vector2()); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, Vector2()); if (!running) { return Vector2(); @@ -2036,7 +2301,7 @@ Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { XR_TYPE_ACTION_STATE_GET_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrActionStateVector2f result_state; @@ -2051,12 +2316,12 @@ Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2(); } -XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { +XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, XRPose::XR_TRACKING_CONFIDENCE_NONE); if (!running) { return XRPose::XR_TRACKING_CONFIDENCE_NONE; @@ -2064,10 +2329,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE); + // print_verbose("Checking " + action->name + " => " + tracker->name + " (" + itos(tracker->toplevel_path) + ")"); + uint64_t index = 0xFFFFFFFF; - uint64_t size = uint64_t(action->toplevel_paths.size()); + uint64_t size = uint64_t(action->trackers.size()); for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) { - if (action->toplevel_paths[i].toplevel_path == path->path) { + if (action->trackers[i].tracker_rid == p_tracker) { index = i; } } @@ -2077,14 +2344,19 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - if (action->toplevel_paths[index].space == XR_NULL_HANDLE) { + XrTime display_time = get_next_frame_time(); + if (display_time == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + if (action->trackers[index].space == XR_NULL_HANDLE) { // if this is a pose we need to define spaces XrActionSpaceCreateInfo action_space_info = { XR_TYPE_ACTION_SPACE_CREATE_INFO, // type nullptr, // next action->handle, // action - action->toplevel_paths[index].toplevel_path, // subactionPath + tracker->toplevel_path, // subactionPath { { 0.0, 0.0, 0.0, 1.0 }, // orientation { 0.0, 0.0, 0.0 } // position @@ -2098,11 +2370,9 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return XRPose::XR_TRACKING_CONFIDENCE_NONE; } - action->toplevel_paths.ptrw()[index].space = space; + action->trackers.ptrw()[index].space = space; } - XrTime display_time = get_next_frame_time(); - XrSpaceVelocity velocity = { XR_TYPE_SPACE_VELOCITY, // type nullptr, // next @@ -2121,7 +2391,7 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, } // pose }; - XrResult result = xrLocateSpace(action->toplevel_paths[index].space, play_space, display_time, &location); + XrResult result = xrLocateSpace(action->trackers[index].space, play_space, display_time, &location); if (XR_FAILED(result)) { print_line("OpenXR: failed to locate space! [", get_error_string(result), "]"); return XRPose::XR_TRACKING_CONFIDENCE_NONE; @@ -2133,12 +2403,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, return confidence; } -bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { +bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); Action *action = action_owner.get_or_null(p_action); ERR_FAIL_NULL_V(action, false); - Path *path = xr_path_owner.get_or_null(p_path); - ERR_FAIL_NULL_V(path, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); if (!running) { return false; @@ -2150,7 +2420,7 @@ bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency XR_TYPE_HAPTIC_ACTION_INFO, // type nullptr, // next action->handle, // action - path->path // subactionPath + tracker->toplevel_path // subactionPath }; XrHapticVibration vibration = { diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 33b503543a..e20826c849 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -55,12 +55,16 @@ // forward declarations, we don't want to include these fully class OpenXRVulkanExtension; +class OpenXRInterface; class OpenXRAPI { private: // our singleton static OpenXRAPI *singleton; + // linked XR interface + OpenXRInterface *xr_interface = nullptr; + // layers uint32_t num_layer_properties = 0; XrApiLayerProperties *layer_properties = nullptr; @@ -148,29 +152,45 @@ private: bool release_image(XrSwapchain p_swapchain); // action map - struct Path { - XrPath path; + struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. + String name; // Name for this tracker (i.e. "/user/hand/left") + XrPath toplevel_path; // OpenXR XrPath for this tracker + RID active_profile_rid; // RID of the active profile for this tracker }; - RID_Owner<Path, true> xr_path_owner; + RID_Owner<Tracker, true> tracker_owner; + RID get_tracker_rid(XrPath p_path); - struct ActionSet { - bool is_attached; - XrActionSet handle; + struct ActionSet { // Action sets define a set of actions that can be enabled together + String name; // Name for this action set (i.e. "godot_action_set") + bool is_attached; // If true our action set has been attached to the session and can no longer be modified + XrActionSet handle; // OpenXR handle for this action set }; RID_Owner<ActionSet, true> action_set_owner; - struct PathWithSpace { - XrPath toplevel_path; - XrSpace space; - bool was_location_valid; + struct ActionTracker { // Links and action to a tracker + RID tracker_rid; // RID of the tracker + XrSpace space; // Optional space for pose actions + bool was_location_valid; // If true the last position we obtained was valid }; - struct Action { - XrActionType action_type; - Vector<PathWithSpace> toplevel_paths; - XrAction handle; + struct Action { // Actions define the inputs and outputs in OpenXR + RID action_set_rid; // RID of the action set this action belongs to + String name; // Name for this action (i.e. "aim_pose") + XrActionType action_type; // Type of action (bool, float, etc.) + Vector<ActionTracker> trackers; // The trackers this action can be used with + XrAction handle; // OpenXR handle for this action }; RID_Owner<Action, true> action_owner; + RID get_action_rid(XrAction p_action); + + struct InteractionProfile { // Interaction profiles define suggested bindings between the physical inputs on controller types and our actions + String name; // Name of the interaction profile (i.e. "/interaction_profiles/valve/index_controller") + XrPath path; // OpenXR path for this profile + Vector<XrActionSuggestedBinding> bindings; // OpenXR action bindings + }; + RID_Owner<InteractionProfile, true> interaction_profile_owner; + RID get_interaction_profile_rid(XrPath p_path); + XrPath get_interaction_profile_path(RID p_interaction_profile); // state changes bool poll_events(); @@ -209,6 +229,7 @@ public: String get_error_string(XrResult result); String get_swapchain_format_name(int64_t p_swapchain_format) const; + void set_xr_interface(OpenXRInterface *p_xr_interface); void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); bool is_initialized(); @@ -233,26 +254,34 @@ public: // action map String get_default_action_map_resource_name(); - RID path_create(const String p_name); - void path_free(RID p_path); + + RID tracker_create(const String p_name); + String tracker_get_name(RID p_tracker); + void tracker_check_profile(RID p_tracker, XrSession p_session = XR_NULL_HANDLE); + void tracker_free(RID p_tracker); + RID action_set_create(const String p_name, const String p_localized_name, const int p_priority); + String action_set_get_name(RID p_action_set); bool action_set_attach(RID p_action_set); void action_set_free(RID p_action_set); - RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths); + + RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers); + String action_get_name(RID p_action); void action_free(RID p_action); - struct Binding { - RID action; - String path; - }; - bool suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings); + RID interaction_profile_create(const String p_name); + String interaction_profile_get_name(RID p_interaction_profile); + void interaction_profile_clear_bindings(RID p_interaction_profile); + bool interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path); + bool interaction_profile_suggest_bindings(RID p_interaction_profile); + void interaction_profile_free(RID p_interaction_profile); bool sync_action_sets(const Vector<RID> p_active_sets); - bool get_action_bool(RID p_action, RID p_path); - float get_action_float(RID p_action, RID p_path); - Vector2 get_action_vector2(RID p_action, RID p_path); - XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); - bool trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + bool get_action_bool(RID p_action, RID p_tracker); + float get_action_float(RID p_action, RID p_tracker); + Vector2 get_action_vector2(RID p_action, RID p_tracker); + XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns); OpenXRAPI(); ~OpenXRAPI(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 394f634687..b89171543f 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -35,7 +35,12 @@ #include "servers/rendering/rendering_server_globals.h" void OpenXRInterface::_bind_methods() { - // todo + // lifecycle signals + ADD_SIGNAL(MethodInfo("session_begun")); + ADD_SIGNAL(MethodInfo("session_stopping")); + ADD_SIGNAL(MethodInfo("session_focussed")); + ADD_SIGNAL(MethodInfo("session_visible")); + ADD_SIGNAL(MethodInfo("pose_recentered")); } StringName OpenXRInterface::get_name() const { @@ -46,6 +51,18 @@ uint32_t OpenXRInterface::get_capabilities() const { return XRInterface::XR_VR + XRInterface::XR_STEREO; }; +PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { + // These are hardcoded in OpenXR, note that they will only be available if added to our action map + + PackedStringArray arr = { + "left_hand", // /user/hand/left is mapped to our defaults + "right_hand", // /user/hand/right is mapped to our defaults + "/user/treadmill" + }; + + return arr; +} + XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { return tracking_state; } @@ -64,8 +81,9 @@ void OpenXRInterface::_load_action_map() { // This allow us to process the relevant actions each frame. // just in case clean up - free_action_sets(); free_trackers(); + free_interaction_profiles(); + free_action_sets(); Ref<OpenXRActionMap> action_map; if (Engine::get_singleton()->is_editor_hint()) { @@ -95,7 +113,7 @@ void OpenXRInterface::_load_action_map() { // process our action map if (action_map.is_valid()) { - Map<Ref<OpenXRAction>, RID> action_rids; + Map<Ref<OpenXRAction>, Action *> xr_actions; Array action_sets = action_map->get_action_sets(); for (int i = 0; i < action_sets.size(); i++) { @@ -112,18 +130,16 @@ void OpenXRInterface::_load_action_map() { Ref<OpenXRAction> xr_action = actions[j]; PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); - Vector<RID> toplevel_rids; Vector<Tracker *> trackers; for (int k = 0; k < toplevel_paths.size(); k++) { - Tracker *tracker = get_tracker(toplevel_paths[k]); + Tracker *tracker = find_tracker(toplevel_paths[k], true); if (tracker) { - toplevel_rids.push_back(tracker->path_rid); trackers.push_back(tracker); } } - Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), toplevel_rids); + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers); if (action) { // we link our actions back to our trackers so we know which actions to check when we're processing our trackers for (int t = 0; t < trackers.size(); t++) { @@ -131,7 +147,7 @@ void OpenXRInterface::_load_action_map() { } // add this to our map for creating our interaction profiles - action_rids[xr_action] = action->action_rid; + xr_actions[xr_action] = action; } } } @@ -139,30 +155,38 @@ void OpenXRInterface::_load_action_map() { // now do our suggestions Array interaction_profiles = action_map->get_interaction_profiles(); for (int i = 0; i < interaction_profiles.size(); i++) { - Vector<OpenXRAPI::Binding> bindings; Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i]; + // Note, we can only have one entry per interaction profile so if it already exists we clear it out + RID ip = openxr_api->interaction_profile_create(xr_interaction_profile->get_interaction_profile_path()); + openxr_api->interaction_profile_clear_bindings(ip); + Array xr_bindings = xr_interaction_profile->get_bindings(); for (int j = 0; j < xr_bindings.size(); j++) { Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; Ref<OpenXRAction> xr_action = xr_binding->get_action(); - OpenXRAPI::Binding binding; - if (action_rids.has(xr_action)) { - binding.action = action_rids[xr_action]; + Action *action = nullptr; + if (xr_actions.has(xr_action)) { + action = xr_actions[xr_action]; } else { print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); continue; } - PackedStringArray xr_paths = xr_binding->get_paths(); - for (int k = 0; k < xr_paths.size(); k++) { - binding.path = xr_paths[k]; - bindings.push_back(binding); + PackedStringArray paths = xr_binding->get_paths(); + for (int k = 0; k < paths.size(); k++) { + openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]); } } - openxr_api->suggest_bindings(xr_interaction_profile->get_interaction_profile_path(), bindings); + // Now submit our suggestions + openxr_api->interaction_profile_suggest_bindings(ip); + + // And record it in our array so we can clean it up later on + if (interaction_profiles.has(ip)) { + interaction_profiles.push_back(ip); + } } } } @@ -193,15 +217,16 @@ void OpenXRInterface::free_action_sets() { for (int i = 0; i < action_sets.size(); i++) { ActionSet *action_set = action_sets[i]; - openxr_api->path_free(action_set->action_set_rid); free_actions(action_set); + openxr_api->action_set_free(action_set->action_set_rid); + memfree(action_set); } action_sets.clear(); } -OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths) { +OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers) { ERR_FAIL_NULL_V(openxr_api, nullptr); for (int i = 0; i < p_action_set->actions.size(); i++) { @@ -211,10 +236,31 @@ OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, } } + Vector<RID> tracker_rids; + for (int i = 0; i < p_trackers.size(); i++) { + tracker_rids.push_back(p_trackers[i]->tracker_rid); + } + Action *action = memnew(Action); - action->action_name = p_action_name; + if (p_action_type == OpenXRAction::OPENXR_ACTION_POSE) { + // We can't have dual action names in OpenXR hence we added _pose, + // but default, aim and grip and default pose action names in Godot so rename them on the tracker. + // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side + if (p_action_name == "default_pose") { + action->action_name = "default"; + } else if (p_action_name == "aim_pose") { + action->action_name = "aim"; + } else if (p_action_name == "grip_pose") { + action->action_name = "grip"; + } else { + action->action_name = p_action_name; + } + } else { + action->action_name = p_action_name; + } + action->action_type = p_action_type; - action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, p_toplevel_paths); + action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); p_action_set->actions.push_back(action); return action; @@ -248,7 +294,7 @@ void OpenXRInterface::free_actions(ActionSet *p_action_set) { p_action_set->actions.clear(); } -OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name) { +OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_name, bool p_create) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, nullptr); ERR_FAIL_NULL_V(openxr_api, nullptr); @@ -256,52 +302,72 @@ OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name Tracker *tracker = nullptr; for (int i = 0; i < trackers.size(); i++) { tracker = trackers[i]; - if (tracker->path_name == p_path_name) { + if (tracker->tracker_name == p_tracker_name) { return tracker; } } + if (!p_create) { + return nullptr; + } + + // Create our RID + RID tracker_rid = openxr_api->tracker_create(p_tracker_name); + ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); + // create our positional tracker Ref<XRPositionalTracker> positional_tracker; positional_tracker.instantiate(); // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. - if (p_path_name == "/user/hand/left") { + if (p_tracker_name == "/user/hand/left") { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); positional_tracker->set_tracker_name("left_hand"); positional_tracker->set_tracker_desc("Left hand controller"); positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); - } else if (p_path_name == "/user/hand/right") { + } else if (p_tracker_name == "/user/hand/right") { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); positional_tracker->set_tracker_name("right_hand"); positional_tracker->set_tracker_desc("Right hand controller"); positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); } else { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name(p_path_name); - positional_tracker->set_tracker_desc(p_path_name); + positional_tracker->set_tracker_name(p_tracker_name); + positional_tracker->set_tracker_desc(p_tracker_name); } + positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); xr_server->add_tracker(positional_tracker); // create a new entry tracker = memnew(Tracker); - tracker->path_name = p_path_name; - tracker->path_rid = openxr_api->path_create(p_path_name); + tracker->tracker_name = p_tracker_name; + tracker->tracker_rid = tracker_rid; tracker->positional_tracker = positional_tracker; + tracker->interaction_profile = RID(); trackers.push_back(tracker); return tracker; } -OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_positional_tracker_name) { - for (int i = 0; i < trackers.size(); i++) { - Tracker *tracker = trackers[i]; - if (tracker->positional_tracker.is_valid() && tracker->positional_tracker->get_tracker_name() == p_positional_tracker_name) { - return tracker; +void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_profile) { + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size() && tracker == nullptr; i++) { + if (trackers[i]->tracker_rid == p_tracker) { + tracker = trackers[i]; } } + ERR_FAIL_NULL(tracker); - return nullptr; + tracker->interaction_profile = p_interaction_profile; + + if (p_interaction_profile.is_null()) { + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); + tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + } else { + String name = openxr_api->interaction_profile_get_name(p_interaction_profile); + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); + tracker->positional_tracker->set_tracker_profile(name); + } } void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { @@ -314,40 +380,43 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); - // handle all the actions + // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction + // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these + // and rebind them or even offer bindings to controllers that are not known to us. + + // We don't really have a consistant way to detect whether a controller is active however as long as it is + // unbound it seems to be unavailable, so far unknown controller seem to mimic one of the profiles we've + // supplied. + if (p_tracker->interaction_profile.is_null()) { + return; + } + + // We check all actions that are related to our tracker. for (int i = 0; i < p_tracker->actions.size(); i++) { Action *action = p_tracker->actions[i]; switch (action->action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: { - bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->path_rid); + bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); } break; case OpenXRAction::OPENXR_ACTION_FLOAT: { - real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->path_rid); + real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_VECTOR2: { - Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->path_rid); + Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_POSE: { Transform3D transform; Vector3 linear, angular; - XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->path_rid, transform, linear, angular); + + XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { - String name; - // We can't have dual action names in OpenXR hence we added _pose, but default, aim and grip and default pose action names in Godot so rename them on the tracker. - // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side - if (action->action_name == "default_pose") { - name = "default"; - } else if (action->action_name == "aim_pose") { - name = "aim"; - } else if (action->action_name == "grip_pose") { - name = "grip"; - } else { - name = action->action_name; - } - p_tracker->positional_tracker->set_pose(name, transform, linear, angular, confidence); + p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); + } else { + p_tracker->positional_tracker->invalidate_pose(action->action_name); } } break; default: { @@ -368,7 +437,7 @@ void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const St XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds - openxr_api->trigger_haptic_pulse(action->action_rid, tracker->path_rid, p_frequency, p_amplitude, duration); + openxr_api->trigger_haptic_pulse(action->action_rid, tracker->tracker_rid, p_frequency, p_amplitude, duration); } void OpenXRInterface::free_trackers() { @@ -379,7 +448,7 @@ void OpenXRInterface::free_trackers() { for (int i = 0; i < trackers.size(); i++) { Tracker *tracker = trackers[i]; - openxr_api->path_free(tracker->path_rid); + openxr_api->tracker_free(tracker->tracker_rid); xr_server->remove_tracker(tracker->positional_tracker); tracker->positional_tracker.unref(); @@ -388,6 +457,15 @@ void OpenXRInterface::free_trackers() { trackers.clear(); } +void OpenXRInterface::free_interaction_profiles() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < interaction_profiles.size(); i++) { + openxr_api->interaction_profile_free(interaction_profiles[i]); + } + interaction_profiles.clear(); +} + bool OpenXRInterface::initialise_on_startup() const { if (openxr_api == nullptr) { return false; @@ -447,14 +525,14 @@ void OpenXRInterface::uninitialize() { // end the session if we need to? // cleanup stuff - free_action_sets(); free_trackers(); + free_interaction_profiles(); + free_action_sets(); XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { if (head.is_valid()) { xr_server->remove_tracker(head); - head.unref(); } } @@ -649,8 +727,31 @@ void OpenXRInterface::end_frame() { } } +void OpenXRInterface::on_state_ready() { + emit_signal(SNAME("session_begun")); +} + +void OpenXRInterface::on_state_visible() { + emit_signal(SNAME("session_visible")); +} + +void OpenXRInterface::on_state_focused() { + emit_signal(SNAME("session_focussed")); +} + +void OpenXRInterface::on_state_stopping() { + emit_signal(SNAME("session_stopping")); +} + +void OpenXRInterface::on_pose_recentered() { + emit_signal(SNAME("pose_recentered")); +} + OpenXRInterface::OpenXRInterface() { openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api) { + openxr_api->set_xr_interface(this); + } // while we don't have head tracking, don't put the headset on the floor... _set_default_pos(head_transform, 1.0, 0); @@ -659,5 +760,11 @@ OpenXRInterface::OpenXRInterface() { } OpenXRInterface::~OpenXRInterface() { - openxr_api = nullptr; + // should already have been called but just in case... + uninitialize(); + + if (openxr_api) { + openxr_api->set_xr_interface(nullptr); + openxr_api = nullptr; + } } diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index ede7d481d2..421838e445 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -37,6 +37,9 @@ #include "action_map/openxr_action_map.h" #include "openxr_api.h" +// declare some default strings +#define INTERACTION_PROFILE_NONE "/interaction_profiles/none" + class OpenXRInterface : public XRInterface { GDCLASS(OpenXRInterface, XRInterface); @@ -54,40 +57,43 @@ private: void _load_action_map(); - struct Action { - String action_name; - OpenXRAction::ActionType action_type; - RID action_rid; + struct Action { // An action we've registered with OpenXR + String action_name; // Name of our action as presented to Godot (can be altered from the action map) + OpenXRAction::ActionType action_type; // The action type of this action + RID action_rid; // RID of the action registered with our OpenXR API }; - struct ActionSet { - String action_set_name; - bool is_active; - RID action_set_rid; - Vector<Action *> actions; + struct ActionSet { // An action set we've registered with OpenXR + String action_set_name; // Name of our action set + bool is_active; // If true this action set is active and we will sync it + Vector<Action *> actions; // List of actions in this action set + RID action_set_rid; // RID of the action registered with our OpenXR API }; - struct Tracker { - String path_name; - RID path_rid; - Ref<XRPositionalTracker> positional_tracker; - Vector<Action *> actions; + struct Tracker { // A tracker we've registered with OpenXR + String tracker_name; // Name of our tracker (can be altered from the action map) + Vector<Action *> actions; // Actions related to this tracker + Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state + RID tracker_rid; // RID of the tracker registered with our OpenXR API + RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null) }; Vector<ActionSet *> action_sets; + Vector<RID> interaction_profiles; Vector<Tracker *> trackers; ActionSet *create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority); void free_action_sets(); - Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths); + Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers); Action *find_action(const String &p_action_name); void free_actions(ActionSet *p_action_set); - Tracker *get_tracker(const String &p_path_name); - Tracker *find_tracker(const String &p_positional_tracker_name); + Tracker *find_tracker(const String &p_tracker_name, bool p_create = false); void link_action_to_tracker(Tracker *p_tracker, Action *p_action); void handle_tracker(Tracker *p_tracker); void free_trackers(); + void free_interaction_profiles(); + void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); protected: @@ -97,6 +103,7 @@ public: virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; + virtual PackedStringArray get_suggested_tracker_names() const override; virtual TrackingStatus get_tracking_status() const override; bool initialise_on_startup() const; @@ -122,6 +129,13 @@ public: virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void end_frame() override; + void on_state_ready(); + void on_state_visible(); + void on_state_focused(); + void on_state_stopping(); + void on_pose_recentered(); + void tracker_profile_changed(RID p_tracker, RID p_interaction_profile); + OpenXRInterface(); ~OpenXRInterface(); }; diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp index e515336daa..230b10c5f1 100644 --- a/modules/openxr/openxr_util.cpp +++ b/modules/openxr/openxr_util.cpp @@ -278,6 +278,20 @@ String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) { } } +String OpenXRUtil::get_action_type_name(XrActionType p_action_type) { + switch (p_action_type) { + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_BOOLEAN_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_FLOAT_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VECTOR2F_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_POSE_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VIBRATION_OUTPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_MAX_ENUM) + default: { + return String("Action type ") + String::num_int64(int64_t(p_action_type)); + } break; + } +} + String OpenXRUtil::make_xr_version_string(XrVersion p_version) { String version; diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h index 1261268376..4371b74d2f 100644 --- a/modules/openxr/openxr_util.h +++ b/modules/openxr/openxr_util.h @@ -40,6 +40,7 @@ public: static String get_reference_space_name(XrReferenceSpaceType p_reference_space); static String get_structure_type_name(XrStructureType p_structure_type); static String get_session_state_name(XrSessionState p_session_state); + static String get_action_type_name(XrActionType p_action_type); static String make_xr_version_string(XrVersion p_version); }; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 86ff368619..7a74c8c089 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -75,9 +75,18 @@ void register_openxr_types() { void unregister_openxr_types() { if (openxr_interface.is_valid()) { + // uninitialise just in case + if (openxr_interface->is_initialized()) { + openxr_interface->uninitialize(); + } + // unregister our interface from the XR server - if (XRServer::get_singleton()) { - XRServer::get_singleton()->remove_interface(openxr_interface); + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (xr_server->get_primary_interface() == openxr_interface) { + xr_server->set_primary_interface(Ref<XRInterface>()); + } + xr_server->remove_interface(openxr_interface); } // and release diff --git a/servers/xr/xr_positional_tracker.cpp b/servers/xr/xr_positional_tracker.cpp index 671eb7a111..4857167a8e 100644 --- a/servers/xr/xr_positional_tracker.cpp +++ b/servers/xr/xr_positional_tracker.cpp @@ -49,6 +49,10 @@ void XRPositionalTracker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRPositionalTracker::set_tracker_desc); ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc"); + ClassDB::bind_method(D_METHOD("get_tracker_profile"), &XRPositionalTracker::get_tracker_profile); + ClassDB::bind_method(D_METHOD("set_tracker_profile", "profile"), &XRPositionalTracker::set_tracker_profile); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "profile"), "set_tracker_profile", "get_tracker_profile"); + ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRPositionalTracker::get_tracker_hand); ClassDB::bind_method(D_METHOD("set_tracker_hand", "hand"), &XRPositionalTracker::set_tracker_hand); ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Unknown,Left,Right"), "set_tracker_hand", "get_tracker_hand"); @@ -65,6 +69,7 @@ void XRPositionalTracker::_bind_methods() { ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value"))); ADD_SIGNAL(MethodInfo("input_axis_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "vector"))); + ADD_SIGNAL(MethodInfo("profile_changed", PropertyInfo(Variant::STRING, "role"))); }; void XRPositionalTracker::set_tracker_type(XRServer::TrackerType p_type) { @@ -95,6 +100,18 @@ String XRPositionalTracker::get_tracker_desc() const { return description; } +void XRPositionalTracker::set_tracker_profile(const String &p_profile) { + if (profile != p_profile) { + profile = p_profile; + + emit_signal("profile_changed", profile); + } +} + +String XRPositionalTracker::get_tracker_profile() const { + return profile; +} + XRPositionalTracker::TrackerHand XRPositionalTracker::get_tracker_hand() const { return hand; }; diff --git a/servers/xr/xr_positional_tracker.h b/servers/xr/xr_positional_tracker.h index ccb30bbbe6..cd06d4a087 100644 --- a/servers/xr/xr_positional_tracker.h +++ b/servers/xr/xr_positional_tracker.h @@ -56,7 +56,8 @@ public: private: XRServer::TrackerType type; // type of tracker StringName name; // (unique) name of the tracker - String description; // description of the tracker, this is interface dependent, for OpenXR this will be the interaction profile bound for to the tracker + String description; // description of the tracker + String profile; // this is interface dependent, for OpenXR this will be the interaction profile bound for to the tracker TrackerHand hand; // if known, the hand this tracker is held in Map<StringName, Ref<XRPose>> poses; @@ -72,6 +73,8 @@ public: StringName get_tracker_name() const; void set_tracker_desc(const String &p_desc); String get_tracker_desc() const; + void set_tracker_profile(const String &p_profile); + String get_tracker_profile() const; XRPositionalTracker::TrackerHand get_tracker_hand() const; void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand); |