Browse Source

Adding signals and events to OpenXR interface
Improving interaction profile logic

Bastiaan Olij 3 years ago
parent
commit
d11cb5fe98

+ 9 - 0
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="&quot;&quot;">
+			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">

+ 27 - 0
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>

+ 363 - 93
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(&current);
+	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(&current);
+	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(&current);
+	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 = {

+ 56 - 27
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();

+ 165 - 58
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;
+	}
 }

+ 31 - 17
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();
 };

+ 14 - 0
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;
 

+ 1 - 0
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);
 };
 

+ 11 - 2
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

+ 17 - 0
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;
 };

+ 4 - 1
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);