summaryrefslogtreecommitdiffstats
path: root/modules/openxr
diff options
context:
space:
mode:
Diffstat (limited to 'modules/openxr')
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp164
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml8
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml17
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_hand_interaction_extension.cpp97
-rw-r--r--modules/openxr/extensions/openxr_hand_interaction_extension.h72
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp2
-rw-r--r--modules/openxr/openxr_api.cpp448
-rw-r--r--modules/openxr/openxr_api.h90
-rw-r--r--modules/openxr/openxr_api_extension.cpp10
-rw-r--r--modules/openxr/openxr_api_extension.h1
-rw-r--r--modules/openxr/openxr_interface.cpp27
-rw-r--r--modules/openxr/openxr_interface.h26
-rw-r--r--modules/openxr/register_types.cpp2
14 files changed, 703 insertions, 265 deletions
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index bbcb63a7e6..ba0e4f6cdd 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -167,11 +167,11 @@ void OpenXRActionMap::create_default_action_sets() {
// we still want it to be part of our action map as we may deploy the same game to platforms that do and don't support it.
// - the same applies for interaction profiles that are only supported if the relevant extension is supported.
- // Create our Godot action set
+ // Create our Godot action set.
Ref<OpenXRActionSet> action_set = OpenXRActionSet::new_action_set("godot", "Godot action set");
add_action_set(action_set);
- // Create our actions
+ // Create our actions.
Ref<OpenXRAction> trigger = action_set->add_new_action("trigger", "Trigger", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> trigger_click = action_set->add_new_action("trigger_click", "Trigger click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> trigger_touch = action_set->add_new_action("trigger_touch", "Trigger touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right");
@@ -193,7 +193,7 @@ void OpenXRActionMap::create_default_action_sets() {
Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE,
"/user/hand/left,"
"/user/hand/right,"
- // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one
+ // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one.
"/user/vive_tracker_htcx/role/left_foot,"
"/user/vive_tracker_htcx/role/right_foot,"
"/user/vive_tracker_htcx/role/left_shoulder,"
@@ -213,7 +213,7 @@ void OpenXRActionMap::create_default_action_sets() {
Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC,
"/user/hand/left,"
"/user/hand/right,"
- // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one
+ // "/user/vive_tracker_htcx/role/handheld_object," <-- getting errors on this one.
"/user/vive_tracker_htcx/role/left_foot,"
"/user/vive_tracker_htcx/role/right_foot,"
"/user/vive_tracker_htcx/role/left_shoulder,"
@@ -227,7 +227,7 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/camera,"
"/user/vive_tracker_htcx/role/keyboard");
- // Create our interaction profiles
+ // Create our interaction profiles.
Ref<OpenXRInteractionProfile> profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/khr/simple_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
@@ -235,11 +235,11 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click");
profile->add_new_binding(select_button, "/user/hand/left/input/select/click,/user/hand/right/input/select/click");
- // generic has no support for triggers, grip, A/B buttons, nor joystick/trackpad inputs
+ // generic has no support for triggers, grip, A/B buttons, nor joystick/trackpad inputs.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Vive controller profile
+ // Create our Vive controller profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
@@ -247,64 +247,64 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click");
profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click");
- // wmr controller has no a/b/x/y buttons
+ // wmr controller has no a/b/x/y buttons.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click");
- profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float
+ profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float.
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
- // primary on our vive controller is our trackpad
+ // primary on our vive controller is our trackpad.
profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad");
profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
- // vive controllers have no secondary input
+ // vive controllers have no secondary input.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our WMR controller profile
+ // Create our WMR controller profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- // wmr controllers have no select button we can use
+ // wmr controllers have no select button we can use.
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click");
- // wmr controller has no a/b/x/y buttons
+ // wmr controller has no a/b/x/y buttons.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
- profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will convert float to bool
- profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float
+ profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will convert float to bool.
+ profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float.
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
- // primary on our wmr controller is our thumbstick, no touch
+ // primary on our wmr controller is our thumbstick, no touch.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
- // secondary on our wmr controller is our trackpad
+ // secondary on our wmr controller is our trackpad.
profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad");
profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click");
profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Meta touch controller profile
+ // Create our Meta touch controller profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/oculus/touch_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- // touch controllers have no select button we can use
- profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/system/click"); // right hand system click may not be available
- profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
+ // touch controllers have no select button we can use.
+ profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/system/click"); // right hand system click may not be available.
+ profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand.
profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch");
- profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
+ profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand.
profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch");
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
- profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean
+ profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean.
profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch");
- profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean
+ profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean.
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value");
- // primary on our touch controller is our thumbstick
+ // primary on our touch controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch");
- // touch controller has no secondary input
+ // touch controller has no secondary input.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
@@ -314,73 +314,73 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available
+ profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); // system click may not be available.
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click");
- profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
+ profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand.
profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch");
- profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
+ profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand.
profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch");
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
- profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean
+ profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean.
profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch");
- profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean
+ profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean.
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value");
- // primary on our pico controller is our thumbstick
+ // primary on our pico controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch");
- // pico controller has no secondary input
+ // pico controller has no secondary input.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Valve index controller profile
+ // Create our Valve index controller profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/valve/index_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- // index controllers have no select button we can use
+ // index controllers have no select button we can use.
profile->add_new_binding(menu_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click");
- profile->add_new_binding(ax_button, "/user/hand/left/input/a/click,/user/hand/right/input/a/click"); // a on both controllers
+ profile->add_new_binding(ax_button, "/user/hand/left/input/a/click,/user/hand/right/input/a/click"); // a on both controllers.
profile->add_new_binding(ax_touch, "/user/hand/left/input/a/touch,/user/hand/right/input/a/touch");
- profile->add_new_binding(by_button, "/user/hand/left/input/b/click,/user/hand/right/input/b/click"); // b on both controllers
+ profile->add_new_binding(by_button, "/user/hand/left/input/b/click,/user/hand/right/input/b/click"); // b on both controllers.
profile->add_new_binding(by_touch, "/user/hand/left/input/b/touch,/user/hand/right/input/b/touch");
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click");
profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch");
profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value");
- profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion
- profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index
- // primary on our index controller is our thumbstick
+ profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion.
+ profile->add_new_binding(grip_force, "/user/hand/left/input/squeeze/force,/user/hand/right/input/squeeze/force"); // grip force seems to be unique to the Valve Index.
+ // primary on our index controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch");
- // secondary on our index controller is our trackpad
+ // secondary on our index controller is our trackpad.
profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad");
profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/force,/user/hand/right/input/trackpad/force"); // not sure if this will work but doesn't seem to support click...
profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our HP MR controller profile
+ // Create our HP MR controller profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- // hpmr controllers have no select button we can use
+ // hpmr controllers have no select button we can use.
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click");
- // hpmr controllers only register click, not touch, on our a/b/x/y buttons
- profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
- profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
+ // hpmr controllers only register click, not touch, on our a/b/x/y buttons.
+ profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand.
+ profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value");
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value");
- // primary on our hpmr controller is our thumbstick
+ // primary on our hpmr controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
- // No secondary on our hpmr controller
+ // No secondary on our hpmr controller.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
@@ -391,72 +391,72 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
- // Odyssey controllers have no select button we can use
+ // Odyssey controllers have no select button we can use.
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click");
- // Odyssey controller has no a/b/x/y buttons
+ // Odyssey controller has no a/b/x/y buttons.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
- // primary on our Odyssey controller is our thumbstick, no touch
+ // primary on our Odyssey controller is our thumbstick, no touch.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
- // secondary on our Odyssey controller is our trackpad
+ // secondary on our Odyssey controller is our trackpad.
profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad");
profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click");
profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Vive Cosmos controller
+ // Create our Vive Cosmos controller.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_cosmos_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click");
- profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select
- profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
- profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
+ profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select.
+ profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand.
+ profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click");
profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
- // primary on our Cosmos controller is our thumbstick
+ // primary on our Cosmos controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch");
- // No secondary on our cosmos controller
+ // No secondary on our cosmos controller.
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Vive Focus 3 controller
+ // Create our Vive Focus 3 controller.
// Note, Vive Focus 3 currently is not yet supported as a stand alone device
- // however HTC currently has a beta OpenXR runtime in testing we may support in the near future
+ // however HTC currently has a beta OpenXR runtime in testing we may support in the near future.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_focus3_controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click");
- profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select
- profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand
- profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand
+ profile->add_new_binding(select_button, "/user/hand/right/input/system/click"); // we'll map system to select.
+ profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand.
+ profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand.
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click");
profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch");
profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click");
- // primary on our Focus 3 controller is our thumbstick
+ // primary on our Focus 3 controller is our thumbstick.
profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick");
profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch");
- // We only have a thumb rest
+ // We only have a thumb rest.
profile->add_new_binding(secondary_touch, "/user/hand/left/input/thumbrest/touch,/user/hand/right/input/thumbrest/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our Huawei controller
+ // Create our Huawei controller.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/huawei/controller");
profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
@@ -465,17 +465,17 @@ void OpenXRActionMap::create_default_action_sets() {
profile->add_new_binding(menu_button, "/user/hand/left/input/home/click,/user/hand/right/input/home/click");
profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value");
profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click");
- // primary on our Huawei controller is our trackpad
+ // primary on our Huawei controller is our trackpad.
profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad");
profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click");
profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch");
profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic");
add_interaction_profile(profile);
- // Create our HTC Vive tracker profile
+ // Create our HTC Vive tracker profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_tracker_htcx");
profile->add_new_binding(default_pose,
- // "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose," <-- getting errors on this one
+ // "/user/vive_tracker_htcx/role/handheld_object/input/grip/pose," <-- getting errors on this one.
"/user/vive_tracker_htcx/role/left_foot/input/grip/pose,"
"/user/vive_tracker_htcx/role/right_foot/input/grip/pose,"
"/user/vive_tracker_htcx/role/left_shoulder/input/grip/pose,"
@@ -489,7 +489,7 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/camera/input/grip/pose,"
"/user/vive_tracker_htcx/role/keyboard/input/grip/pose");
profile->add_new_binding(haptic,
- // "/user/vive_tracker_htcx/role/handheld_object/output/haptic," <-- getting errors on this one
+ // "/user/vive_tracker_htcx/role/handheld_object/output/haptic," <-- getting errors on this one.
"/user/vive_tracker_htcx/role/left_foot/output/haptic,"
"/user/vive_tracker_htcx/role/right_foot/output/haptic,"
"/user/vive_tracker_htcx/role/left_shoulder/output/haptic,"
@@ -504,10 +504,30 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/keyboard/output/haptic");
add_interaction_profile(profile);
- // Create our eye gaze interaction profile
+ // Create our eye gaze interaction profile.
profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction");
profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose");
add_interaction_profile(profile);
+
+ // Create our hand interaction profile.
+ profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/hand_interaction_ext");
+ profile->add_new_binding(default_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
+ profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose");
+ profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose");
+ profile->add_new_binding(palm_pose, "/user/hand/left/input/palm_ext/pose,/user/hand/right/input/palm_ext/pose");
+
+ // Use pinch as primary.
+ profile->add_new_binding(primary, "/user/hand/left/input/pinch_ext/value,/user/hand/right/input/pinch_ext/value");
+ profile->add_new_binding(primary_click, "/user/hand/left/input/pinch_ext/ready_ext,/user/hand/right/input/pinch_ext/ready_ext");
+
+ // Use activation as secondary.
+ profile->add_new_binding(secondary, "/user/hand/left/input/aim_activate_ext/value,/user/hand/right/input/aim_activate_ext/value");
+ profile->add_new_binding(secondary_click, "/user/hand/left/input/aim_activate_ext/ready_ext,/user/hand/right/input/aim_activate_ext/ready_ext");
+
+ // We link grasp to our grip.
+ profile->add_new_binding(grip, "/user/hand/left/input/grasp_ext/value,/user/hand/right/input/grasp_ext/value");
+ profile->add_new_binding(grip_click, "/user/hand/left/input/grasp_ext/ready_ext,/user/hand/right/input/grasp_ext/ready_ext");
+ add_interaction_profile(profile);
}
void OpenXRActionMap::create_editor_action_sets() {
diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
index f737f3b642..4419d24dd3 100644
--- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
@@ -54,7 +54,7 @@
<method name="get_next_frame_time">
<return type="int" />
<description>
- Returns the timing for the next frame.
+ Returns the predicted display timing for the next frame.
</description>
</method>
<method name="get_play_space">
@@ -63,6 +63,12 @@
Returns the play space, which is an [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSpace.html]XrSpace[/url] cast to an integer.
</description>
</method>
+ <method name="get_predicted_display_time">
+ <return type="int" />
+ <description>
+ Returns the predicted display timing for the current frame.
+ </description>
+ </method>
<method name="get_session">
<return type="int" />
<description>
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index 05dff7d6ae..86ba1416c8 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -106,6 +106,13 @@
[b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop.
</description>
</method>
+ <method name="is_hand_interaction_supported" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if OpenXR's hand interaction profile is supported and enabled.
+ [b]Note:[/b] This only returns a valid value after OpenXR has been initialized.
+ </description>
+ </method>
<method name="is_hand_tracking_supported">
<return type="bool" />
<description>
@@ -147,6 +154,11 @@
</member>
</members>
<signals>
+ <signal name="instance_exiting">
+ <description>
+ Informs our OpenXR instance is exiting.
+ </description>
+ </signal>
<signal name="pose_recentered">
<description>
Informs the user queued a recenter of the player position.
@@ -169,6 +181,11 @@
Informs our OpenXR session now has focus.
</description>
</signal>
+ <signal name="session_loss_pending">
+ <description>
+ Informs our OpenXR session is in the process of being lost.
+ </description>
+ </signal>
<signal name="session_stopping">
<description>
Informs our OpenXR session is stopping.
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index 1fba8e5f8b..51f4a03d52 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -274,7 +274,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
if (swapchain_size == viewport_size && !p_static_image && !static_image) {
// We're all good! Just acquire it.
// We can ignore should_render here, return will be false.
- XrBool32 should_render = true;
+ bool should_render = true;
return swapchain_info.acquire(should_render);
}
@@ -296,7 +296,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
// Acquire our image so we can start rendering into it,
// we can ignore should_render here, ret will be false.
- XrBool32 should_render = true;
+ bool should_render = true;
bool ret = swapchain_info.acquire(should_render);
swapchain_size = viewport_size;
diff --git a/modules/openxr/extensions/openxr_hand_interaction_extension.cpp b/modules/openxr/extensions/openxr_hand_interaction_extension.cpp
new file mode 100644
index 0000000000..65de4b23c4
--- /dev/null
+++ b/modules/openxr/extensions/openxr_hand_interaction_extension.cpp
@@ -0,0 +1,97 @@
+/**************************************************************************/
+/* openxr_hand_interaction_extension.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_hand_interaction_extension.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+#include "core/config/project_settings.h"
+
+OpenXRHandInteractionExtension *OpenXRHandInteractionExtension::singleton = nullptr;
+
+OpenXRHandInteractionExtension *OpenXRHandInteractionExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRHandInteractionExtension::OpenXRHandInteractionExtension() {
+ singleton = this;
+}
+
+OpenXRHandInteractionExtension::~OpenXRHandInteractionExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRHandInteractionExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ // Only enable this extension when requested.
+ // We still register our meta data or the action map editor will fail.
+ if (GLOBAL_GET("xr/openxr/extensions/hand_interaction_profile")) {
+ request_extensions[XR_EXT_HAND_INTERACTION_EXTENSION_NAME] = &available;
+ }
+
+ return request_extensions;
+}
+
+bool OpenXRHandInteractionExtension::is_available() {
+ return available;
+}
+
+void OpenXRHandInteractionExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Hand interaction profile
+ metadata->register_interaction_profile("Hand interaction", "/interaction_profiles/ext/hand_interaction_ext", XR_EXT_HAND_INTERACTION_EXTENSION_NAME);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch pose", "/user/hand/left", "/user/hand/left/input/pinch_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch pose", "/user/hand/right", "/user/hand/right/input/pinch_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Poke pose", "/user/hand/left", "/user/hand/left/input/poke_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Poke pose", "/user/hand/right", "/user/hand/right/input/poke_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Palm pose", "/user/hand/left", "/user/hand/left/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Palm pose", "/user/hand/right", "/user/hand/right/input/palm_ext/pose", XR_EXT_PALM_POSE_EXTENSION_NAME, OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch", "/user/hand/left", "/user/hand/left/input/pinch_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch", "/user/hand/right", "/user/hand/right/input/pinch_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch ready", "/user/hand/left", "/user/hand/left/input/pinch_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Pinch ready", "/user/hand/right", "/user/hand/right/input/pinch_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate", "/user/hand/left", "/user/hand/left/input/aim_activate_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate", "/user/hand/right", "/user/hand/right/input/aim_activate_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate ready", "/user/hand/left", "/user/hand/left/input/aim_activate_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Aim activate ready", "/user/hand/right", "/user/hand/right/input/aim_activate_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp", "/user/hand/left", "/user/hand/left/input/grasp_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp", "/user/hand/right", "/user/hand/right/input/grasp_ext/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp ready", "/user/hand/left", "/user/hand/left/input/grasp_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/ext/hand_interaction_ext", "Grasp ready", "/user/hand/right", "/user/hand/right/input/grasp_ext/ready_ext", "", OpenXRAction::OPENXR_ACTION_BOOL);
+}
diff --git a/modules/openxr/extensions/openxr_hand_interaction_extension.h b/modules/openxr/extensions/openxr_hand_interaction_extension.h
new file mode 100644
index 0000000000..789e300c0b
--- /dev/null
+++ b/modules/openxr/extensions/openxr_hand_interaction_extension.h
@@ -0,0 +1,72 @@
+/**************************************************************************/
+/* openxr_hand_interaction_extension.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_HAND_INTERACTION_EXTENSION_H
+#define OPENXR_HAND_INTERACTION_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+// When supported the hand interaction extension introduces an interaction
+// profile that becomes active when the user either lets go of their
+// controllers or isn't using controllers at all.
+//
+// The OpenXR specification states that all XR runtimes that support this
+// interaction profile should also allow it's controller to use this
+// interaction profile.
+// This means that if you only supply this interaction profile in your
+// action map, it should work both when the player is holding a controller
+// or using visual hand tracking.
+//
+// This allows easier portability between games that use controller
+// tracking or hand tracking.
+//
+// See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_hand_interaction
+// for more information.
+
+class OpenXRHandInteractionExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRHandInteractionExtension *get_singleton();
+
+ OpenXRHandInteractionExtension();
+ virtual ~OpenXRHandInteractionExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+ virtual void on_register_metadata() override;
+
+private:
+ static OpenXRHandInteractionExtension *singleton;
+
+ bool available = false;
+};
+
+#endif // OPENXR_HAND_INTERACTION_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index 12fa3bed7e..b8a2f58935 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -128,7 +128,7 @@ void OpenXRHandTrackingExtension::on_process() {
}
// process our hands
- const XrTime time = OpenXRAPI::get_singleton()->get_next_frame_time(); // This data will be used for the next frame we render
+ const XrTime time = OpenXRAPI::get_singleton()->get_predicted_display_time();
if (time == 0) {
// we don't have timing info yet, or we're skipping a frame...
return;
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 1fe402341b..40e3ecfefc 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -160,7 +160,7 @@ void OpenXRAPI::OpenXRSwapChainInfo::free() {
}
}
-bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) {
+bool OpenXRAPI::OpenXRSwapChainInfo::acquire(bool &p_should_render) {
ERR_FAIL_COND_V(image_acquired, true); // This was not released when it should be, error out and reuse...
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
@@ -193,10 +193,18 @@ bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) {
XrSwapchainImageWaitInfo swapchain_image_wait_info = {
XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type
nullptr, // next
- 17000000 // timeout in nanoseconds
+ 1000000000 // 1s timeout in nanoseconds
};
- result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info);
+ // Wait for a maximum of 10 seconds before calling it a critical failure...
+ for (int retry = 0; retry < 10; retry++) {
+ result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info);
+ if (result != XR_TIMEOUT_EXPIRED) {
+ break;
+ }
+ WARN_PRINT("OpenXR: timed out waiting for swapchain image.");
+ }
+
if (!XR_UNQUALIFIED_SUCCESS(result)) {
// Make sure end_frame knows we need to submit an empty frame
p_should_render = false;
@@ -206,6 +214,8 @@ bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) {
print_line("OpenXR: failed to wait for swapchain image [", openxr_api->get_error_string(result), "]");
return false;
} else {
+ WARN_PRINT("OpenXR: couldn't to wait for swapchain but not a complete error [" + openxr_api->get_error_string(result) + "]");
+
// Make sure to skip trying to acquire the swapchain image in the next frame
skip_acquire_swapchain = true;
return false;
@@ -760,21 +770,6 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType
print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount));
}
- // Allocate buffers we'll be populating with view information.
- views = (XrView *)memalloc(sizeof(XrView) * view_count);
- ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views");
- memset(views, 0, sizeof(XrView) * view_count);
-
- projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count);
- ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views");
- memset(projection_views, 0, sizeof(XrCompositionLayerProjectionView) * view_count);
-
- if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) {
- depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count);
- ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views");
- memset(depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * view_count);
- }
-
return true;
}
@@ -927,6 +922,9 @@ bool OpenXRAPI::setup_play_space() {
// If we've previously created a play space, clean it up first.
if (play_space != XR_NULL_HANDLE) {
+ // TODO Investigate if destroying our play space here is safe,
+ // it may still be used in the rendering thread.
+
xrDestroySpace(play_space);
}
play_space = new_play_space;
@@ -936,7 +934,11 @@ bool OpenXRAPI::setup_play_space() {
if (emulating_local_floor) {
// We'll use the STAGE space to get the floor height, but we can't do that until
// after xrWaitFrame(), so just set this flag for now.
+ // Render state will be updated then.
should_reset_emulated_floor_height = true;
+ } else {
+ // Update render state so this play space is used rendering the upcoming frame.
+ set_render_play_space(play_space);
}
return true;
@@ -1016,7 +1018,7 @@ bool OpenXRAPI::reset_emulated_floor_height() {
identityPose, // pose
};
- result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location);
+ result = xrLocateSpace(stage_space, local_space, get_predicted_display_time(), &stage_location);
xrDestroySpace(local_space);
xrDestroySpace(stage_space);
@@ -1042,6 +1044,9 @@ bool OpenXRAPI::reset_emulated_floor_height() {
// report that as the reference space to the outside world.
reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
+ // Update render state so this play space is used rendering the upcoming frame.
+ set_render_play_space(play_space);
+
return true;
}
@@ -1136,6 +1141,7 @@ bool OpenXRAPI::obtain_swapchain_formats() {
}
bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
+ ERR_NOT_ON_RENDER_THREAD_V(false);
ERR_FAIL_NULL_V(graphics_extension, false);
ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
@@ -1154,12 +1160,12 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support
*/
- main_swapchain_size = p_size;
+ render_state.main_swapchain_size = p_size;
uint32_t sample_count = 1;
// We start with our color swapchain...
if (color_swapchain_format != 0) {
- if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) {
+ if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) {
return false;
}
}
@@ -1169,7 +1175,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
// - we support our depth layer extension
// - we have our spacewarp extension (not yet implemented)
if (depth_swapchain_format != 0 && submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) {
- if (!main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) {
+ if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) {
return false;
}
}
@@ -1180,36 +1186,36 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
// TBD
}
- for (uint32_t i = 0; i < view_count; i++) {
- views[i].type = XR_TYPE_VIEW;
- views[i].next = nullptr;
-
- projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
- projection_views[i].next = nullptr;
- projection_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain();
- projection_views[i].subImage.imageArrayIndex = i;
- projection_views[i].subImage.imageRect.offset.x = 0;
- projection_views[i].subImage.imageRect.offset.y = 0;
- projection_views[i].subImage.imageRect.extent.width = main_swapchain_size.width;
- projection_views[i].subImage.imageRect.extent.height = main_swapchain_size.height;
-
- if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) {
- projection_views[i].next = &depth_views[i];
-
- depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
- depth_views[i].next = nullptr;
- depth_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain();
- depth_views[i].subImage.imageArrayIndex = i;
- depth_views[i].subImage.imageRect.offset.x = 0;
- depth_views[i].subImage.imageRect.offset.y = 0;
- depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width;
- depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height;
+ for (uint32_t i = 0; i < render_state.view_count; i++) {
+ render_state.views[i].type = XR_TYPE_VIEW;
+ render_state.views[i].next = nullptr;
+
+ render_state.projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
+ render_state.projection_views[i].next = nullptr;
+ render_state.projection_views[i].subImage.swapchain = render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain();
+ render_state.projection_views[i].subImage.imageArrayIndex = i;
+ render_state.projection_views[i].subImage.imageRect.offset.x = 0;
+ render_state.projection_views[i].subImage.imageRect.offset.y = 0;
+ render_state.projection_views[i].subImage.imageRect.extent.width = render_state.main_swapchain_size.width;
+ render_state.projection_views[i].subImage.imageRect.extent.height = render_state.main_swapchain_size.height;
+
+ if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && render_state.depth_views) {
+ render_state.projection_views[i].next = &render_state.depth_views[i];
+
+ render_state.depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
+ render_state.depth_views[i].next = nullptr;
+ render_state.depth_views[i].subImage.swapchain = render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain();
+ render_state.depth_views[i].subImage.imageArrayIndex = i;
+ render_state.depth_views[i].subImage.imageRect.offset.x = 0;
+ render_state.depth_views[i].subImage.imageRect.offset.y = 0;
+ render_state.depth_views[i].subImage.imageRect.extent.width = render_state.main_swapchain_size.width;
+ render_state.depth_views[i].subImage.imageRect.extent.height = render_state.main_swapchain_size.height;
// OpenXR spec says that: minDepth < maxDepth.
- depth_views[i].minDepth = 0.0;
- depth_views[i].maxDepth = 1.0;
+ render_state.depth_views[i].minDepth = 0.0;
+ render_state.depth_views[i].maxDepth = 1.0;
// But we can reverse near and far for reverse-Z.
- depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix
- depth_views[i].farZ = 0.01;
+ render_state.depth_views[i].nearZ = 100.0; // Near and far Z will be set to the correct values in fill_projection_matrix
+ render_state.depth_views[i].farZ = 0.01;
}
};
@@ -1217,23 +1223,33 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
};
void OpenXRAPI::destroy_session() {
- if (running && session != XR_NULL_HANDLE) {
- xrEndSession(session);
+ // TODO need to figure out if we're still rendering our current frame
+ // in a separate rendering thread and if so,
+ // if we need to wait for completion.
+ // We could be pulling the rug from underneath rendering...
+
+ if (running) {
+ if (session != XR_NULL_HANDLE) {
+ xrEndSession(session);
+ }
+
+ running = false;
+ render_state.running = false;
}
- if (views != nullptr) {
- memfree(views);
- views = nullptr;
+ if (render_state.views != nullptr) {
+ memfree(render_state.views);
+ render_state.views = nullptr;
}
- if (projection_views != nullptr) {
- memfree(projection_views);
- projection_views = nullptr;
+ if (render_state.projection_views != nullptr) {
+ memfree(render_state.projection_views);
+ render_state.projection_views = nullptr;
}
- if (depth_views != nullptr) {
- memfree(depth_views);
- depth_views = nullptr;
+ if (render_state.depth_views != nullptr) {
+ memfree(render_state.depth_views);
+ render_state.depth_views = nullptr;
}
free_main_swapchains();
@@ -1248,6 +1264,7 @@ void OpenXRAPI::destroy_session() {
if (play_space != XR_NULL_HANDLE) {
xrDestroySpace(play_space);
play_space = XR_NULL_HANDLE;
+ render_state.play_space = XR_NULL_HANDLE;
}
if (view_space != XR_NULL_HANDLE) {
xrDestroySpace(view_space);
@@ -1298,6 +1315,7 @@ bool OpenXRAPI::on_state_ready() {
// we're running
running = true;
+ set_render_session_running(true);
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_ready();
@@ -1374,34 +1392,37 @@ bool OpenXRAPI::on_state_stopping() {
}
running = false;
+ set_render_session_running(false);
}
- // TODO further cleanup
-
return true;
}
bool OpenXRAPI::on_state_loss_pending() {
print_verbose("On state loss pending");
+ if (xr_interface) {
+ xr_interface->on_state_loss_pending();
+ }
+
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_loss_pending();
}
- // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting)
-
return true;
}
bool OpenXRAPI::on_state_exiting() {
print_verbose("On state existing");
+ if (xr_interface) {
+ xr_interface->on_state_exiting();
+ }
+
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_state_exiting();
}
- // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting)
-
return true;
}
@@ -1419,10 +1440,7 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat
bool OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) {
requested_reference_space = p_requested_reference_space;
-
- if (is_initialized()) {
- return setup_play_space();
- }
+ play_space_is_dirty = true;
return true;
}
@@ -1625,11 +1643,6 @@ bool OpenXRAPI::initialize_session() {
return false;
}
- if (!setup_play_space()) {
- destroy_session();
- return false;
- }
-
if (!setup_view_space()) {
destroy_session();
return false;
@@ -1645,6 +1658,8 @@ bool OpenXRAPI::initialize_session() {
return false;
}
+ allocate_view_buffers(view_count, submit_depth_buffer);
+
return true;
}
@@ -1696,12 +1711,18 @@ XrHandTrackerEXT OpenXRAPI::get_hand_tracker(int p_hand_index) {
}
Size2 OpenXRAPI::get_recommended_target_size() {
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
ERR_FAIL_NULL_V(view_configuration_views, Size2());
Size2 target_size;
- target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier;
- target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier;
+ if (rendering_server && rendering_server->is_on_render_thread()) {
+ target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_state.render_target_size_multiplier;
+ target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_state.render_target_size_multiplier;
+ } else {
+ target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier;
+ target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier;
+ }
return target_size;
}
@@ -1713,14 +1734,12 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform,
return XRPose::XR_TRACKING_CONFIDENCE_NONE;
}
- // xrWaitFrame not run yet
- if (frame_state.predictedDisplayTime == 0) {
+ // Get display time
+ XrTime display_time = get_predicted_display_time();
+ if (display_time == 0) {
return XRPose::XR_TRACKING_CONFIDENCE_NONE;
}
- // Get timing for the next frame, as that is the current frame we're processing
- XrTime display_time = get_next_frame_time();
-
XrSpaceVelocity velocity = {
XR_TYPE_SPACE_VELOCITY, // type
nullptr, // next
@@ -1764,54 +1783,47 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform,
}
bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) {
- if (!running) {
- return false;
- }
+ ERR_NOT_ON_RENDER_THREAD_V(false);
- // xrWaitFrame not run yet
- if (frame_state.predictedDisplayTime == 0) {
+ if (!render_state.running) {
return false;
}
// we don't have valid view info
- if (views == nullptr || !view_pose_valid) {
+ if (render_state.views == nullptr || !render_state.view_pose_valid) {
return false;
}
// Note, the timing of this is set right before rendering, which is what we need here.
- r_transform = transform_from_pose(views[p_view].pose);
+ r_transform = transform_from_pose(render_state.views[p_view].pose);
return true;
}
bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) {
+ ERR_NOT_ON_RENDER_THREAD_V(false);
ERR_FAIL_NULL_V(graphics_extension, false);
- if (!running) {
- return false;
- }
-
- // xrWaitFrame not run yet
- if (frame_state.predictedDisplayTime == 0) {
+ if (!render_state.running) {
return false;
}
// we don't have valid view info
- if (views == nullptr || !view_pose_valid) {
+ if (render_state.views == nullptr || !render_state.view_pose_valid) {
return false;
}
// if we're using depth views, make sure we update our near and far there...
- if (depth_views != nullptr) {
- for (uint32_t i = 0; i < view_count; i++) {
+ if (render_state.depth_views != nullptr) {
+ for (uint32_t i = 0; i < render_state.view_count; i++) {
// As we are using reverse-Z these need to be flipped.
- depth_views[i].nearZ = p_z_far;
- depth_views[i].farZ = p_z_near;
+ render_state.depth_views[i].nearZ = p_z_far;
+ render_state.depth_views[i].farZ = p_z_near;
}
}
// now update our projection
- return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix);
+ return graphics_extension->create_projection_fov(render_state.views[p_view].fov, p_z_near, p_z_far, p_camera_matrix);
}
bool OpenXRAPI::poll_events() {
@@ -1934,53 +1946,85 @@ bool OpenXRAPI::poll_events() {
}
}
-bool OpenXRAPI::process() {
- ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
+void OpenXRAPI::_allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
- if (!poll_events()) {
- return false;
- }
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
- if (!running) {
- return false;
- }
+ openxr_api->render_state.view_count = p_view_count;
+ openxr_api->render_state.submit_depth_buffer = p_submit_depth_buffer;
- for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
- wrapper->on_process();
+ // Allocate buffers we'll be populating with view information.
+ openxr_api->render_state.views = (XrView *)memalloc(sizeof(XrView) * p_view_count);
+ ERR_FAIL_NULL_MSG(openxr_api->render_state.views, "OpenXR Couldn't allocate memory for views");
+ memset(openxr_api->render_state.views, 0, sizeof(XrView) * p_view_count);
+
+ openxr_api->render_state.projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * p_view_count);
+ ERR_FAIL_NULL_MSG(openxr_api->render_state.projection_views, "OpenXR Couldn't allocate memory for projection views");
+ memset(openxr_api->render_state.projection_views, 0, sizeof(XrCompositionLayerProjectionView) * p_view_count);
+
+ if (p_submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) {
+ openxr_api->render_state.depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count);
+ ERR_FAIL_NULL_MSG(openxr_api->render_state.depth_views, "OpenXR Couldn't allocate memory for depth views");
+ memset(openxr_api->render_state.depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count);
}
+}
- return true;
+void OpenXRAPI::_set_render_session_running(bool p_is_running) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
+
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+ openxr_api->render_state.running = p_is_running;
}
-void OpenXRAPI::free_main_swapchains() {
- for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
- main_swapchains[i].queue_free();
- }
+void OpenXRAPI::_set_render_display_info(XrTime p_predicted_display_time, bool p_should_render) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
+
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+ openxr_api->render_state.predicted_display_time = p_predicted_display_time;
+ openxr_api->render_state.should_render = p_should_render;
}
-void OpenXRAPI::pre_render() {
- ERR_FAIL_COND(instance == XR_NULL_HANDLE);
+void OpenXRAPI::_set_render_play_space(uint64_t p_play_space) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
- if (!running) {
- return;
- }
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+ openxr_api->render_state.play_space = XrSpace(p_play_space);
+}
- // Process any swapchains that were queued to be freed
- OpenXRSwapChainInfo::free_queued();
+void OpenXRAPI::_set_render_state_multiplier(double p_render_target_size_multiplier) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
- Size2i swapchain_size = get_recommended_target_size();
- if (swapchain_size != main_swapchain_size) {
- // Out with the old.
- free_main_swapchains();
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+ openxr_api->render_state.render_target_size_multiplier = p_render_target_size_multiplier;
+}
- // In with the new.
- create_main_swapchains(swapchain_size);
+bool OpenXRAPI::process() {
+ ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
+
+ if (!poll_events()) {
+ return false;
}
- // Waitframe does 2 important things in our process:
- // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit
- // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible
- // This must thus be called as close to when we start rendering as possible
+ if (!running) {
+ return false;
+ }
+
+ // We call xrWaitFrame as early as possible, this will allow OpenXR to get
+ // proper timing info between this point, and when we're ready to start rendering.
+ // As the name suggests, OpenXR can pause the thread to minimize the time between
+ // retrieving tracking data and using that tracking data to render.
+ // OpenXR thus works best if rendering is performed on a separate thread.
XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr };
frame_state.predictedDisplayTime = 0;
frame_state.predictedDisplayPeriod = 0;
@@ -1995,7 +2039,9 @@ void OpenXRAPI::pre_render() {
frame_state.predictedDisplayPeriod = 0;
frame_state.shouldRender = false;
- return;
+ set_render_display_info(0, false);
+
+ return false;
}
if (frame_state.predictedDisplayPeriod > 500000000) {
@@ -2004,12 +2050,54 @@ void OpenXRAPI::pre_render() {
frame_state.predictedDisplayPeriod = 0;
}
+ set_render_display_info(frame_state.predictedDisplayTime, frame_state.shouldRender);
+
+ if (unlikely(play_space_is_dirty)) {
+ setup_play_space();
+ play_space_is_dirty = false;
+ }
+
if (unlikely(should_reset_emulated_floor_height)) {
reset_emulated_floor_height();
should_reset_emulated_floor_height = false;
}
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
+ wrapper->on_process();
+ }
+
+ return true;
+}
+
+void OpenXRAPI::free_main_swapchains() {
+ for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
+ render_state.main_swapchains[i].queue_free();
+ }
+}
+
+void OpenXRAPI::pre_render() {
+ ERR_FAIL_COND(session == XR_NULL_HANDLE);
+
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!render_state.running) {
+ return;
+ }
+
+ // Process any swapchains that were queued to be freed
+ OpenXRSwapChainInfo::free_queued();
+
+ Size2i swapchain_size = get_recommended_target_size();
+ if (swapchain_size != render_state.main_swapchain_size) {
+ // Out with the old.
+ free_main_swapchains();
+
+ // In with the new.
+ create_main_swapchains(swapchain_size);
+ }
+
+ for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_pre_render();
}
@@ -2028,8 +2116,8 @@ void OpenXRAPI::pre_render() {
XR_TYPE_VIEW_LOCATE_INFO, // type
nullptr, // next
view_configuration, // viewConfigurationType
- frame_state.predictedDisplayTime, // displayTime
- play_space // space
+ render_state.predicted_display_time, // displayTime
+ render_state.play_space // space
};
XrViewState view_state = {
XR_TYPE_VIEW_STATE, // type
@@ -2037,7 +2125,7 @@ void OpenXRAPI::pre_render() {
0 // viewStateFlags
};
uint32_t view_count_output;
- result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views);
+ XrResult result = xrLocateViews(session, &view_locate_info, &view_state, render_state.view_count, &view_count_output, render_state.views);
if (XR_FAILED(result)) {
print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]");
return;
@@ -2050,9 +2138,9 @@ void OpenXRAPI::pre_render() {
pose_valid = false;
}
}
- if (view_pose_valid != pose_valid) {
- view_pose_valid = pose_valid;
- if (!view_pose_valid) {
+ if (render_state.view_pose_valid != pose_valid) {
+ render_state.view_pose_valid = pose_valid;
+ if (!render_state.view_pose_valid) {
print_verbose("OpenXR View pose became invalid");
} else {
print_verbose("OpenXR View pose became valid");
@@ -2071,23 +2159,24 @@ void OpenXRAPI::pre_render() {
}
// Reset this, we haven't found a viewport for output yet
- has_xr_viewport = false;
+ render_state.has_xr_viewport = false;
}
bool OpenXRAPI::pre_draw_viewport(RID p_render_target) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD_V(false);
+
// We found an XR viewport!
- has_xr_viewport = true;
+ render_state.has_xr_viewport = true;
- if (!can_render()) {
+ if (instance == XR_NULL_HANDLE || session == XR_NULL_HANDLE || !render_state.running || !render_state.view_pose_valid || !render_state.should_render) {
return false;
}
- // TODO: at some point in time we may support multiple viewports in which case we need to handle that...
-
// Acquire our images
for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
- if (!main_swapchains[i].is_image_acquired() && main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) {
- if (!main_swapchains[i].acquire(frame_state.shouldRender)) {
+ if (!render_state.main_swapchains[i].is_image_acquired() && render_state.main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) {
+ if (!render_state.main_swapchains[i].acquire(render_state.should_render)) {
return false;
}
}
@@ -2101,24 +2190,33 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) {
}
XrSwapchain OpenXRAPI::get_color_swapchain() {
- return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain();
+ ERR_NOT_ON_RENDER_THREAD_V(XR_NULL_HANDLE);
+
+ return render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain();
}
RID OpenXRAPI::get_color_texture() {
- return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image();
+ ERR_NOT_ON_RENDER_THREAD_V(RID());
+
+ return render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image();
}
RID OpenXRAPI::get_depth_texture() {
+ ERR_NOT_ON_RENDER_THREAD_V(RID());
+
// Note, image will not be acquired if we didn't have a suitable swap chain format.
- if (submit_depth_buffer) {
- return main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image();
+ if (render_state.submit_depth_buffer && render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].is_image_acquired()) {
+ return render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image();
} else {
return RID();
}
}
void OpenXRAPI::post_draw_viewport(RID p_render_target) {
- if (!can_render()) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (instance == XR_NULL_HANDLE || session == XR_NULL_HANDLE || !render_state.running || !render_state.view_pose_valid || !render_state.should_render) {
return;
}
@@ -2130,30 +2228,33 @@ void OpenXRAPI::post_draw_viewport(RID p_render_target) {
void OpenXRAPI::end_frame() {
XrResult result;
- ERR_FAIL_COND(instance == XR_NULL_HANDLE);
+ ERR_FAIL_COND(session == XR_NULL_HANDLE);
- if (!running) {
+ // Must be called from rendering thread!
+ ERR_NOT_ON_RENDER_THREAD;
+
+ if (!render_state.running) {
return;
}
- if (frame_state.shouldRender && view_pose_valid) {
- if (!has_xr_viewport) {
+ if (render_state.should_render && render_state.view_pose_valid) {
+ if (!render_state.has_xr_viewport) {
print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!");
- } else if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) {
+ } else if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) {
print_line("OpenXR: No swapchain could be acquired to render to!");
}
}
// must have:
- // - shouldRender set to true
+ // - should_render set to true
// - a valid view pose for projection_views[eye].pose to submit layer
// - an image to render
- if (!frame_state.shouldRender || !view_pose_valid || !main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) {
+ if (!render_state.should_render || !render_state.view_pose_valid || !render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) {
// submit 0 layers when we shouldn't render
XrFrameEndInfo frame_end_info = {
XR_TYPE_FRAME_END_INFO, // type
nullptr, // next
- frame_state.predictedDisplayTime, // displayTime
+ render_state.predicted_display_time, // displayTime
environment_blend_mode, // environmentBlendMode
0, // layerCount
nullptr // layers
@@ -2170,14 +2271,14 @@ void OpenXRAPI::end_frame() {
// release our swapchain image if we acquired it
for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
- if (main_swapchains[i].is_image_acquired()) {
- main_swapchains[i].release();
+ if (render_state.main_swapchains[i].is_image_acquired()) {
+ render_state.main_swapchains[i].release();
}
}
- for (uint32_t eye = 0; eye < view_count; eye++) {
- projection_views[eye].fov = views[eye].fov;
- projection_views[eye].pose = views[eye].pose;
+ for (uint32_t eye = 0; eye < render_state.view_count; eye++) {
+ render_state.projection_views[eye].fov = render_state.views[eye].fov;
+ render_state.projection_views[eye].pose = render_state.views[eye].pose;
}
Vector<OrderedCompositionLayer> ordered_layers_list;
@@ -2210,9 +2311,9 @@ void OpenXRAPI::end_frame() {
XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type
nullptr, // next
layer_flags, // layerFlags
- play_space, // space
- view_count, // viewCount
- projection_views, // views
+ render_state.play_space, // space
+ render_state.view_count, // viewCount
+ render_state.projection_views, // views
};
ordered_layers_list.push_back({ (const XrCompositionLayerBaseHeader *)&projection_layer, 0 });
@@ -2228,7 +2329,7 @@ void OpenXRAPI::end_frame() {
XrFrameEndInfo frame_end_info = {
XR_TYPE_FRAME_END_INFO, // type
nullptr, // next
- frame_state.predictedDisplayTime, // displayTime
+ render_state.predicted_display_time, // displayTime
environment_blend_mode, // environmentBlendMode
static_cast<uint32_t>(layers_list.size()), // layerCount
layers_list.ptr() // layers
@@ -2271,6 +2372,7 @@ double OpenXRAPI::get_render_target_size_multiplier() const {
void OpenXRAPI::set_render_target_size_multiplier(double multiplier) {
render_target_size_multiplier = multiplier;
+ set_render_state_multiplier(multiplier);
}
bool OpenXRAPI::is_foveation_supported() const {
@@ -2414,10 +2516,6 @@ OpenXRAPI::OpenXRAPI() {
submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer");
}
-
- // Reset a few things that can't be done in our class definition.
- frame_state.predictedDisplayTime = 0;
- frame_state.predictedDisplayPeriod = 0;
}
OpenXRAPI::~OpenXRAPI() {
@@ -3132,7 +3230,7 @@ XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracke
return XRPose::XR_TRACKING_CONFIDENCE_NONE;
}
- XrTime display_time = get_next_frame_time();
+ XrTime display_time = get_predicted_display_time();
if (display_time == 0) {
return XRPose::XR_TRACKING_CONFIDENCE_NONE;
}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index e835366200..c95867810c 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -46,13 +46,11 @@
#include "core/templates/rb_map.h"
#include "core/templates/rid_owner.h"
#include "core/templates/vector.h"
+#include "servers/rendering_server.h"
#include "servers/xr/xr_pose.h"
#include <openxr/openxr.h>
-// Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initializing structs which ensures zeroing out unspecified members.
-// Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set.
-
// forward declarations, we don't want to include these fully
class OpenXRInterface;
@@ -77,7 +75,7 @@ public:
static void free_queued();
void free();
- bool acquire(XrBool32 &p_should_render);
+ bool acquire(bool &p_should_render);
bool release();
RID get_image();
};
@@ -151,9 +149,6 @@ private:
uint32_t view_count = 0;
XrViewConfigurationView *view_configuration_views = nullptr;
- XrView *views = nullptr;
- XrCompositionLayerProjectionView *projection_views = nullptr;
- XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available
enum OpenXRSwapChainTypes {
OPENXR_SWAPCHAIN_COLOR,
@@ -164,14 +159,11 @@ private:
int64_t color_swapchain_format = 0;
int64_t depth_swapchain_format = 0;
- Size2i main_swapchain_size = { 0, 0 };
- OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX];
+ bool play_space_is_dirty = true;
XrSpace play_space = XR_NULL_HANDLE;
XrSpace view_space = XR_NULL_HANDLE;
- bool view_pose_valid = false;
XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
- bool has_xr_viewport = false;
bool emulating_local_floor = false;
bool should_reset_emulated_floor_height = false;
@@ -328,6 +320,72 @@ private:
// convenience
void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len);
+ // Render state, Only accessible in rendering thread
+ struct RenderState {
+ bool running = false;
+ bool should_render = false;
+ bool has_xr_viewport = false;
+ XrTime predicted_display_time = 0;
+ XrSpace play_space = XR_NULL_HANDLE;
+ double render_target_size_multiplier = 1.0;
+
+ uint32_t view_count = 0;
+ XrView *views = nullptr;
+ XrCompositionLayerProjectionView *projection_views = nullptr;
+ XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available
+ bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled.
+ bool view_pose_valid = false;
+
+ Size2i main_swapchain_size;
+ OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX];
+ } render_state;
+
+ static void _allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer);
+ static void _set_render_session_running(bool p_is_running);
+ static void _set_render_display_info(XrTime p_predicted_display_time, bool p_should_render);
+ static void _set_render_play_space(uint64_t p_play_space);
+ static void _set_render_state_multiplier(double p_render_target_size_multiplier);
+
+ _FORCE_INLINE_ void allocate_view_buffers(uint32_t p_view_count, bool p_submit_depth_buffer) {
+ // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_allocate_view_buffers).bind(p_view_count, p_submit_depth_buffer));
+ }
+
+ _FORCE_INLINE_ void set_render_session_running(bool p_is_running) {
+ // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_session_running).bind(p_is_running));
+ }
+
+ _FORCE_INLINE_ void set_render_display_info(XrTime p_predicted_display_time, bool p_should_render) {
+ // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_display_info).bind(p_predicted_display_time, p_should_render));
+ }
+
+ _FORCE_INLINE_ void set_render_play_space(XrSpace p_play_space) {
+ // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_play_space).bind(uint64_t(p_play_space)));
+ }
+
+ _FORCE_INLINE_ void set_render_state_multiplier(double p_render_target_size_multiplier) {
+ // If we're rendering on a separate thread, we may still be processing the last frame, don't communicate this till we're ready...
+ RenderingServer *rendering_server = RenderingServer::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ rendering_server->call_on_render_thread(callable_mp_static(&OpenXRAPI::_set_render_state_multiplier).bind(p_render_target_size_multiplier));
+ }
+
public:
XrInstance get_instance() const { return instance; };
XrSystemId get_system_id() const { return system_id; };
@@ -384,9 +442,13 @@ public:
bool initialize_session();
void finish();
- XrSpace get_play_space() const { return play_space; }
- XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }
- bool can_render() { return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && view_pose_valid && frame_state.shouldRender; }
+ _FORCE_INLINE_ XrSpace get_play_space() const { return play_space; }
+ _FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; }
+ _FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }
+ _FORCE_INLINE_ bool can_render() {
+ ERR_ON_RENDER_THREAD_V(false);
+ return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender;
+ }
XrHandTrackerEXT get_hand_tracker(int p_hand_index);
diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp
index fae0fc13d3..a1744fa1db 100644
--- a/modules/openxr/openxr_api_extension.cpp
+++ b/modules/openxr/openxr_api_extension.cpp
@@ -48,6 +48,7 @@ void OpenXRAPIExtension::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running);
ClassDB::bind_method(D_METHOD("get_play_space"), &OpenXRAPIExtension::get_play_space);
+ ClassDB::bind_method(D_METHOD("get_predicted_display_time"), &OpenXRAPIExtension::get_predicted_display_time);
ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time);
ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render);
@@ -130,8 +131,17 @@ uint64_t OpenXRAPIExtension::get_play_space() {
return (uint64_t)OpenXRAPI::get_singleton()->get_play_space();
}
+int64_t OpenXRAPIExtension::get_predicted_display_time() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (XrTime)OpenXRAPI::get_singleton()->get_predicted_display_time();
+}
+
int64_t OpenXRAPIExtension::get_next_frame_time() {
ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+
+ // In the past we needed to look a frame ahead, may be calling this unintentionally so lets warn the dev.
+ WARN_PRINT_ONCE("OpenXR: Next frame timing called, verify this is intended.");
+
return (XrTime)OpenXRAPI::get_singleton()->get_next_frame_time();
}
diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h
index 576e497798..cff2c4738e 100644
--- a/modules/openxr/openxr_api_extension.h
+++ b/modules/openxr/openxr_api_extension.h
@@ -69,6 +69,7 @@ public:
bool is_running();
uint64_t get_play_space();
+ int64_t get_predicted_display_time();
int64_t get_next_frame_time();
bool can_render();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index aa68441f03..39a61d1b4d 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -35,6 +35,7 @@
#include "servers/rendering/rendering_server_globals.h"
#include "extensions/openxr_eye_gaze_interaction.h"
+#include "extensions/openxr_hand_interaction_extension.h"
#include "thirdparty/openxr/include/openxr/openxr.h"
void OpenXRInterface::_bind_methods() {
@@ -43,6 +44,8 @@ void OpenXRInterface::_bind_methods() {
ADD_SIGNAL(MethodInfo("session_stopping"));
ADD_SIGNAL(MethodInfo("session_focussed"));
ADD_SIGNAL(MethodInfo("session_visible"));
+ ADD_SIGNAL(MethodInfo("session_loss_pending"));
+ ADD_SIGNAL(MethodInfo("instance_exiting"));
ADD_SIGNAL(MethodInfo("pose_recentered"));
ADD_SIGNAL(MethodInfo("refresh_rate_changed", PropertyInfo(Variant::FLOAT, "refresh_rate")));
@@ -91,6 +94,7 @@ void OpenXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity);
ClassDB::bind_method(D_METHOD("is_hand_tracking_supported"), &OpenXRInterface::is_hand_tracking_supported);
+ ClassDB::bind_method(D_METHOD("is_hand_interaction_supported"), &OpenXRInterface::is_hand_interaction_supported);
ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported);
BIND_ENUM_CONSTANT(HAND_LEFT);
@@ -806,6 +810,21 @@ bool OpenXRInterface::is_hand_tracking_supported() {
}
}
+bool OpenXRInterface::is_hand_interaction_supported() const {
+ if (openxr_api == nullptr) {
+ return false;
+ } else if (!openxr_api->is_initialized()) {
+ return false;
+ } else {
+ OpenXRHandInteractionExtension *hand_interaction_ext = OpenXRHandInteractionExtension::get_singleton();
+ if (hand_interaction_ext == nullptr) {
+ return false;
+ } else {
+ return hand_interaction_ext->is_available();
+ }
+ }
+}
+
bool OpenXRInterface::is_eye_gaze_interaction_supported() {
if (openxr_api == nullptr) {
return false;
@@ -1258,6 +1277,14 @@ void OpenXRInterface::on_state_stopping() {
emit_signal(SNAME("session_stopping"));
}
+void OpenXRInterface::on_state_loss_pending() {
+ emit_signal(SNAME("session_loss_pending"));
+}
+
+void OpenXRInterface::on_state_exiting() {
+ emit_signal(SNAME("instance_exiting"));
+}
+
void OpenXRInterface::on_pose_recentered() {
emit_signal(SNAME("pose_recentered"));
}
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index e916c7dac2..ac33304757 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -31,6 +31,29 @@
#ifndef OPENXR_INTERFACE_H
#define OPENXR_INTERFACE_H
+// A note on multithreading and thread safety in OpenXR.
+//
+// Most entry points will be called from the main thread in Godot
+// however a number of entry points will be called from the
+// rendering thread, potentially while we're already processing
+// the next frame on the main thread.
+//
+// OpenXR itself has been designed with threading in mind including
+// a high likelihood that the XR runtime runs in separate threads
+// as well.
+// Hence all the frame timing information, use of swapchains and
+// sync functions.
+// Do note that repeated calls to tracking APIs will provide
+// increasingly more accurate data for the same timestamp as
+// tracking data is continuously updated.
+//
+// For our code we mostly implement this in our OpenXRAPI class.
+// We store data accessed from the rendering thread in a separate
+// struct, setting values through our renderer command queue.
+//
+// As some data is setup before we start rendering, and cleaned up
+// after we've stopped, that is accessed directly from both threads.
+
#include "action_map/openxr_action_map.h"
#include "extensions/openxr_hand_tracking_extension.h"
#include "openxr_api.h"
@@ -110,6 +133,7 @@ public:
virtual TrackingStatus get_tracking_status() const override;
bool is_hand_tracking_supported();
+ bool is_hand_interaction_supported() const;
bool is_eye_gaze_interaction_supported();
bool initialize_on_startup() const;
@@ -173,6 +197,8 @@ public:
void on_state_visible();
void on_state_focused();
void on_state_stopping();
+ void on_state_loss_pending();
+ void on_state_exiting();
void on_pose_recentered();
void on_refresh_rate_changes(float p_new_rate);
void tracker_profile_changed(RID p_tracker, RID p_interaction_profile);
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index eb0527f07c..85514737f2 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -49,6 +49,7 @@
#include "extensions/openxr_composition_layer_extension.h"
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
+#include "extensions/openxr_hand_interaction_extension.h"
#include "extensions/openxr_hand_tracking_extension.h"
#include "extensions/openxr_htc_controller_extension.h"
#include "extensions/openxr_htc_vive_tracker_extension.h"
@@ -124,6 +125,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension));
// register gated extensions
if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {