Adding signals and events to OpenXR interface

Improving interaction profile logic
This commit is contained in:
Bastiaan Olij 2022-03-05 21:09:21 +11:00
parent 450ebaab9f
commit d11cb5fe98
11 changed files with 700 additions and 200 deletions

View file

@ -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">

View file

@ -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>

View file

@ -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) {
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];
}
}
return RID();
}
RID OpenXRAPI::tracker_create(const String p_name) {
ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID());
// 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!
Tracker new_tracker;
new_tracker.name = p_name;
new_tracker.toplevel_path = XR_NULL_PATH;
new_tracker.active_profile_rid = RID();
Path new_path;
print_line("Parsing path ", p_name);
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];
}
}
XrPath interaction_profile;
Vector<XrActionSuggestedBinding> bindings;
return RID();
}
XrResult result = xrStringToPath(instance, p_interaction_profile.utf8().get_data(), &interaction_profile);
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);
return ip->path;
}
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), "]");
print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]");
return RID();
}
RID existing_ip = get_interaction_profile_rid(new_interaction_profile.path);
if (existing_ip.is_valid()) {
return existing_ip;
}
new_interaction_profile.name = p_name;
return interaction_profile_owner.make_rid(new_interaction_profile);
}
String OpenXRAPI::interaction_profile_get_name(RID p_interaction_profile) {
if (p_interaction_profile.is_null()) {
return String("None");
}
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);
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;
}
for (int i = 0; i < p_bindings.size(); i++) {
XrActionSuggestedBinding binding;
ip->bindings.push_back(binding);
Action *action = action_owner.get_or_null(p_bindings[i].action);
if (action == nullptr || action->handle == XR_NULL_HANDLE) {
// just skip it
continue;
}
return true;
}
binding.action = action->handle;
bool OpenXRAPI::interaction_profile_suggest_bindings(RID p_interaction_profile) {
ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
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;
}
bindings.push_back(binding);
}
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 = {

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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();
};

View file

@ -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;

View file

@ -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);
};

View file

@ -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

View file

@ -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;
};

View file

@ -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);