diff options
Diffstat (limited to 'modules/openxr')
39 files changed, 3319 insertions, 393 deletions
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 593d1ff3c1..84542be3b9 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -18,38 +18,36 @@ env_openxr.Prepend( thirdparty_dir + "/src", thirdparty_dir + "/src/common", thirdparty_dir + "/src/external/jsoncpp/include", - thirdparty_dir + "/src/loader", ] ) -# may need to check and set: -# - XR_USE_TIMESPEC - -env_thirdparty = env_openxr.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) - if env["platform"] == "android": # may need to set OPENXR_ANDROID_VERSION_SUFFIX - env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_ANDROID", "XR_USE_PLATFORM_ANDROID"]) + env_openxr.AppendUnique(CPPDEFINES=["XR_OS_ANDROID", "XR_USE_PLATFORM_ANDROID"]) + env_openxr.AppendUnique(CPPDEFINES=["JSON_USE_EXCEPTION=0"]) # may need to include java parts of the openxr loader elif env["platform"] == "linuxbsd": - env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX"]) + env_openxr.AppendUnique(CPPDEFINES=["XR_OS_LINUX"]) if env["x11"]: - env_thirdparty.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"]) + env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"]) # FIXME: Review what needs to be set for Android and macOS. - env_thirdparty.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) + env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) elif env["platform"] == "windows": - env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) + env_openxr.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") +# may need to check and set: +# - XR_USE_TIMESPEC -# add in common files (hope these don't clash with us) -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/object_info.cpp") +env_thirdparty = env_openxr.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) + +if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]: + env_thirdparty["CXXFLAGS"].remove("-fno-exceptions") +env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"]) # add in external jsoncpp dependency env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_reader.cpp") @@ -57,17 +55,24 @@ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_writer.cpp") # add in load -if env["platform"] == "android": - env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/android_utilities.cpp") - -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_core.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") +if env["platform"] != "android": + # On Android the openxr_loader is provided by separate plugins for each device + # Build the engine using object files + khrloader_obj = [] + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/object_info.cpp") + + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_core.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") + env.modules_sources += khrloader_obj env.modules_sources += thirdparty_obj @@ -78,18 +83,26 @@ module_obj = [] env_openxr.add_source_files(module_obj, "*.cpp") env_openxr.add_source_files(module_obj, "action_map/*.cpp") +env_openxr.add_source_files(module_obj, "scene/*.cpp") # We're a little more targeted with our extensions if env["platform"] == "android": env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") if env["vulkan"]: env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") +if env["opengl3"]: + env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") env.modules_sources += module_obj -if env["tools"]: +if env.editor_build: SConscript("editor/SCsub") # Needed to force rebuilding the module files when the thirdparty library is updated. diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp index 359975a480..0fb4f0773f 100644 --- a/modules/openxr/action_map/openxr_action.cpp +++ b/modules/openxr/action_map/openxr_action.cpp @@ -64,13 +64,13 @@ Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_loc } String OpenXRAction::get_name_with_set() const { - String name = get_name(); + String action_name = get_name(); if (action_set != nullptr) { - name = action_set->get_name() + "/" + name; + action_name = action_set->get_name() + "/" + action_name; } - return name; + return action_name; } void OpenXRAction::set_localized_name(const String p_localized_name) { diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 0eb5302442..123f860ce9 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -157,7 +157,11 @@ void OpenXRActionMap::remove_interaction_profile(Ref<OpenXRInteractionProfile> p } void OpenXRActionMap::create_default_action_sets() { - // Note, if you make changes here make sure to delete your default_action_map.tres file of it will load an old version. + // Note: + // - if you make changes here make sure to delete your default_action_map.tres file of it will load an old version. + // - our palm pose is only available if the relevant extension is supported, + // 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 Ref<OpenXRActionSet> action_set = OpenXRActionSet::new_action_set("godot", "Godot action set"); @@ -200,6 +204,7 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/keyboard"); Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> palm_pose = action_set->add_new_action("palm_pose", "Palm pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left," "/user/hand/right," @@ -219,9 +224,10 @@ void OpenXRActionMap::create_default_action_sets() { // 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/grip/pose,/user/hand/right/input/grip/pose"); + 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,/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 @@ -230,9 +236,10 @@ void OpenXRActionMap::create_default_action_sets() { // Create our Vive controller profile profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + 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,/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 @@ -250,9 +257,10 @@ void OpenXRActionMap::create_default_action_sets() { // Create our WMR controller profile profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + 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 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 @@ -272,9 +280,10 @@ void OpenXRActionMap::create_default_action_sets() { // 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/grip/pose,/user/hand/right/input/grip/pose"); + 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 @@ -296,9 +305,10 @@ void OpenXRActionMap::create_default_action_sets() { // 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/grip/pose,/user/hand/right/input/grip/pose"); + 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 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 @@ -321,16 +331,12 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); add_interaction_profile(profile); - // Note, the following profiles are all part of extensions. - // We include these regardless of whether the extension is active. - // We want our action map to be as complete as possible so our game is as portable as possible. - // It is very possible these will in due time become core. - // 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/grip/pose,/user/hand/right/input/grip/pose"); + 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 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 @@ -350,9 +356,10 @@ void OpenXRActionMap::create_default_action_sets() { // Create our Samsung Odyssey controller profile, // Note that this controller is only identified specifically on WMR, on SteamVR this is identified as a normal WMR controller. profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/samsung/odyssey_controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + 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"); // 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 @@ -372,9 +379,10 @@ void OpenXRActionMap::create_default_action_sets() { // 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/grip/pose,/user/hand/right/input/grip/pose"); + 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/left/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 @@ -395,9 +403,10 @@ void OpenXRActionMap::create_default_action_sets() { // 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 profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_focus3_controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + 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/left/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 @@ -418,9 +427,10 @@ void OpenXRActionMap::create_default_action_sets() { // Create our Huawei controller profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/huawei/controller"); - profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + 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/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"); diff --git a/modules/openxr/action_map/openxr_defs.cpp b/modules/openxr/action_map/openxr_defs.cpp index 89860199be..59ce829f1b 100644 --- a/modules/openxr/action_map/openxr_defs.cpp +++ b/modules/openxr/action_map/openxr_defs.cpp @@ -62,6 +62,8 @@ OpenXRDefs::IOPath OpenXRDefs::simple_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -78,6 +80,8 @@ OpenXRDefs::IOPath OpenXRDefs::vive_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -109,6 +113,8 @@ OpenXRDefs::IOPath OpenXRDefs::motion_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -143,6 +149,8 @@ OpenXRDefs::IOPath OpenXRDefs::hpmr_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -175,6 +183,8 @@ OpenXRDefs::IOPath OpenXRDefs::touch_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -213,6 +223,8 @@ OpenXRDefs::IOPath OpenXRDefs::index_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -260,6 +272,8 @@ OpenXRDefs::IOPath OpenXRDefs::odyssey_io_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -294,6 +308,8 @@ OpenXRDefs::IOPath OpenXRDefs::vive_cosmos_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -331,6 +347,8 @@ OpenXRDefs::IOPath OpenXRDefs::vive_focus3_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Menu click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/menu/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "System click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/system/click", OpenXRAction::OPENXR_ACTION_BOOL }, @@ -371,6 +389,8 @@ OpenXRDefs::IOPath OpenXRDefs::huawei_controller_paths[] = { { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Aim pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/aim/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "Palm pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/palm_ext/pose", OpenXRAction::OPENXR_ACTION_POSE }, { "Home click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/home/click", OpenXRAction::OPENXR_ACTION_BOOL }, { "Home click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/home/click", OpenXRAction::OPENXR_ACTION_BOOL }, diff --git a/modules/openxr/config.py b/modules/openxr/config.py index f91cb1359f..279168cc59 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -1,7 +1,5 @@ def can_build(env, platform): - if ( - platform == "linuxbsd" or platform == "windows" - ): # or platform == "android" -- temporarily disabled android support + if platform in ("linuxbsd", "windows", "android"): return env["openxr"] else: # not supported on these platforms @@ -20,6 +18,7 @@ def get_doc_classes(): "OpenXRActionMap", "OpenXRInteractionProfile", "OpenXRIPBinding", + "OpenXRHand", ] diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml index d1a2ce2d2e..a3a45ebb4c 100644 --- a/modules/openxr/doc_classes/OpenXRAction.xml +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -6,7 +6,7 @@ <description> This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics). OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/code] if the trigger is depressed and [code]true[/code] if pressed fully. - Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. + Actions are not directly bound to specific devices, instead OpenXR recognizes a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. Note that the name of the resource is used to register the action with. </description> <tutorials> @@ -16,7 +16,7 @@ The type of action. </member> <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> - The localised description of this action. + The localized description of this action. </member> <member name="toplevel_paths" type="PackedStringArray" setter="set_toplevel_paths" getter="get_toplevel_paths" default="PackedStringArray()"> A collections of toplevel paths to which this action can be bound. diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml index db3259ec07..39e518750a 100644 --- a/modules/openxr/doc_classes/OpenXRActionSet.xml +++ b/modules/openxr/doc_classes/OpenXRActionSet.xml @@ -36,7 +36,7 @@ Collection of actions for this action set. </member> <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> - The localised name of this action set. + The localized name of this action set. </member> <member name="priority" type="int" setter="set_priority" getter="get_priority" default="0"> The priority for this action set. diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml new file mode 100644 index 0000000000..5d5f8a6126 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRHand.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRHand" inherits="Node3D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Node supporting finger tracking in OpenXR. + </brief_description> + <description> + This node enables OpenXR's hand tracking functionality. The node should be a child node of an [XROrigin3D] node, tracking will update its position to where the player's actual hand is positioned. This node also updates the skeleton of a properly skinned hand model. The hand mesh should be a child node of this node. + </description> + <tutorials> + </tutorials> + <members> + <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0"> + Specifies whether this node tracks the left or right hand of the player. + </member> + <member name="hand_skeleton" type="NodePath" setter="set_hand_skeleton" getter="get_hand_skeleton" default="NodePath("")"> + Set a [Skeleton3D] node for which the pose positions will be updated. + </member> + <member name="motion_range" type="int" setter="set_motion_range" getter="get_motion_range" enum="OpenXRHand.MotionRange" default="0"> + Set the motion range (if supported) limiting the hand motion. + </member> + </members> + <constants> + <constant name="HAND_LEFT" value="0" enum="Hands"> + Tracking the player's left hand. + </constant> + <constant name="HAND_RIGHT" value="1" enum="Hands"> + Tracking the player's right hand. + </constant> + <constant name="HAND_MAX" value="2" enum="Hands"> + Maximum supported hands. + </constant> + <constant name="MOTION_RANGE_UNOBSTRUCTED" value="0" enum="MotionRange"> + When player grips, hand skeleton will form a full fist. + </constant> + <constant name="MOTION_RANGE_CONFORM_TO_CONTROLLER" value="1" enum="MotionRange"> + When player grips, hand skeleton conforms to the controller the player is holding. + </constant> + <constant name="MOTION_RANGE_MAX" value="2" enum="MotionRange"> + Maximum supported motion ranges. + </constant> + </constants> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 25bf496de9..7251a4a9bd 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -5,11 +5,24 @@ </brief_description> <description> The OpenXR interface allows Godot to interact with OpenXR runtimes and make it possible to create XR experiences and games. - Due to the needs of OpenXR this interface works slightly different than other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. + Due to the needs of OpenXR this interface works slightly different than other plugin based XR interfaces. It needs to be initialized when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. </description> <tutorials> <link title="Setting up XR">$DOCS_URL/tutorials/xr/setting_up_xr.html</link> </tutorials> + <methods> + <method name="get_available_display_refresh_rates" qualifiers="const"> + <return type="Array" /> + <description> + Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. + </description> + </method> + </methods> + <members> + <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> + The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized. + </member> + </members> <signals> <signal name="pose_recentered"> <description> diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index 51c402d746..b5223e5903 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -128,7 +128,7 @@ void OpenXRActionMapEditor::_update_interaction_profiles() { interaction_profiles.remove_at(0); tabs->remove_child(interaction_profile); - interaction_profile->queue_delete(); + interaction_profile->queue_free(); } // in with the new... @@ -205,7 +205,7 @@ void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { action_map->remove_action_set(action_set); actionsets_vb->remove_child(action_set_editor); - action_set_editor->queue_delete(); + action_set_editor->queue_free(); } void OpenXRActionMapEditor::_on_action_removed() { @@ -290,7 +290,7 @@ void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) { action_map->remove_interaction_profile(interaction_profile); tabs->remove_child(profile_editor); - profile_editor->queue_delete(); + profile_editor->queue_free(); } void OpenXRActionMapEditor::open_action_map(String p_path) { @@ -364,7 +364,7 @@ OpenXRActionMapEditor::OpenXRActionMapEditor() { select_interaction_profile_dialog->connect("interaction_profile_selected", callable_mp(this, &OpenXRActionMapEditor::_on_interaction_profile_selected)); add_child(select_interaction_profile_dialog); - _load_action_map(ProjectSettings::get_singleton()->get("xr/openxr/default_action_map")); + _load_action_map(GLOBAL_GET("xr/openxr/default_action_map")); } OpenXRActionMapEditor::~OpenXRActionMapEditor() { diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp index 804808a6b9..3869146e8e 100644 --- a/modules/openxr/editor/openxr_action_set_editor.cpp +++ b/modules/openxr/editor/openxr_action_set_editor.cpp @@ -140,7 +140,7 @@ void OpenXRActionSetEditor::_on_remove_action(Object *p_action_editor) { // And remove it.... action_map->remove_action(action->get_name_with_set()); // remove it from the set and any interaction profile it relates to actions_vb->remove_child(action_editor); - action_editor->queue_delete(); + action_editor->queue_free(); // Let action map editor know so we can update our interaction profiles emit_signal("action_removed"); diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp index 3bd4db169c..8f6d5c28db 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -29,7 +29,12 @@ /*************************************************************************/ #include "openxr_android_extension.h" +#include "java_godot_wrapper.h" +#include "os_android.h" +#include "thread_jandroid.h" +#include <jni.h> +#include <modules/openxr/openxr_api.h> #include <openxr/openxr.h> #include <openxr/openxr_platform.h> @@ -42,19 +47,16 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api) { singleton = this; - request_extensions[XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME] = nullptr; // must be available +} - // Initialize the loader - PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; - result = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction *)(&xrInitializeLoaderKHR)); - ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to retrieve pointer to xrInitializeLoaderKHR"); +void OpenXRAndroidExtension::on_before_instance_created() { + EXT_INIT_XR_FUNC(xrInitializeLoaderKHR); - // TODO fix this code, this is still code from GDNative! - JNIEnv *env = android_api->godot_android_get_env(); + JNIEnv *env = get_jni_env(); JavaVM *vm; env->GetJavaVM(&vm); - jobject activity_object = env->NewGlobalRef(android_api->godot_android_get_activity()); + jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); XrLoaderInitInfoAndroidKHR loader_init_info_android = { .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, @@ -62,7 +64,7 @@ OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : .applicationVM = vm, .applicationContext = activity_object }; - xrInitializeLoaderKHR((const XrLoaderInitInfoBaseHeaderKHR *)&loader_init_info_android); + XrResult result = xrInitializeLoaderKHR((const XrLoaderInitInfoBaseHeaderKHR *)&loader_init_info_android); ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR"); } diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h index 88b0e310e7..eda7022064 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -31,6 +31,7 @@ #ifndef OPENXR_ANDROID_EXTENSION_H #define OPENXR_ANDROID_EXTENSION_H +#include "../util.h" #include "openxr_extension_wrapper.h" class OpenXRAndroidExtension : public OpenXRExtensionWrapper { @@ -38,10 +39,16 @@ public: static OpenXRAndroidExtension *get_singleton(); OpenXRAndroidExtension(OpenXRAPI *p_openxr_api); + + virtual void on_before_instance_created() override; + virtual ~OpenXRAndroidExtension() override; private: static OpenXRAndroidExtension *singleton; + + // Initialize the loader + EXT_PROTO_XRRESULT_FUNC1(xrInitializeLoaderKHR, (const XrLoaderInitInfoBaseHeaderKHR *), loaderInitInfo) }; #endif // OPENXR_ANDROID_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp new file mode 100644 index 0000000000..0f6aaf8afb --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* openxr_composition_layer_depth_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_composition_layer_depth_extension.h" + +OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::singleton = nullptr; + +OpenXRCompositionLayerDepthExtension *OpenXRCompositionLayerDepthExtension::get_singleton() { + return singleton; +} + +OpenXRCompositionLayerDepthExtension::OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME] = &available; +} + +OpenXRCompositionLayerDepthExtension::~OpenXRCompositionLayerDepthExtension() { + singleton = nullptr; +} + +bool OpenXRCompositionLayerDepthExtension::is_available() { + return available; +} + +XrCompositionLayerBaseHeader *OpenXRCompositionLayerDepthExtension::get_composition_layer() { + // Seems this is all done in our base layer... Just in case this changes... + + return nullptr; +} diff --git a/modules/openxr/extensions/openxr_composition_layer_depth_extension.h b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h new file mode 100644 index 0000000000..9533783d83 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_depth_extension.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* openxr_composition_layer_depth_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_COMPOSITION_LAYER_DEPTH_EXTENSION_H +#define OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H + +#include "openxr_composition_layer_provider.h" +#include "openxr_extension_wrapper.h" + +class OpenXRCompositionLayerDepthExtension : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { +public: + static OpenXRCompositionLayerDepthExtension *get_singleton(); + + OpenXRCompositionLayerDepthExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRCompositionLayerDepthExtension() override; + + bool is_available(); + virtual XrCompositionLayerBaseHeader *get_composition_layer() override; + +private: + static OpenXRCompositionLayerDepthExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_COMPOSITION_LAYER_DEPTH_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h index 019dffa2a8..a4c4cbe0c6 100644 --- a/modules/openxr/extensions/openxr_composition_layer_provider.h +++ b/modules/openxr/extensions/openxr_composition_layer_provider.h @@ -31,15 +31,15 @@ #ifndef OPENXR_COMPOSITION_LAYER_PROVIDER_H #define OPENXR_COMPOSITION_LAYER_PROVIDER_H +#include "openxr_extension_wrapper.h" #include <openxr/openxr.h> // Interface for OpenXR extensions that provide a composition layer. class OpenXRCompositionLayerProvider { public: - // TODO changed to normal method definition for now - // CI complains until we implement this, haven't ported it yet from plugin - // virtual XrCompositionLayerBaseHeader *get_composition_layer() = 0; - XrCompositionLayerBaseHeader *get_composition_layer() { return nullptr; }; + virtual XrCompositionLayerBaseHeader *get_composition_layer() = 0; + + virtual ~OpenXRCompositionLayerProvider() {} }; #endif // OPENXR_COMPOSITION_LAYER_PROVIDER_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index ecc6e0dd4e..c417c90d11 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -66,6 +66,7 @@ public: virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void on_before_instance_created() {} virtual void on_instance_created(const XrInstance p_instance) {} virtual void on_instance_destroyed() {} virtual void on_session_created(const XrSession p_instance) {} @@ -87,6 +88,11 @@ public: return false; } + // Return false if this extension is responsible for this path but the path is not enabled + virtual bool is_path_supported(const String &p_path) { + return true; + } + OpenXRExtensionWrapper(OpenXRAPI *p_openxr_api) { openxr_api = p_openxr_api; }; virtual ~OpenXRExtensionWrapper() = default; }; @@ -94,11 +100,12 @@ public: class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper { public: virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) = 0; + virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) = 0; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) = 0; - virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0; + virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) = 0; OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api){}; diff --git a/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp new file mode 100644 index 0000000000..c0bbaea5b4 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.cpp @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* openxr_fb_display_refresh_rate_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_fb_display_refresh_rate_extension.h" + +OpenXRDisplayRefreshRateExtension *OpenXRDisplayRefreshRateExtension::singleton = nullptr; + +OpenXRDisplayRefreshRateExtension *OpenXRDisplayRefreshRateExtension::get_singleton() { + return singleton; +} + +OpenXRDisplayRefreshRateExtension::OpenXRDisplayRefreshRateExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + // Extensions we use for our hand tracking. + request_extensions[XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME] = &display_refresh_rate_ext; +} + +OpenXRDisplayRefreshRateExtension::~OpenXRDisplayRefreshRateExtension() { + display_refresh_rate_ext = false; +} + +void OpenXRDisplayRefreshRateExtension::on_instance_created(const XrInstance p_instance) { + if (display_refresh_rate_ext) { + EXT_INIT_XR_FUNC(xrEnumerateDisplayRefreshRatesFB); + EXT_INIT_XR_FUNC(xrGetDisplayRefreshRateFB); + EXT_INIT_XR_FUNC(xrRequestDisplayRefreshRateFB); + } +} + +void OpenXRDisplayRefreshRateExtension::on_instance_destroyed() { + display_refresh_rate_ext = false; +} + +float OpenXRDisplayRefreshRateExtension::get_refresh_rate() const { + float refresh_rate = 0.0; + + if (display_refresh_rate_ext) { + float rate; + XrResult result = xrGetDisplayRefreshRateFB(openxr_api->get_session(), &rate); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rate [", openxr_api->get_error_string(result), "]"); + } else { + refresh_rate = rate; + } + } + + return refresh_rate; +} + +void OpenXRDisplayRefreshRateExtension::set_refresh_rate(float p_refresh_rate) { + if (display_refresh_rate_ext) { + XrResult result = xrRequestDisplayRefreshRateFB(openxr_api->get_session(), p_refresh_rate); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to set refresh rate [", openxr_api->get_error_string(result), "]"); + } + } +} + +Array OpenXRDisplayRefreshRateExtension::get_available_refresh_rates() const { + Array arr; + XrResult result; + + if (display_refresh_rate_ext) { + uint32_t display_refresh_rate_count = 0; + result = xrEnumerateDisplayRefreshRatesFB(openxr_api->get_session(), 0, &display_refresh_rate_count, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rates count [", openxr_api->get_error_string(result), "]"); + } + + if (display_refresh_rate_count > 0) { + float *display_refresh_rates = (float *)memalloc(sizeof(float) * display_refresh_rate_count); + if (display_refresh_rates == nullptr) { + print_line("OpenXR: Failed to obtain refresh rates memory buffer [", openxr_api->get_error_string(result), "]"); + return arr; + } + + result = xrEnumerateDisplayRefreshRatesFB(openxr_api->get_session(), display_refresh_rate_count, &display_refresh_rate_count, display_refresh_rates); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain refresh rates count [", openxr_api->get_error_string(result), "]"); + memfree(display_refresh_rates); + return arr; + } + + for (uint32_t i = 0; i < display_refresh_rate_count; i++) { + float refresh_rate = display_refresh_rates[i]; + arr.push_back(Variant(refresh_rate)); + } + + memfree(display_refresh_rates); + } + } + + return arr; +} diff --git a/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h new file mode 100644 index 0000000000..dcd52fe4d1 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_display_refresh_rate_extension.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* openxr_fb_display_refresh_rate_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_FB_DISPLAY_REFRESH_RATE_EXTENSION_H +#define OPENXR_FB_DISPLAY_REFRESH_RATE_EXTENSION_H + +// This extension gives us access to the possible display refresh rates +// supported by the HMD. +// While this is an FB extension it has been adopted by most runtimes and +// will likely become core in the near future. + +#include "../openxr_api.h" +#include "../util.h" + +#include "openxr_extension_wrapper.h" + +class OpenXRDisplayRefreshRateExtension : public OpenXRExtensionWrapper { +public: + static OpenXRDisplayRefreshRateExtension *get_singleton(); + + OpenXRDisplayRefreshRateExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRDisplayRefreshRateExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + float get_refresh_rate() const; + void set_refresh_rate(float p_refresh_rate); + + Array get_available_refresh_rates() const; + +private: + static OpenXRDisplayRefreshRateExtension *singleton; + + bool display_refresh_rate_ext = false; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateDisplayRefreshRatesFB, (XrSession), session, (uint32_t), displayRefreshRateCapacityInput, (uint32_t *), displayRefreshRateCountOutput, (float *), displayRefreshRates); + EXT_PROTO_XRRESULT_FUNC2(xrGetDisplayRefreshRateFB, (XrSession), session, (float *), display_refresh_rate); + EXT_PROTO_XRRESULT_FUNC2(xrRequestDisplayRefreshRateFB, (XrSession), session, (float), display_refresh_rate); +}; + +#endif // OPENXR_FB_DISPLAY_REFRESH_RATE_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp new file mode 100644 index 0000000000..259b1236a3 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.cpp @@ -0,0 +1,234 @@ +/*************************************************************************/ +/* openxr_fb_passthrough_extension_wrapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_fb_passthrough_extension_wrapper.h" + +#include "core/os/os.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" + +using namespace godot; + +OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::singleton = nullptr; + +OpenXRFbPassthroughExtensionWrapper *OpenXRFbPassthroughExtensionWrapper::get_singleton() { + return singleton; +} + +OpenXRFbPassthroughExtensionWrapper::OpenXRFbPassthroughExtensionWrapper(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + request_extensions[XR_FB_PASSTHROUGH_EXTENSION_NAME] = &fb_passthrough_ext; + request_extensions[XR_FB_TRIANGLE_MESH_EXTENSION_NAME] = &fb_triangle_mesh_ext; + singleton = this; +} + +OpenXRFbPassthroughExtensionWrapper::~OpenXRFbPassthroughExtensionWrapper() { + cleanup(); +} + +void OpenXRFbPassthroughExtensionWrapper::cleanup() { + fb_passthrough_ext = false; + fb_triangle_mesh_ext = false; +} + +Viewport *OpenXRFbPassthroughExtensionWrapper::get_main_viewport() { + MainLoop *main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) { + print_error("Unable to retrieve main loop"); + return nullptr; + } + + auto *scene_tree = Object::cast_to<SceneTree>(main_loop); + if (!scene_tree) { + print_error("Unable to retrieve scene tree"); + return nullptr; + } + + Viewport *viewport = scene_tree->get_root()->get_viewport(); + return viewport; +} + +void OpenXRFbPassthroughExtensionWrapper::on_instance_created(const XrInstance instance) { + if (fb_passthrough_ext) { + bool result = initialize_fb_passthrough_extension(instance); + if (!result) { + print_error("Failed to initialize fb_passthrough extension"); + fb_passthrough_ext = false; + } + } + + if (fb_triangle_mesh_ext) { + bool result = initialize_fb_triangle_mesh_extension(instance); + if (!result) { + print_error("Failed to initialize fb_triangle_mesh extension"); + fb_triangle_mesh_ext = false; + } + } + + if (fb_passthrough_ext) { + openxr_api->register_composition_layer_provider(this); + } +} + +bool OpenXRFbPassthroughExtensionWrapper::is_passthrough_enabled() { + return fb_passthrough_ext && passthrough_handle != XR_NULL_HANDLE && passthrough_layer != XR_NULL_HANDLE; +} + +bool OpenXRFbPassthroughExtensionWrapper::is_composition_passthrough_layer_ready() { + return fb_passthrough_ext && passthrough_handle != XR_NULL_HANDLE && composition_passthrough_layer.layerHandle != XR_NULL_HANDLE; +} + +bool OpenXRFbPassthroughExtensionWrapper::start_passthrough() { + if (passthrough_handle == XR_NULL_HANDLE) { + return false; + } + + if (is_passthrough_enabled()) { + return true; + } + + // Start the passthrough feature + XrResult result = xrPassthroughStartFB(passthrough_handle); + if (!is_valid_passthrough_result(result, "Failed to start passthrough")) { + stop_passthrough(); + return false; + } + + // Create the passthrough layer + result = xrCreatePassthroughLayerFB(openxr_api->get_session(), &passthrough_layer_config, &passthrough_layer); + if (!is_valid_passthrough_result(result, "Failed to create the passthrough layer")) { + stop_passthrough(); + return false; + } + + // Check if the the viewport has transparent background + Viewport *viewport = get_main_viewport(); + if (viewport && !viewport->has_transparent_background()) { + print_error("Main viewport doesn't have transparent background! Passthrough may not properly render."); + } + + composition_passthrough_layer.layerHandle = passthrough_layer; + + return true; +} + +void OpenXRFbPassthroughExtensionWrapper::on_session_created(const XrSession session) { + if (fb_passthrough_ext) { + // Create the passthrough feature and start it. + XrResult result = xrCreatePassthroughFB(openxr_api->get_session(), &passthrough_create_info, &passthrough_handle); + if (!openxr_api->xr_result(result, "Failed to create passthrough")) { + passthrough_handle = XR_NULL_HANDLE; + return; + } + } +} + +XrCompositionLayerBaseHeader *OpenXRFbPassthroughExtensionWrapper::get_composition_layer() { + if (is_composition_passthrough_layer_ready()) { + return (XrCompositionLayerBaseHeader *)&composition_passthrough_layer; + } else { + return nullptr; + } +} + +void OpenXRFbPassthroughExtensionWrapper::stop_passthrough() { + if (!fb_passthrough_ext) { + return; + } + + composition_passthrough_layer.layerHandle = XR_NULL_HANDLE; + + XrResult result; + if (passthrough_layer != XR_NULL_HANDLE) { + // Destroy the layer + result = xrDestroyPassthroughLayerFB(passthrough_layer); + openxr_api->xr_result(result, "Unable to destroy passthrough layer"); + passthrough_layer = XR_NULL_HANDLE; + } + + if (passthrough_handle != XR_NULL_HANDLE) { + result = xrPassthroughPauseFB(passthrough_handle); + openxr_api->xr_result(result, "Unable to stop passthrough feature"); + } +} + +void OpenXRFbPassthroughExtensionWrapper::on_session_destroyed() { + if (fb_passthrough_ext) { + stop_passthrough(); + + XrResult result; + if (passthrough_handle != XR_NULL_HANDLE) { + result = xrDestroyPassthroughFB(passthrough_handle); + openxr_api->xr_result(result, "Unable to destroy passthrough feature"); + passthrough_handle = XR_NULL_HANDLE; + } + } +} + +void OpenXRFbPassthroughExtensionWrapper::on_instance_destroyed() { + if (fb_passthrough_ext) { + openxr_api->unregister_composition_layer_provider(this); + } + cleanup(); +} + +bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_passthrough_extension(const XrInstance p_instance) { + ERR_FAIL_NULL_V(openxr_api, false); + + EXT_INIT_XR_FUNC_V(xrCreatePassthroughFB); + EXT_INIT_XR_FUNC_V(xrDestroyPassthroughFB); + EXT_INIT_XR_FUNC_V(xrPassthroughStartFB); + EXT_INIT_XR_FUNC_V(xrPassthroughPauseFB); + EXT_INIT_XR_FUNC_V(xrCreatePassthroughLayerFB); + EXT_INIT_XR_FUNC_V(xrDestroyPassthroughLayerFB); + EXT_INIT_XR_FUNC_V(xrPassthroughLayerPauseFB); + EXT_INIT_XR_FUNC_V(xrPassthroughLayerResumeFB); + EXT_INIT_XR_FUNC_V(xrPassthroughLayerSetStyleFB); + EXT_INIT_XR_FUNC_V(xrCreateGeometryInstanceFB); + EXT_INIT_XR_FUNC_V(xrDestroyGeometryInstanceFB); + EXT_INIT_XR_FUNC_V(xrGeometryInstanceSetTransformFB); + + return true; +} + +bool OpenXRFbPassthroughExtensionWrapper::initialize_fb_triangle_mesh_extension(const XrInstance p_instance) { + ERR_FAIL_NULL_V(openxr_api, false); + + EXT_INIT_XR_FUNC_V(xrCreateTriangleMeshFB); + EXT_INIT_XR_FUNC_V(xrDestroyTriangleMeshFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshGetVertexBufferFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshGetIndexBufferFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginUpdateFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshEndUpdateFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshBeginVertexBufferUpdateFB); + EXT_INIT_XR_FUNC_V(xrTriangleMeshEndVertexBufferUpdateFB); + + return true; +} diff --git a/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h new file mode 100644 index 0000000000..1959f3fdc4 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_passthrough_extension_wrapper.h @@ -0,0 +1,254 @@ +/*************************************************************************/ +/* openxr_fb_passthrough_extension_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_FB_PASSTHROUGH_EXTENSION_WRAPPER_H +#define OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H + +#include "../openxr_api.h" +#include "../util.h" + +#include "openxr_composition_layer_provider.h" +#include "openxr_extension_wrapper.h" + +#include <map> + +class Viewport; + +// Wrapper for the set of Facebook XR passthrough extensions. +class OpenXRFbPassthroughExtensionWrapper : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { + friend class OpenXRAPI; + +public: + void on_instance_created(const XrInstance instance) override; + + void on_session_created(const XrSession session) override; + + void on_session_destroyed() override; + + void on_instance_destroyed() override; + + XrCompositionLayerBaseHeader *get_composition_layer() override; + + bool is_passthrough_supported() { + return fb_passthrough_ext; + } + + bool is_passthrough_enabled(); + + bool start_passthrough(); + + void stop_passthrough(); + + static OpenXRFbPassthroughExtensionWrapper *get_singleton(); + +protected: + OpenXRFbPassthroughExtensionWrapper(OpenXRAPI *p_openxr_api); + ~OpenXRFbPassthroughExtensionWrapper(); + +private: + // Create a passthrough feature + EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughFB, + (XrSession), session, + (const XrPassthroughCreateInfoFB *), create_info, + (XrPassthroughFB *), feature_out) + + // Destroy a previously created passthrough feature + EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughFB, (XrPassthroughFB), feature) + + //*** Passthrough feature state management functions ********* + // Start the passthrough feature + EXT_PROTO_XRRESULT_FUNC1(xrPassthroughStartFB, (XrPassthroughFB), passthrough) + // Pause the passthrough feature + EXT_PROTO_XRRESULT_FUNC1(xrPassthroughPauseFB, (XrPassthroughFB), passthrough) + + EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughLayerFB, (XrSession), session, + (const XrPassthroughLayerCreateInfoFB *), config, + (XrPassthroughLayerFB *), layer_out) + + EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughLayerFB, (XrPassthroughLayerFB), layer) + + EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerPauseFB, (XrPassthroughLayerFB), layer) + EXT_PROTO_XRRESULT_FUNC1(xrPassthroughLayerResumeFB, (XrPassthroughLayerFB), layer) + + // Set the style of an existing passthrough layer. If the enabled feature set + // doesn’t change, this is a lightweight operation that can be called in every + // frame to animate the style. Changes that may incur a bigger cost: + // - Enabling/disabling the color mapping, or changing the type of mapping + // (monochromatic to RGBA or back). + // - Changing `textureOpacityFactor` from 0 to non-zero or vice versa + // - Changing `edgeColor[3]` from 0 to non-zero or vice versa + // NOTE: For XR_FB_passthrough, all color values are treated as linear. + EXT_PROTO_XRRESULT_FUNC2(xrPassthroughLayerSetStyleFB, + (XrPassthroughLayerFB), layer, + (const XrPassthroughStyleFB *), style) + + // Create a geometry instance to be used as a projection surface for passthrough. + // A geometry instance assigns a triangle mesh as part of the specified layer's + // projection surface. + // The operation is only valid if the passthrough layer's purpose has been set to + // `XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB`. Otherwise, the call this function will + // result in an error. In the specified layer, Passthrough will be visible where the view + // is covered by the user-specified geometries. + // + // A triangle mesh object can be instantiated multiple times - in the same or different layers' + // projection surface. Each instantiation has its own transformation, which + // can be updated using `xrGeometryInstanceSetTransformFB`. + EXT_PROTO_XRRESULT_FUNC3(xrCreateGeometryInstanceFB, + (XrSession), session, + (const XrGeometryInstanceCreateInfoFB *), create_info, + (XrGeometryInstanceFB *), out_geometry_instance) + + // Destroys a previously created geometry instance from passthrough rendering. + // This removes the geometry instance from passthrough rendering. + // The operation has no effect on other instances or the underlying mesh. + EXT_PROTO_XRRESULT_FUNC1(xrDestroyGeometryInstanceFB, (XrGeometryInstanceFB), instance) + + // Update the transformation of a passthrough geometry instance. + EXT_PROTO_XRRESULT_FUNC2(xrGeometryInstanceSetTransformFB, + (XrGeometryInstanceFB), instance, + (const XrGeometryInstanceTransformFB *), transformation) + + // Create a triangle mesh geometry object. + // Depending on the behavior flags, the mesh could be created immutable (data is assigned + // at creation and cannot be changed) or mutable (the mesh is created empty and can be updated + // by calling begin/end update functions). + EXT_PROTO_XRRESULT_FUNC3(xrCreateTriangleMeshFB, + (XrSession), session, + (const XrTriangleMeshCreateInfoFB *), create_info, + (XrTriangleMeshFB *), out_triangle_mesh) + + // Destroy an `XrTriangleMeshFB` object along with its data. The mesh buffers must not be + // accessed anymore after their parent mesh object has been destroyed. + EXT_PROTO_XRRESULT_FUNC1(xrDestroyTriangleMeshFB, (XrTriangleMeshFB), mesh) + + // Retrieve a pointer to the vertex buffer. The vertex buffer is structured as an array of 3 floats + // per vertex representing x, y, and z: `[x0, y0, z0, x1, y1, z1, ...]`. The size of the buffer is + // `maxVertexCount * 3` floats. The application must call `xrTriangleMeshBeginUpdateFB` or + // `xrTriangleMeshBeginVertexBufferUpdateFB` before making modifications to the vertex + // buffer. The buffer location is guaranteed to remain constant over the lifecycle of the mesh + // object. + EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetVertexBufferFB, + (XrTriangleMeshFB), mesh, + (XrVector3f **), out_vertex_buffer) + + // Retrieve the index buffer that defines the topology of the triangle mesh. Each triplet of + // consecutive elements point to three vertices in the vertex buffer and thus form a triangle. The + // size of each element is `indexElementSize` bytes, and thus the size of the buffer is + // `maxTriangleCount * 3 * indexElementSize` bytes. The application must call + // `xrTriangleMeshBeginUpdateFB` before making modifications to the index buffer. The buffer + // location is guaranteed to remain constant over the lifecycle of the mesh object. + EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshGetIndexBufferFB, + (XrTriangleMeshFB), mesh, + (uint32_t **), out_index_buffer) + + // Begin updating the mesh buffer data. The application must call this function before it makes any + // modifications to the buffers retrieved by `xrTriangleMeshGetVertexBufferFB` and + // `xrTriangleMeshGetIndexBufferFB`. If only the vertex buffer needs to be updated, + // `xrTriangleMeshBeginVertexBufferUpdateFB` can be used instead. To commit the + // modifications, the application must call `xrTriangleMeshEndUpdateFB`. + EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshBeginUpdateFB, (XrTriangleMeshFB), mesh) + + // Signal the API that the application has finished updating the mesh buffers after a call to + // `xrTriangleMeshBeginUpdateFB`. `vertexCount` and `triangleCount` specify the actual + // number of primitives that make up the mesh after the update. They must be larger than zero but + // smaller or equal to the maximum counts defined at create time. Buffer data beyond these counts + // is ignored. + EXT_PROTO_XRRESULT_FUNC3(xrTriangleMeshEndUpdateFB, + (XrTriangleMeshFB), mesh, + (uint32_t), vertexCount, + (uint32_t), triangle_count) + + // Update the vertex positions of a triangle mesh. Can only be called once the mesh topology has + // been set using `xrTriangleMeshBeginUpdateFB`/`xrTriangleMeshEndUpdateFB`. The + // vertex count is defined by the last invocation to `xrTriangleMeshEndUpdateFB`. Once the + // modification is done, `xrTriangleMeshEndVertexBufferUpdateFB` must be called. + EXT_PROTO_XRRESULT_FUNC2(xrTriangleMeshBeginVertexBufferUpdateFB, + (XrTriangleMeshFB), mesh, + (uint32_t *), out_vertex_count) + + // Signal the API that the contents of the vertex buffer data has been updated + // after a call to `xrTriangleMeshBeginVertexBufferUpdateFB`. + EXT_PROTO_XRRESULT_FUNC1(xrTriangleMeshEndVertexBufferUpdateFB, (XrTriangleMeshFB), mesh) + + bool initialize_fb_passthrough_extension(const XrInstance instance); + + bool initialize_fb_triangle_mesh_extension(const XrInstance instance); + + void cleanup(); + + // TODO: Temporary workaround (https://github.com/GodotVR/godot_openxr/issues/138) + // Address a bug in the passthrough api where XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB is + // returned even when the operation is valid on Meta Quest devices. + // The issue should be addressed on that platform in OS release v37. + inline bool is_valid_passthrough_result(XrResult result, const char *format) { + return openxr_api->xr_result(result, format) || result == XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB; + } + + Viewport *get_main_viewport(); + + bool is_composition_passthrough_layer_ready(); + + static OpenXRFbPassthroughExtensionWrapper *singleton; + + bool fb_passthrough_ext = false; // required for any passthrough functionality + bool fb_triangle_mesh_ext = false; // only use for projected passthrough + + XrPassthroughCreateInfoFB passthrough_create_info = { + XR_TYPE_PASSTHROUGH_CREATE_INFO_FB, + nullptr, + 0, + }; + XrPassthroughFB passthrough_handle = XR_NULL_HANDLE; + + XrPassthroughLayerCreateInfoFB passthrough_layer_config = { + XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB, + nullptr, + passthrough_handle, + XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB, + XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB, + }; + XrPassthroughStyleFB passthrough_layer_style = { + XR_TYPE_PASSTHROUGH_STYLE_FB, + nullptr, + 1, + { 0, 0, 0, 0 }, + }; + XrPassthroughLayerFB passthrough_layer = XR_NULL_HANDLE; + + XrCompositionLayerPassthroughFB composition_passthrough_layer = { + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB, + nullptr, + XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT, + XR_NULL_HANDLE, + XR_NULL_HANDLE, + }; +}; + +#endif // OPENXR_FB_PASSTHROUGH_EXTENSION_WRAPPER_H diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp new file mode 100644 index 0000000000..85e2ee4903 --- /dev/null +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -0,0 +1,268 @@ +/*************************************************************************/ +/* openxr_hand_tracking_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_tracking_extension.h" +#include "../openxr_api.h" +#include "core/string/print_string.h" +#include "servers/xr_server.h" + +#include <openxr/openxr.h> + +OpenXRHandTrackingExtension *OpenXRHandTrackingExtension::singleton = nullptr; + +OpenXRHandTrackingExtension *OpenXRHandTrackingExtension::get_singleton() { + return singleton; +} + +OpenXRHandTrackingExtension::OpenXRHandTrackingExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + // Extensions we use for our hand tracking. + request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext; + request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext; + request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &hand_tracking_aim_state_ext; + + // Make sure this is cleared until we actually request it + handTrackingSystemProperties.supportsHandTracking = false; +} + +OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() { + singleton = nullptr; +} + +void OpenXRHandTrackingExtension::on_instance_created(const XrInstance p_instance) { + if (hand_tracking_ext) { + EXT_INIT_XR_FUNC(xrCreateHandTrackerEXT); + EXT_INIT_XR_FUNC(xrDestroyHandTrackerEXT); + EXT_INIT_XR_FUNC(xrLocateHandJointsEXT); + + hand_tracking_ext = xrCreateHandTrackerEXT_ptr && xrDestroyHandTrackerEXT_ptr && xrLocateHandJointsEXT_ptr; + } +} + +void OpenXRHandTrackingExtension::on_session_destroyed() { + cleanup_hand_tracking(); +} + +void OpenXRHandTrackingExtension::on_instance_destroyed() { + xrCreateHandTrackerEXT_ptr = nullptr; + xrDestroyHandTrackerEXT_ptr = nullptr; + xrLocateHandJointsEXT_ptr = nullptr; +} + +void *OpenXRHandTrackingExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) { + if (!hand_tracking_ext) { + // not supported... + return p_next_pointer; + } + + handTrackingSystemProperties = { + XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT, // type + p_next_pointer, // next + false, // supportsHandTracking + }; + + return &handTrackingSystemProperties; +} + +void OpenXRHandTrackingExtension::on_state_ready() { + if (!handTrackingSystemProperties.supportsHandTracking) { + // not supported... + return; + } + + // Setup our hands and reset data + for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + // we'll do this later + hand_trackers[i].is_initialized = false; + hand_trackers[i].hand_tracker = XR_NULL_HANDLE; + + hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; + hand_trackers[i].aimState.pinchStrengthIndex = 0.0; + hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; + hand_trackers[i].aimState.pinchStrengthRing = 0.0; + hand_trackers[i].aimState.pinchStrengthLittle = 0.0; + + hand_trackers[i].locations.isActive = false; + + for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; j++) { + hand_trackers[i].joint_locations[j] = { 0, { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }, 0.0 }; + hand_trackers[i].joint_velocities[j] = { 0, { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; + } + } +} + +void OpenXRHandTrackingExtension::on_process() { + if (!handTrackingSystemProperties.supportsHandTracking) { + // not supported... + return; + } + + // process our hands + const XrTime time = openxr_api->get_next_frame_time(); // This data will be used for the next frame we render + + XrResult result; + + for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) { + XrHandTrackerCreateInfoEXT createInfo = { + XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type + nullptr, // next + i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, // hand + XR_HAND_JOINT_SET_DEFAULT_EXT, // handJointSet + }; + + result = xrCreateHandTrackerEXT(openxr_api->get_session(), &createInfo, &hand_trackers[i].hand_tracker); + if (XR_FAILED(result)) { + // not successful? then we do nothing. + print_line("OpenXR: Failed to obtain hand tracking information [", openxr_api->get_error_string(result), "]"); + hand_trackers[i].is_initialized = false; + } else { + void *next_pointer = nullptr; + if (hand_tracking_aim_state_ext) { + hand_trackers[i].aimState.type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB; + hand_trackers[i].aimState.next = next_pointer; + hand_trackers[i].aimState.status = 0; + hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; + hand_trackers[i].aimState.pinchStrengthIndex = 0.0; + hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; + hand_trackers[i].aimState.pinchStrengthRing = 0.0; + hand_trackers[i].aimState.pinchStrengthLittle = 0.0; + + next_pointer = &hand_trackers[i].aimState; + } + + hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT; + hand_trackers[i].velocities.next = next_pointer; + hand_trackers[i].velocities.jointCount = XR_HAND_JOINT_COUNT_EXT; + hand_trackers[i].velocities.jointVelocities = hand_trackers[i].joint_velocities; + next_pointer = &hand_trackers[i].velocities; + + hand_trackers[i].locations.type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT; + hand_trackers[i].locations.next = next_pointer; + hand_trackers[i].locations.isActive = false; + hand_trackers[i].locations.jointCount = XR_HAND_JOINT_COUNT_EXT; + hand_trackers[i].locations.jointLocations = hand_trackers[i].joint_locations; + + hand_trackers[i].is_initialized = true; + } + } + + if (hand_trackers[i].is_initialized) { + void *next_pointer = nullptr; + + XrHandJointsMotionRangeInfoEXT motionRangeInfo; + + if (hand_motion_range_ext) { + motionRangeInfo.type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT; + motionRangeInfo.next = next_pointer; + motionRangeInfo.handJointsMotionRange = hand_trackers[i].motion_range; + + next_pointer = &motionRangeInfo; + } + + XrHandJointsLocateInfoEXT locateInfo = { + XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, // type + next_pointer, // next + openxr_api->get_play_space(), // baseSpace + time, // time + }; + + result = xrLocateHandJointsEXT(hand_trackers[i].hand_tracker, &locateInfo, &hand_trackers[i].locations); + if (XR_FAILED(result)) { + // not successful? then we do nothing. + print_line("OpenXR: Failed to get tracking for hand", i, "[", openxr_api->get_error_string(result), "]"); + continue; + } + + // For some reason an inactive controller isn't coming back as inactive but has coordinates either as NAN or very large + const XrPosef &palm = hand_trackers[i].joint_locations[XR_HAND_JOINT_PALM_EXT].pose; + if ( + !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { + hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive + } + + /* TODO change this to managing the controller from openxr_interface + if (hand_tracking_aim_state_ext && hand_trackers[i].locations.isActive && check_bit(XR_HAND_TRACKING_AIM_VALID_BIT_FB, hand_trackers[i].aimState.status)) { + // Controllers are updated based on the aim state's pose and pinches' strength + if (hand_trackers[i].aim_state_godot_controller == -1) { + hand_trackers[i].aim_state_godot_controller = + arvr_api->godot_arvr_add_controller( + const_cast<char *>(hand_controller_names[i]), + i + HAND_CONTROLLER_ID_OFFSET, + true, + true); + } + } + */ + } + } +} + +void OpenXRHandTrackingExtension::on_state_stopping() { + // cleanup + cleanup_hand_tracking(); +} + +void OpenXRHandTrackingExtension::cleanup_hand_tracking() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + + for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) { + xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker); + + hand_trackers[i].is_initialized = false; + hand_trackers[i].hand_tracker = XR_NULL_HANDLE; + } + } +} + +bool OpenXRHandTrackingExtension::get_active() { + return handTrackingSystemProperties.supportsHandTracking; +} + +const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(uint32_t p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, nullptr); + + return &hand_trackers[p_hand]; +} + +XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(uint32_t p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT); + + return hand_trackers[p_hand].motion_range; +} + +void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range) { + ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS); + hand_trackers[p_hand].motion_range = p_motion_range; +} diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h new file mode 100644 index 0000000000..0eca80bcfb --- /dev/null +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* openxr_hand_tracking_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_TRACKING_EXTENSION_H +#define OPENXR_HAND_TRACKING_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +#include "../util.h" + +#define MAX_OPENXR_TRACKED_HANDS 2 + +class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper { +public: + struct HandTracker { + bool is_initialized = false; + XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; + + XrHandTrackerEXT hand_tracker = XR_NULL_HANDLE; + XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; + XrHandJointVelocityEXT joint_velocities[XR_HAND_JOINT_COUNT_EXT]; + + XrHandTrackingAimStateFB aimState; + XrHandJointVelocitiesEXT velocities; + XrHandJointLocationsEXT locations; + }; + + static OpenXRHandTrackingExtension *get_singleton(); + + OpenXRHandTrackingExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRHandTrackingExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + virtual void on_session_destroyed() override; + + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override; + virtual void on_state_ready() override; + virtual void on_process() override; + virtual void on_state_stopping() override; + + bool get_active(); + const HandTracker *get_hand_tracker(uint32_t p_hand) const; + + XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const; + void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range); + +private: + static OpenXRHandTrackingExtension *singleton; + + // state + XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties; + HandTracker hand_trackers[MAX_OPENXR_TRACKED_HANDS]; // Fixed for left and right hand + + // related extensions + bool hand_tracking_ext = false; + bool hand_motion_range_ext = false; + bool hand_tracking_aim_state_ext = false; + + // functions + void cleanup_hand_tracking(); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateHandTrackerEXT, (XrSession), p_session, (const XrHandTrackerCreateInfoEXT *), p_createInfo, (XrHandTrackerEXT *), p_handTracker) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyHandTrackerEXT, (XrHandTrackerEXT), p_handTracker) + EXT_PROTO_XRRESULT_FUNC3(xrLocateHandJointsEXT, (XrHandTrackerEXT), p_handTracker, (const XrHandJointsLocateInfoEXT *), p_locateInfo, (XrHandJointLocationsEXT *), p_locations) +}; + +#endif // OPENXR_HAND_TRACKING_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp index 302acf4e30..4d996e6283 100644 --- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp @@ -65,3 +65,40 @@ bool OpenXRHTCViveTrackerExtension::on_event_polled(const XrEventDataBuffer &eve } break; } } + +bool OpenXRHTCViveTrackerExtension::is_path_supported(const String &p_path) { + if (p_path == "/interaction_profiles/htc/vive_tracker_htcx") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/handheld_object") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_foot") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_foot") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_shoulder") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_shoulder") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_elbow") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_elbow") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/left_knee") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/right_knee") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/waist") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/chest") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/chest") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/camera") { + return available; + } else if (p_path == "/user/vive_tracker_htcx/role/keyboard") { + return available; + } + + // Not a path under this extensions control, so we return true; + return true; +} diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h index 7f37351f27..ab8e8535f1 100644 --- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h @@ -41,7 +41,9 @@ public: virtual ~OpenXRHTCViveTrackerExtension() override; bool is_available(); + virtual bool on_event_polled(const XrEventDataBuffer &event) override; + virtual bool is_path_supported(const String &p_path) override; private: static OpenXRHTCViveTrackerExtension *singleton; diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp new file mode 100644 index 0000000000..ee69144123 --- /dev/null +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -0,0 +1,466 @@ +/*************************************************************************/ +/* openxr_opengl_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef GLES3_ENABLED + +#include "../extensions/openxr_opengl_extension.h" +#include "../openxr_util.h" +#include "drivers/gles3/effects/copy_effects.h" +#include "drivers/gles3/storage/texture_storage.h" +#include "servers/rendering/rendering_server_globals.h" +#include "servers/rendering_server.h" + +OpenXROpenGLExtension::OpenXROpenGLExtension(OpenXRAPI *p_openxr_api) : + OpenXRGraphicsExtensionWrapper(p_openxr_api) { +#ifdef ANDROID_ENABLED + request_extensions[XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME] = nullptr; +#else + request_extensions[XR_KHR_OPENGL_ENABLE_EXTENSION_NAME] = nullptr; +#endif + + ERR_FAIL_NULL(openxr_api); +} + +OpenXROpenGLExtension::~OpenXROpenGLExtension() { +} + +void OpenXROpenGLExtension::on_instance_created(const XrInstance p_instance) { + ERR_FAIL_NULL(openxr_api); + + // Obtain pointers to functions we're accessing here. + +#ifdef ANDROID_ENABLED + EXT_INIT_XR_FUNC(xrGetOpenGLESGraphicsRequirementsKHR); +#else + EXT_INIT_XR_FUNC(xrGetOpenGLGraphicsRequirementsKHR); +#endif + EXT_INIT_XR_FUNC(xrEnumerateSwapchainImages); +} + +bool OpenXROpenGLExtension::check_graphics_api_support(XrVersion p_desired_version) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrSystemId system_id = openxr_api->get_system_id(); + XrInstance instance = openxr_api->get_instance(); + +#ifdef ANDROID_ENABLED + XrGraphicsRequirementsOpenGLESKHR opengl_requirements; + opengl_requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR; + opengl_requirements.next = nullptr; + + XrResult result = xrGetOpenGLESGraphicsRequirementsKHR(instance, system_id, &opengl_requirements); + if (!openxr_api->xr_result(result, "Failed to get OpenGL graphics requirements!")) { + return false; + } +#else + XrGraphicsRequirementsOpenGLKHR opengl_requirements; + opengl_requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR; + opengl_requirements.next = nullptr; + + XrResult result = xrGetOpenGLGraphicsRequirementsKHR(instance, system_id, &opengl_requirements); + if (!openxr_api->xr_result(result, "Failed to get OpenGL graphics requirements!")) { + return false; + } +#endif + + if (p_desired_version < opengl_requirements.minApiVersionSupported) { + print_line("OpenXR: Requested OpenGL version does not meet the minimum version this runtime supports."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(opengl_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(opengl_requirements.maxApiVersionSupported)); + return false; + } + + if (p_desired_version > opengl_requirements.maxApiVersionSupported) { + print_line("OpenXR: Requested OpenGL version exceeds the maximum version this runtime has been tested on and is known to support."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(opengl_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(opengl_requirements.maxApiVersionSupported)); + } + + return true; +} + +#ifdef WIN32 +XrGraphicsBindingOpenGLWin32KHR OpenXROpenGLExtension::graphics_binding_gl; +#elif ANDROID_ENABLED +XrGraphicsBindingOpenGLESAndroidKHR OpenXROpenGLExtension::graphics_binding_gl; +#else +XrGraphicsBindingOpenGLXlibKHR OpenXROpenGLExtension::graphics_binding_gl; +#endif + +void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) { + XrVersion desired_version = XR_MAKE_VERSION(3, 3, 0); + + if (!check_graphics_api_support(desired_version)) { + print_line("OpenXR: Trying to initialize with OpenGL anyway..."); + //return p_next_pointer; + } + + DisplayServer *display_server = DisplayServer::get_singleton(); + +#ifdef WIN32 + graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR, + graphics_binding_gl.next = p_next_pointer; + + graphics_binding_gl.hDC = (HDC)display_server->window_get_native_handle(DisplayServer::WINDOW_VIEW); + graphics_binding_gl.hGLRC = (HGLRC)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT); +#elif ANDROID_ENABLED + graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR; + graphics_binding_gl.next = p_next_pointer; + + graphics_binding_gl.display = eglGetCurrentDisplay(); + graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122 + graphics_binding_gl.context = eglGetCurrentContext(); +#else + graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR; + graphics_binding_gl.next = p_next_pointer; + + void *display_handle = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE); + void *glxcontext_handle = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT); + void *glxdrawable_handle = (void *)display_server->window_get_native_handle(DisplayServer::WINDOW_HANDLE); + + graphics_binding_gl.xDisplay = (Display *)display_handle; + graphics_binding_gl.glxContext = (GLXContext)glxcontext_handle; + graphics_binding_gl.glxDrawable = (GLXDrawable)glxdrawable_handle; + + // spec says to use proper values but runtimes don't care + graphics_binding_gl.visualid = 0; + graphics_binding_gl.glxFBConfig = 0; +#endif + + return &graphics_binding_gl; +} + +void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { +#ifdef WIN32 + p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); + p_usable_swap_chains.push_back(GL_RGBA8); +#elif ANDROID_ENABLED + p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); + p_usable_swap_chains.push_back(GL_RGBA8); +#else + p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8_EXT); + p_usable_swap_chains.push_back(GL_RGBA8_EXT); +#endif +} + +void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_depth_formats) { + p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT32F); + p_usable_depth_formats.push_back(GL_DEPTH24_STENCIL8); + p_usable_depth_formats.push_back(GL_DEPTH32F_STENCIL8); +} + +bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + ERR_FAIL_NULL_V(texture_storage, false); + + uint32_t swapchain_length; + XrResult result = xrEnumerateSwapchainImages(p_swapchain, 0, &swapchain_length, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim image count [", openxr_api->get_error_string(result), "]"); + return false; + } + +#ifdef ANDROID_ENABLED + XrSwapchainImageOpenGLESKHR *images = (XrSwapchainImageOpenGLESKHR *)memalloc(sizeof(XrSwapchainImageOpenGLESKHR) * swapchain_length); +#else + XrSwapchainImageOpenGLKHR *images = (XrSwapchainImageOpenGLKHR *)memalloc(sizeof(XrSwapchainImageOpenGLKHR) * swapchain_length); +#endif + ERR_FAIL_NULL_V_MSG(images, false, "OpenXR Couldn't allocate memory for swap chain image"); + + for (uint64_t i = 0; i < swapchain_length; i++) { +#ifdef ANDROID_ENABLED + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; +#else + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR; +#endif + images[i].next = nullptr; + images[i].image = 0; + } + + result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim images [", openxr_api->get_error_string(result), "]"); + memfree(images); + return false; + } + + SwapchainGraphicsData *data = memnew(SwapchainGraphicsData); + if (data == nullptr) { + print_line("OpenXR: Failed to allocate memory for swapchain data"); + memfree(images); + return false; + } + *r_swapchain_graphics_data = data; + data->is_multiview = (p_array_size > 1); + + Image::Format format = Image::FORMAT_RGBA8; + + Vector<RID> texture_rids; + + for (uint64_t i = 0; i < swapchain_length; i++) { + RID texture_rid = texture_storage->texture_create_external( + p_array_size == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + format, + images[i].image, + p_width, + p_height, + 1, + p_array_size); + + texture_rids.push_back(texture_rid); + } + + data->texture_rids = texture_rids; + + memfree(images); + + return true; +} + +bool OpenXROpenGLExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) { + XrMatrix4x4f matrix; + XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); + + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + r_camera_matrix.columns[j][i] = matrix.m[j * 4 + i]; + } + } + + return true; +} + +RID OpenXROpenGLExtension::get_texture(void *p_swapchain_graphics_data, int p_image_index) { + SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; + ERR_FAIL_NULL_V(data, RID()); + + ERR_FAIL_INDEX_V(p_image_index, data->texture_rids.size(), RID()); + return data->texture_rids[p_image_index]; +} + +void OpenXROpenGLExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { + if (*p_swapchain_graphics_data == nullptr) { + return; + } + + GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); + ERR_FAIL_NULL(texture_storage); + + SwapchainGraphicsData *data = (SwapchainGraphicsData *)*p_swapchain_graphics_data; + + for (int i = 0; i < data->texture_rids.size(); i++) { + texture_storage->texture_free(data->texture_rids[i]); + } + data->texture_rids.clear(); + + memdelete(data); + *p_swapchain_graphics_data = nullptr; +} + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +String OpenXROpenGLExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { + // These are somewhat different per platform, will need to weed some stuff out... + switch (p_swapchain_format) { +#ifdef WIN32 + // using definitions from GLAD + ENUM_TO_STRING_CASE(GL_R8_SNORM) + ENUM_TO_STRING_CASE(GL_RG8_SNORM) + ENUM_TO_STRING_CASE(GL_RGB8_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) + ENUM_TO_STRING_CASE(GL_R16_SNORM) + ENUM_TO_STRING_CASE(GL_RG16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB16_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB4) + ENUM_TO_STRING_CASE(GL_RGB5) + ENUM_TO_STRING_CASE(GL_RGB8) + ENUM_TO_STRING_CASE(GL_RGB10) + ENUM_TO_STRING_CASE(GL_RGB12) + ENUM_TO_STRING_CASE(GL_RGB16) + ENUM_TO_STRING_CASE(GL_RGBA2) + ENUM_TO_STRING_CASE(GL_RGBA4) + ENUM_TO_STRING_CASE(GL_RGB5_A1) + ENUM_TO_STRING_CASE(GL_RGBA8) + ENUM_TO_STRING_CASE(GL_RGB10_A2) + ENUM_TO_STRING_CASE(GL_RGBA12) + ENUM_TO_STRING_CASE(GL_RGBA16) + ENUM_TO_STRING_CASE(GL_RGBA32F) + ENUM_TO_STRING_CASE(GL_RGB32F) + ENUM_TO_STRING_CASE(GL_RGBA16F) + ENUM_TO_STRING_CASE(GL_RGB16F) + ENUM_TO_STRING_CASE(GL_RGBA32UI) + ENUM_TO_STRING_CASE(GL_RGB32UI) + ENUM_TO_STRING_CASE(GL_RGBA16UI) + ENUM_TO_STRING_CASE(GL_RGB16UI) + ENUM_TO_STRING_CASE(GL_RGBA8UI) + ENUM_TO_STRING_CASE(GL_RGB8UI) + ENUM_TO_STRING_CASE(GL_RGBA32I) + ENUM_TO_STRING_CASE(GL_RGB32I) + ENUM_TO_STRING_CASE(GL_RGBA16I) + ENUM_TO_STRING_CASE(GL_RGB16I) + ENUM_TO_STRING_CASE(GL_RGBA8I) + ENUM_TO_STRING_CASE(GL_RGB8I) + ENUM_TO_STRING_CASE(GL_RGB10_A2UI) + ENUM_TO_STRING_CASE(GL_SRGB) + ENUM_TO_STRING_CASE(GL_SRGB8) + ENUM_TO_STRING_CASE(GL_SRGB_ALPHA) + ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32) + ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) + ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32F) + ENUM_TO_STRING_CASE(GL_DEPTH32F_STENCIL8) + +#elif ANDROID_ENABLED + // using definitions from GLES3/gl3.h + + ENUM_TO_STRING_CASE(GL_RGBA4) + ENUM_TO_STRING_CASE(GL_RGB5_A1) + ENUM_TO_STRING_CASE(GL_RGB565) + ENUM_TO_STRING_CASE(GL_RGB8) + ENUM_TO_STRING_CASE(GL_RGBA8) + ENUM_TO_STRING_CASE(GL_RGB10_A2) + ENUM_TO_STRING_CASE(GL_RGBA32F) + ENUM_TO_STRING_CASE(GL_RGB32F) + ENUM_TO_STRING_CASE(GL_RGBA16F) + ENUM_TO_STRING_CASE(GL_RGB16F) + ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) + ENUM_TO_STRING_CASE(GL_UNSIGNED_INT_10F_11F_11F_REV) + ENUM_TO_STRING_CASE(GL_RGB9_E5) + ENUM_TO_STRING_CASE(GL_UNSIGNED_INT_5_9_9_9_REV) + ENUM_TO_STRING_CASE(GL_RGBA32UI) + ENUM_TO_STRING_CASE(GL_RGB32UI) + ENUM_TO_STRING_CASE(GL_RGBA16UI) + ENUM_TO_STRING_CASE(GL_RGB16UI) + ENUM_TO_STRING_CASE(GL_RGBA8UI) + ENUM_TO_STRING_CASE(GL_RGB8UI) + ENUM_TO_STRING_CASE(GL_RGBA32I) + ENUM_TO_STRING_CASE(GL_RGB32I) + ENUM_TO_STRING_CASE(GL_RGBA16I) + ENUM_TO_STRING_CASE(GL_RGB16I) + ENUM_TO_STRING_CASE(GL_RGBA8I) + ENUM_TO_STRING_CASE(GL_RGB8I) + ENUM_TO_STRING_CASE(GL_RG) + ENUM_TO_STRING_CASE(GL_RG_INTEGER) + ENUM_TO_STRING_CASE(GL_R8) + ENUM_TO_STRING_CASE(GL_RG8) + ENUM_TO_STRING_CASE(GL_R16F) + ENUM_TO_STRING_CASE(GL_R32F) + ENUM_TO_STRING_CASE(GL_RG16F) + ENUM_TO_STRING_CASE(GL_RG32F) + ENUM_TO_STRING_CASE(GL_R8I) + ENUM_TO_STRING_CASE(GL_R8UI) + ENUM_TO_STRING_CASE(GL_R16I) + ENUM_TO_STRING_CASE(GL_R16UI) + ENUM_TO_STRING_CASE(GL_R32I) + ENUM_TO_STRING_CASE(GL_R32UI) + ENUM_TO_STRING_CASE(GL_RG8I) + ENUM_TO_STRING_CASE(GL_RG8UI) + ENUM_TO_STRING_CASE(GL_RG16I) + ENUM_TO_STRING_CASE(GL_RG16UI) + ENUM_TO_STRING_CASE(GL_RG32I) + ENUM_TO_STRING_CASE(GL_RG32UI) + ENUM_TO_STRING_CASE(GL_R8_SNORM) + ENUM_TO_STRING_CASE(GL_RG8_SNORM) + ENUM_TO_STRING_CASE(GL_RGB8_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) + ENUM_TO_STRING_CASE(GL_RGB10_A2UI) + ENUM_TO_STRING_CASE(GL_SRGB) + ENUM_TO_STRING_CASE(GL_SRGB8) + ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) + ENUM_TO_STRING_CASE(GL_COMPRESSED_R11_EAC) + ENUM_TO_STRING_CASE(GL_COMPRESSED_SIGNED_R11_EAC) + ENUM_TO_STRING_CASE(GL_COMPRESSED_RG11_EAC) + ENUM_TO_STRING_CASE(GL_COMPRESSED_SIGNED_RG11_EAC) + ENUM_TO_STRING_CASE(GL_COMPRESSED_RGB8_ETC2) + ENUM_TO_STRING_CASE(GL_COMPRESSED_SRGB8_ETC2) + ENUM_TO_STRING_CASE(GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) + ENUM_TO_STRING_CASE(GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) + ENUM_TO_STRING_CASE(GL_COMPRESSED_RGBA8_ETC2_EAC) + ENUM_TO_STRING_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) + ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) + +#else + // using definitions from GL/gl.h + ENUM_TO_STRING_CASE(GL_ALPHA4_EXT) + ENUM_TO_STRING_CASE(GL_ALPHA8_EXT) + ENUM_TO_STRING_CASE(GL_ALPHA12_EXT) + ENUM_TO_STRING_CASE(GL_ALPHA16_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE4_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE8_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE12_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE16_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE4_ALPHA4_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE6_ALPHA2_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE8_ALPHA8_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA4_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA12_EXT) + ENUM_TO_STRING_CASE(GL_LUMINANCE16_ALPHA16_EXT) + ENUM_TO_STRING_CASE(GL_INTENSITY_EXT) + ENUM_TO_STRING_CASE(GL_INTENSITY4_EXT) + ENUM_TO_STRING_CASE(GL_INTENSITY8_EXT) + ENUM_TO_STRING_CASE(GL_INTENSITY12_EXT) + ENUM_TO_STRING_CASE(GL_INTENSITY16_EXT) + ENUM_TO_STRING_CASE(GL_RGB2_EXT) + ENUM_TO_STRING_CASE(GL_RGB4_EXT) + ENUM_TO_STRING_CASE(GL_RGB5_EXT) + ENUM_TO_STRING_CASE(GL_RGB8_EXT) + ENUM_TO_STRING_CASE(GL_RGB10_EXT) + ENUM_TO_STRING_CASE(GL_RGB12_EXT) + ENUM_TO_STRING_CASE(GL_RGB16_EXT) + ENUM_TO_STRING_CASE(GL_RGBA2_EXT) + ENUM_TO_STRING_CASE(GL_RGBA4_EXT) + ENUM_TO_STRING_CASE(GL_RGB5_A1_EXT) + ENUM_TO_STRING_CASE(GL_RGBA8_EXT) + ENUM_TO_STRING_CASE(GL_RGB10_A2_EXT) + ENUM_TO_STRING_CASE(GL_RGBA12_EXT) + ENUM_TO_STRING_CASE(GL_RGBA16_EXT) + ENUM_TO_STRING_CASE(GL_SRGB_EXT) + ENUM_TO_STRING_CASE(GL_SRGB8_EXT) + ENUM_TO_STRING_CASE(GL_SRGB_ALPHA_EXT) + ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8_EXT) +#endif + default: { + return String("Swapchain format 0x") + String::num_int64(p_swapchain_format, 16); + } break; + } +} + +#endif // GLES3_ENABLED diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h new file mode 100644 index 0000000000..b666653c8e --- /dev/null +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* openxr_opengl_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_OPENGL_EXTENSION_H +#define OPENXR_OPENGL_EXTENSION_H + +#ifdef GLES3_ENABLED + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" + +#include "../openxr_api.h" +#include "../util.h" + +#ifdef ANDROID_ENABLED +#define XR_USE_GRAPHICS_API_OPENGL_ES +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> +#else +#define XR_USE_GRAPHICS_API_OPENGL +#endif + +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif + +#ifdef X11_ENABLED +#include OPENGL_INCLUDE_H +#define GL_GLEXT_PROTOTYPES 1 +#define GL3_PROTOTYPES 1 +#include <GL/gl.h> +#include <GL/glext.h> +#include <GL/glx.h> +#include <X11/Xlib.h> +#endif + +#ifdef ANDROID_ENABLED +// The jobject type from jni.h is used by openxr_platform.h on Android. +#include <jni.h> +#endif + +// include platform dependent structs +#include <openxr/openxr_platform.h> + +class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { +public: + OpenXROpenGLExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXROpenGLExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; + + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; + virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; + virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override; + virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override; + +private: + static OpenXROpenGLExtension *singleton; + +#ifdef WIN32 + static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl; +#elif ANDROID_ENABLED + static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl; +#else + static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl; +#endif + + struct SwapchainGraphicsData { + bool is_multiview; + Vector<RID> texture_rids; + }; + + bool check_graphics_api_support(XrVersion p_desired_version); + +#ifdef ANDROID_ENABLED + EXT_PROTO_XRRESULT_FUNC3(xrGetOpenGLESGraphicsRequirementsKHR, (XrInstance), p_instance, (XrSystemId), p_system_id, (XrGraphicsRequirementsOpenGLESKHR *), p_graphics_requirements) +#else + EXT_PROTO_XRRESULT_FUNC3(xrGetOpenGLGraphicsRequirementsKHR, (XrInstance), p_instance, (XrSystemId), p_system_id, (XrGraphicsRequirementsOpenGLKHR *), p_graphics_requirements) +#endif + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateSwapchainImages, (XrSwapchain), p_swapchain, (uint32_t), p_image_capacity_input, (uint32_t *), p_image_count_output, (XrSwapchainImageBaseHeader *), p_images) +}; + +#endif // GLES3_ENABLED + +#endif // OPENXR_OPENGL_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_palm_pose_extension.cpp b/modules/openxr/extensions/openxr_palm_pose_extension.cpp new file mode 100644 index 0000000000..fd3b8f50fe --- /dev/null +++ b/modules/openxr/extensions/openxr_palm_pose_extension.cpp @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* openxr_palm_pose_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_palm_pose_extension.h" +#include "core/string/print_string.h" + +OpenXRPalmPoseExtension *OpenXRPalmPoseExtension::singleton = nullptr; + +OpenXRPalmPoseExtension *OpenXRPalmPoseExtension::get_singleton() { + return singleton; +} + +OpenXRPalmPoseExtension::OpenXRPalmPoseExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_EXT_PALM_POSE_EXTENSION_NAME] = &available; +} + +OpenXRPalmPoseExtension::~OpenXRPalmPoseExtension() { + singleton = nullptr; +} + +bool OpenXRPalmPoseExtension::is_available() { + return available; +} + +bool OpenXRPalmPoseExtension::is_path_supported(const String &p_path) { + if (p_path == "/user/hand/left/input/palm_ext/pose") { + return available; + } + + if (p_path == "/user/hand/right/input/palm_ext/pose") { + return available; + } + + // Not a path under this extensions control, so we return true; + return true; +} diff --git a/modules/openxr/extensions/openxr_palm_pose_extension.h b/modules/openxr/extensions/openxr_palm_pose_extension.h new file mode 100644 index 0000000000..a7ef83c5d5 --- /dev/null +++ b/modules/openxr/extensions/openxr_palm_pose_extension.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* openxr_palm_pose_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_PALM_POSE_EXTENSION_H +#define OPENXR_PALM_POSE_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRPalmPoseExtension : public OpenXRExtensionWrapper { +public: + static OpenXRPalmPoseExtension *get_singleton(); + + OpenXRPalmPoseExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRPalmPoseExtension() override; + + bool is_available(); + + virtual bool is_path_supported(const String &p_path) override; + +private: + static OpenXRPalmPoseExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_PALM_POSE_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp index 2608c4ac17..ee5bda2881 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -31,30 +31,12 @@ #include "core/string/print_string.h" #include "../extensions/openxr_vulkan_extension.h" -#include "../openxr_api.h" #include "../openxr_util.h" #include "servers/rendering/renderer_rd/effects/copy_effects.h" #include "servers/rendering/renderer_rd/storage_rd/texture_storage.h" #include "servers/rendering/rendering_server_globals.h" #include "servers/rendering_server.h" -// need to include Vulkan so we know of type definitions -#define XR_USE_GRAPHICS_API_VULKAN - -#ifdef WINDOWS_ENABLED -// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform -// however due to the way the openxr headers are put together, we have no choice. -#include <windows.h> -#endif - -// include platform dependent structs -#include <openxr/openxr_platform.h> - -PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR_ptr = nullptr; -PFN_xrCreateVulkanInstanceKHR xrCreateVulkanInstanceKHR_ptr = nullptr; -PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR_ptr = nullptr; -PFN_xrCreateVulkanDeviceKHR xrCreateVulkanDeviceKHR_ptr = nullptr; - OpenXRVulkanExtension::OpenXRVulkanExtension(OpenXRAPI *p_openxr_api) : OpenXRGraphicsExtensionWrapper(p_openxr_api) { VulkanContext::set_vulkan_hooks(this); @@ -69,36 +51,15 @@ OpenXRVulkanExtension::~OpenXRVulkanExtension() { } void OpenXRVulkanExtension::on_instance_created(const XrInstance p_instance) { - XrResult result; - ERR_FAIL_NULL(openxr_api); // Obtain pointers to functions we're accessing here, they are (not yet) part of core. - result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsRequirements2KHR_ptr); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to xrGetVulkanGraphicsRequirements2KHR entry point [", openxr_api->get_error_string(result), "]"); - } - - result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanInstanceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanInstanceKHR_ptr); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to xrCreateVulkanInstanceKHR entry point [", openxr_api->get_error_string(result), "]"); - } - - result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsDevice2KHR_ptr); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to xrGetVulkanGraphicsDevice2KHR entry point [", openxr_api->get_error_string(result), "]"); - } - result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanDeviceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanDeviceKHR_ptr); - if (XR_FAILED(result)) { - print_line("OpenXR: Failed to xrCreateVulkanDeviceKHR entry point [", openxr_api->get_error_string(result), "]"); - } -} - -XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements) { - ERR_FAIL_NULL_V(xrGetVulkanGraphicsRequirements2KHR_ptr, XR_ERROR_HANDLE_INVALID); - - return (*xrGetVulkanGraphicsRequirements2KHR_ptr)(p_instance, p_system_id, p_graphics_requirements); + EXT_INIT_XR_FUNC(xrGetVulkanGraphicsRequirements2KHR); + EXT_INIT_XR_FUNC(xrCreateVulkanInstanceKHR); + EXT_INIT_XR_FUNC(xrGetVulkanGraphicsDevice2KHR); + EXT_INIT_XR_FUNC(xrCreateVulkanDeviceKHR); + EXT_INIT_XR_FUNC(xrEnumerateSwapchainImages); } bool OpenXRVulkanExtension::check_graphics_api_support(XrVersion p_desired_version) { @@ -141,12 +102,6 @@ bool OpenXRVulkanExtension::check_graphics_api_support(XrVersion p_desired_versi return true; } -XrResult OpenXRVulkanExtension::xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result) { - ERR_FAIL_NULL_V(xrCreateVulkanInstanceKHR_ptr, XR_ERROR_HANDLE_INVALID); - - return (*xrCreateVulkanInstanceKHR_ptr)(p_instance, p_create_info, r_vulkan_instance, r_vulkan_result); -} - bool OpenXRVulkanExtension::create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) { // get the vulkan version we are creating uint32_t vulkan_version = p_vulkan_create_info->pApplicationInfo->apiVersion; @@ -195,12 +150,6 @@ bool OpenXRVulkanExtension::create_vulkan_instance(const VkInstanceCreateInfo *p return true; } -XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device) { - ERR_FAIL_NULL_V(xrGetVulkanGraphicsDevice2KHR_ptr, XR_ERROR_HANDLE_INVALID); - - return (*xrGetVulkanGraphicsDevice2KHR_ptr)(p_instance, p_get_info, r_vulkan_physical_device); -} - bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) { ERR_FAIL_NULL_V(openxr_api, false); @@ -222,12 +171,6 @@ bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) { return true; } -XrResult OpenXRVulkanExtension::xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result) { - ERR_FAIL_NULL_V(xrCreateVulkanDeviceKHR_ptr, XR_ERROR_HANDLE_INVALID); - - return (*xrCreateVulkanDeviceKHR_ptr)(p_instance, p_create_info, r_device, r_result); -} - bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) { ERR_FAIL_NULL_V(openxr_api, false); @@ -285,6 +228,12 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usab p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_UINT); } +void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) { + p_usable_swap_chains.push_back(VK_FORMAT_R32_SFLOAT); + p_usable_swap_chains.push_back(VK_FORMAT_D24_UNORM_S8_UINT); + p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); +} + bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { XrSwapchainImageVulkanKHR *images = nullptr; @@ -328,7 +277,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; - uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT; switch (p_swapchain_format) { case VK_FORMAT_R8G8B8A8_SRGB: @@ -340,16 +289,32 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in // will thus do an sRGB -> Linear conversion as expected. // format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_B8G8R8A8_SRGB: // format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB; format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_R8G8B8A8_UINT: format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; break; case VK_FORMAT_B8G8R8A8_UINT: format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + break; + case VK_FORMAT_R32_SFLOAT: + format = RenderingDevice::DATA_FORMAT_R32_SFLOAT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + break; + case VK_FORMAT_D24_UNORM_S8_UINT: + format = RenderingDevice::DATA_FORMAT_D24_UNORM_S8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + break; + case VK_FORMAT_D32_SFLOAT_S8_UINT: + format = RenderingDevice::DATA_FORMAT_D32_SFLOAT_S8_UINT; + usage_flags |= RenderingDevice::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; break; default: // continue with our default value @@ -385,8 +350,7 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in break; } - Vector<RID> image_rids; - Vector<RID> framebuffers; + Vector<RID> texture_rids; // create Godot texture objects for each entry in our swapchain for (uint64_t i = 0; i < swapchain_length; i++) { @@ -401,19 +365,10 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in 1, p_array_size); - image_rids.push_back(image_rid); - - { - Vector<RID> fb; - fb.push_back(image_rid); - - RID fb_rid = rendering_device->framebuffer_create(fb, RenderingDevice::INVALID_ID, p_array_size); - framebuffers.push_back(fb_rid); - } + texture_rids.push_back(image_rid); } - data->image_rids = image_rids; - data->framebuffers = framebuffers; + data->texture_rids = texture_rids; memfree(images); @@ -427,33 +382,19 @@ bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z for (int j = 0; j < 4; j++) { for (int i = 0; i < 4; i++) { - r_camera_matrix.matrix[j][i] = matrix.m[j * 4 + i]; + r_camera_matrix.columns[j][i] = matrix.m[j * 4 + i]; } } return true; } -bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) { +RID OpenXRVulkanExtension::get_texture(void *p_swapchain_graphics_data, int p_image_index) { SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; - ERR_FAIL_NULL_V(data, false); - ERR_FAIL_COND_V(p_from_render_target.is_null(), false); - - RID source_image = RendererRD::TextureStorage::get_singleton()->render_target_get_rd_texture(p_from_render_target); - ERR_FAIL_COND_V(source_image.is_null(), false); - - RID depth_image; // TODO implement - - ERR_FAIL_INDEX_V(p_image_index, data->framebuffers.size(), false); - RID fb = data->framebuffers[p_image_index]; - ERR_FAIL_COND_V(fb.is_null(), false); + ERR_FAIL_NULL_V(data, RID()); - // Our vulkan extension can only be used in conjunction with our vulkan renderer. - RendererRD::CopyEffects *copy_effects = RendererRD::CopyEffects::get_singleton(); - ERR_FAIL_NULL_V(copy_effects, false); - copy_effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); - - return true; + ERR_FAIL_INDEX_V(p_image_index, data->texture_rids.size(), RID()); + return data->texture_rids[p_image_index]; } void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { @@ -468,17 +409,11 @@ void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g RenderingDevice *rendering_device = rendering_server->get_rendering_device(); ERR_FAIL_NULL(rendering_device); - for (int i = 0; i < data->image_rids.size(); i++) { - // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain - rendering_device->free(data->image_rids[i]); - } - data->image_rids.clear(); - - for (int i = 0; i < data->framebuffers.size(); i++) { + for (int i = 0; i < data->texture_rids.size(); i++) { // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain - rendering_device->free(data->framebuffers[i]); + rendering_device->free(data->texture_rids[i]); } - data->framebuffers.clear(); + data->texture_rids.clear(); memdelete(data); *p_swapchain_graphics_data = nullptr; diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index 5dddc4b9c9..71abe3b166 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -36,16 +36,25 @@ #include "drivers/vulkan/vulkan_context.h" -// Forward declare these so we don't need OpenXR headers where-ever this is included -// Including OpenXR at this point gives loads and loads of compile issues especially -// on Windows because windows.h is EVIL and really shouldn't be included outside of platform -// but we really don't have a choice in the matter +#include "../openxr_api.h" +#include "../util.h" -struct XrGraphicsRequirementsVulkanKHR; -struct XrVulkanInstanceCreateInfoKHR; -struct XrVulkanGraphicsDeviceGetInfoKHR; -struct XrVulkanDeviceCreateInfoKHR; -struct XrGraphicsBindingVulkanKHR; +// need to include Vulkan so we know of type definitions +#define XR_USE_GRAPHICS_API_VULKAN + +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif + +#ifdef ANDROID_ENABLED +// The jobject type from jni.h is used by openxr_platform.h on Android. +#include <jni.h> +#endif + +// include platform dependent structs +#include <openxr/openxr_platform.h> class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { public: @@ -60,11 +69,12 @@ public: virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override; virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) override; - virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override; + virtual RID get_texture(void *p_swapchain_graphics_data, int p_image_index) override; private: static OpenXRVulkanExtension *singleton; @@ -72,8 +82,7 @@ private: struct SwapchainGraphicsData { bool is_multiview; - Vector<RID> image_rids; - Vector<RID> framebuffers; + Vector<RID> texture_rids; }; bool check_graphics_api_support(XrVersion p_desired_version); @@ -84,10 +93,11 @@ private: uint32_t vulkan_queue_family_index = 0; uint32_t vulkan_queue_index = 0; - XrResult xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements); - XrResult xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result); - XrResult xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device); - XrResult xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result); + EXT_PROTO_XRRESULT_FUNC3(xrGetVulkanGraphicsRequirements2KHR, (XrInstance), p_instance, (XrSystemId), p_system_id, (XrGraphicsRequirementsVulkanKHR *), p_graphics_requirements) + EXT_PROTO_XRRESULT_FUNC4(xrCreateVulkanInstanceKHR, (XrInstance), p_instance, (const XrVulkanInstanceCreateInfoKHR *), p_create_info, (VkInstance *), r_vulkan_instance, (VkResult *), r_vulkan_result) + EXT_PROTO_XRRESULT_FUNC3(xrGetVulkanGraphicsDevice2KHR, (XrInstance), p_instance, (const XrVulkanGraphicsDeviceGetInfoKHR *), p_get_info, (VkPhysicalDevice *), r_vulkan_physical_device) + EXT_PROTO_XRRESULT_FUNC4(xrCreateVulkanDeviceKHR, (XrInstance), p_instance, (const XrVulkanDeviceCreateInfoKHR *), p_create_info, (VkDevice *), r_device, (VkResult *), r_result) + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateSwapchainImages, (XrSwapchain), p_swapchain, (uint32_t), p_image_capacity_input, (uint32_t *), p_image_count_output, (XrSwapchainImageBaseHeader *), p_images) }; #endif // OPENXR_VULKAN_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 92d074cb75..88111afede 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -41,14 +41,50 @@ #endif #ifdef ANDROID_ENABLED +#define OPENXR_LOADER_NAME "libopenxr_loader.so" #include "extensions/openxr_android_extension.h" #endif +// We need to have all the graphics API defines before the Vulkan or OpenGL +// extensions are included, otherwise we'll only get one graphics API. +#ifdef VULKAN_ENABLED +#define XR_USE_GRAPHICS_API_VULKAN +#endif +#ifdef GLES3_ENABLED +#ifdef ANDROID +#define XR_USE_GRAPHICS_API_OPENGL_ES +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> +#else +#define XR_USE_GRAPHICS_API_OPENGL +#endif // ANDROID +#ifdef X11_ENABLED +#include OPENGL_INCLUDE_H +#define GL_GLEXT_PROTOTYPES 1 +#define GL3_PROTOTYPES 1 +#include <GL/gl.h> +#include <GL/glext.h> +#include <GL/glx.h> +#include <X11/Xlib.h> +#endif // X11_ENABLED +#endif // GLES_ENABLED + #ifdef VULKAN_ENABLED #include "extensions/openxr_vulkan_extension.h" #endif +#ifdef GLES3_ENABLED +#include "extensions/openxr_opengl_extension.h" +#endif + +#include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_fb_display_refresh_rate_extension.h" +#include "extensions/openxr_fb_passthrough_extension_wrapper.h" +#include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" +#include "extensions/openxr_palm_pose_extension.h" #include "modules/openxr/openxr_interface.h" @@ -126,11 +162,9 @@ bool OpenXRAPI::load_layer_properties() { result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties"); -#ifdef DEBUG for (uint32_t i = 0; i < num_layer_properties; i++) { - print_line("OpenXR: Found OpenXR layer ", layer_properties[i].layerName); + print_verbose(String("OpenXR: Found OpenXR layer ") + layer_properties[i].layerName); } -#endif return true; } @@ -158,11 +192,9 @@ bool OpenXRAPI::load_supported_extensions() { result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties"); -#ifdef DEBUG for (uint32_t i = 0; i < num_supported_extensions; i++) { - print_line("OpenXR: Found OpenXR extension ", supported_extensions[i].extensionName); + print_verbose(String("OpenXR: Found OpenXR extension ") + supported_extensions[i].extensionName); } -#endif return true; } @@ -170,20 +202,27 @@ bool OpenXRAPI::load_supported_extensions() { bool OpenXRAPI::is_extension_supported(const String &p_extension) const { for (uint32_t i = 0; i < num_supported_extensions; i++) { if (supported_extensions[i].extensionName == p_extension) { -#ifdef DEBUG - print_line("OpenXR: requested extension", p_extension, "is supported"); -#endif return true; } } -#ifdef DEBUG - print_line("OpenXR: requested extension", p_extension, "is not supported"); -#endif - return false; } +bool OpenXRAPI::is_path_supported(const String &p_path) { + // This checks with extensions whether a path is *unsupported* and returns false if this is so. + // This allows us to filter out paths that are only available if related extensions are supported. + // WARNING: This method will return true for unknown/mistyped paths as we have no way to validate those. + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + if (!wrapper->is_path_supported(p_path)) { + return false; + } + } + + return true; +} + void OpenXRAPI::copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len) { CharString char_string = p_string.utf8(); int len = char_string.length(); @@ -226,7 +265,7 @@ bool OpenXRAPI::create_instance() { if (!is_extension_supported(requested_extension.key)) { if (requested_extension.value == nullptr) { // nullptr means this is a manditory extension so we fail - ERR_FAIL_V_MSG(false, "OpenXR: OpenXR Runtime does not support OpenGL extension!"); + ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!")); } else { // set this extension as not supported *requested_extension.value = false; @@ -284,6 +323,9 @@ bool OpenXRAPI::create_instance() { 0, // runtimeVersion, from here will be set by our get call "" // runtimeName }; + + OPENXR_API_INIT_XR_FUNC_V(xrGetInstanceProperties); + result = xrGetInstanceProperties(instance, &instanceProps); if (XR_FAILED(result)) { // not fatal probably @@ -379,11 +421,9 @@ bool OpenXRAPI::load_supported_view_configuration_types() { result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations"); -#ifdef DEBUG for (uint32_t i = 0; i < num_view_configuration_types; i++) { - print_line("OpenXR: Found supported view configuration ", OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); + print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); } -#endif return true; } @@ -432,17 +472,15 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations"); -#ifdef DEBUG for (uint32_t i = 0; i < view_count; i++) { - print_line("OpenXR: Found supported view configuration view"); - print_line(" - width: ", view_configuration_views[i].maxImageRectWidth); - print_line(" - height: ", view_configuration_views[i].maxImageRectHeight); - print_line(" - sample count: ", view_configuration_views[i].maxSwapchainSampleCount); - print_line(" - recommended render width: ", view_configuration_views[i].recommendedImageRectWidth); - print_line(" - recommended render height: ", view_configuration_views[i].recommendedImageRectHeight); - print_line(" - recommended render sample count: ", view_configuration_views[i].recommendedSwapchainSampleCount); + print_verbose("OpenXR: Found supported view configuration view"); + print_verbose(String(" - width: ") + itos(view_configuration_views[i].maxImageRectWidth)); + print_verbose(String(" - height: ") + itos(view_configuration_views[i].maxImageRectHeight)); + print_verbose(String(" - sample count: ") + itos(view_configuration_views[i].maxSwapchainSampleCount)); + print_verbose(String(" - recommended render width: ") + itos(view_configuration_views[i].recommendedImageRectWidth)); + print_verbose(String(" - recommended render height: ") + itos(view_configuration_views[i].recommendedImageRectHeight)); + print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); } -#endif return true; } @@ -524,11 +562,9 @@ bool OpenXRAPI::load_supported_reference_spaces() { result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces"); - // #ifdef DEBUG for (uint32_t i = 0; i < num_reference_spaces; i++) { - print_line("OpenXR: Found supported reference space ", OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); + print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); } - // #endif return true; } @@ -621,11 +657,9 @@ bool OpenXRAPI::load_supported_swapchain_formats() { result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats"); - // #ifdef DEBUG for (uint32_t i = 0; i < num_swapchain_formats; i++) { - print_line("OpenXR: Found supported swapchain format ", get_swapchain_format_name(supported_swapchain_formats[i])); + print_verbose(String("OpenXR: Found supported swapchain format ") + get_swapchain_format_name(supported_swapchain_formats[i])); } - // #endif return true; } @@ -642,7 +676,7 @@ bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { return false; } -bool OpenXRAPI::create_main_swapchain() { +bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V(graphics_extension, false); ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -660,34 +694,36 @@ bool OpenXRAPI::create_main_swapchain() { already rendering the next frame. Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, - as we render 3D content into internal buffers that are copied into the swapchain, we don't get any of the performance gains - until such time as we implement VRS. + as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support */ - // Build a vector with swapchain formats we want to use, from best fit to worst - Vector<int64_t> usable_swapchain_formats; - int64_t swapchain_format_to_use = 0; + Size2 recommended_size = get_recommended_target_size(); - graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); + // We start with our color swapchain... + { + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; - // now find out which one is supported - for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { - if (is_swapchain_format_supported(usable_swapchain_formats[i])) { - swapchain_format_to_use = usable_swapchain_formats[i]; - } - } + graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); - if (swapchain_format_to_use == 0) { - swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... - print_line("Couldn't find usable swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); - } else { - print_line("Using swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); - } + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } - Size2 recommended_size = get_recommended_target_size(); + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable color swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); + } - if (!create_swapchain(swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchain, &swapchain_graphics_data)) { - return false; + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { + return false; + } } views = (XrView *)memalloc(sizeof(XrView) * view_count); @@ -696,18 +732,73 @@ bool OpenXRAPI::create_main_swapchain() { projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + // We create our depth swapchain if: + // - we support our depth layer extension + // - we have our spacewarp extension (not yet implemented) + if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; + + graphics_extension->get_usable_depth_formats(usable_swapchain_formats); + + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } + + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable depth swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); + } + + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { + return false; + } + + depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + } + + // We create our velocity swapchain if: + // - we have our spacewarp extension (not yet implemented) + { + // 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 = swapchain; + projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].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 = recommended_size.width; projection_views[i].subImage.imageRect.extent.height = recommended_size.height; + + if (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 = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; + depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageRect.offset.x = 0; + depth_views[i].subImage.imageRect.offset.y = 0; + depth_views[i].subImage.imageRect.extent.width = recommended_size.width; + depth_views[i].subImage.imageRect.extent.height = recommended_size.height; + depth_views[i].minDepth = 0.0; + depth_views[i].maxDepth = 1.0; + depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix + depth_views[i].farZ = 100.0; + } }; return true; @@ -719,7 +810,7 @@ void OpenXRAPI::destroy_session() { } if (graphics_extension) { - graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + graphics_extension->cleanup_swapchain_graphics_data(&swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data); } if (views != nullptr) { @@ -732,9 +823,16 @@ void OpenXRAPI::destroy_session() { projection_views = nullptr; } - if (swapchain != XR_NULL_HANDLE) { - xrDestroySwapchain(swapchain); - swapchain = XR_NULL_HANDLE; + if (depth_views != nullptr) { + memfree(depth_views); + depth_views = nullptr; + } + + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + if (swapchains[i].swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(swapchains[i].swapchain); + swapchains[i].swapchain = XR_NULL_HANDLE; + } } if (supported_swapchain_formats != nullptr) { @@ -768,7 +866,7 @@ void OpenXRAPI::destroy_session() { } } -bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { +bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); ERR_FAIL_NULL_V(graphics_extension, false); @@ -786,7 +884,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u XR_TYPE_SWAPCHAIN_CREATE_INFO, // type next_pointer, // next 0, // createFlags - XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags + p_usage_flags, // usageFlags p_swapchain_format, // format p_sample_count, // sampleCount p_width, // width @@ -814,9 +912,7 @@ bool OpenXRAPI::create_swapchain(int64_t p_swapchain_format, uint32_t p_width, u } bool OpenXRAPI::on_state_idle() { -#ifdef DEBUG - print_line("On state idle"); -#endif + print_verbose("On state idle"); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_idle(); @@ -826,9 +922,7 @@ bool OpenXRAPI::on_state_idle() { } bool OpenXRAPI::on_state_ready() { -#ifdef DEBUG - print_line("On state ready"); -#endif + print_verbose("On state ready"); // begin session XrSessionBeginInfo session_begin_info = { @@ -850,7 +944,7 @@ bool OpenXRAPI::on_state_ready() { // That will be very very ugly // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module - if (!create_main_swapchain()) { + if (!create_swapchains()) { return false; } @@ -871,9 +965,7 @@ bool OpenXRAPI::on_state_ready() { } bool OpenXRAPI::on_state_synchronized() { -#ifdef DEBUG - print_line("On state synchronized"); -#endif + print_verbose("On state synchronized"); // Just in case, see if we already have active trackers... List<RID> trackers; @@ -890,9 +982,7 @@ bool OpenXRAPI::on_state_synchronized() { } bool OpenXRAPI::on_state_visible() { -#ifdef DEBUG - print_line("On state visible"); -#endif + print_verbose("On state visible"); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_visible(); @@ -906,9 +996,7 @@ bool OpenXRAPI::on_state_visible() { } bool OpenXRAPI::on_state_focused() { -#ifdef DEBUG - print_line("On state focused"); -#endif + print_verbose("On state focused"); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_focused(); @@ -922,9 +1010,7 @@ bool OpenXRAPI::on_state_focused() { } bool OpenXRAPI::on_state_stopping() { -#ifdef DEBUG - print_line("On state stopping"); -#endif + print_verbose("On state stopping"); if (xr_interface) { xr_interface->on_state_stopping(); @@ -950,9 +1036,7 @@ bool OpenXRAPI::on_state_stopping() { } bool OpenXRAPI::on_state_loss_pending() { -#ifdef DEBUG - print_line("On state loss pending"); -#endif + print_verbose("On state loss pending"); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_loss_pending(); @@ -964,9 +1048,7 @@ bool OpenXRAPI::on_state_loss_pending() { } bool OpenXRAPI::on_state_exiting() { -#ifdef DEBUG - print_line("On state existing"); -#endif + print_verbose("On state existing"); for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_state_exiting(); @@ -992,9 +1074,94 @@ bool OpenXRAPI::is_running() { return running; } +bool OpenXRAPI::openxr_loader_init() { +#ifdef ANDROID_ENABLED + ERR_FAIL_COND_V_MSG(openxr_loader_library_handle != nullptr, false, "OpenXR Loader library is already loaded."); + + { + Error error_code = OS::get_singleton()->open_dynamic_library(OPENXR_LOADER_NAME, openxr_loader_library_handle); + ERR_FAIL_COND_V_MSG(error_code != OK, false, "OpenXR loader not found."); + } + + { + Error error_code = OS::get_singleton()->get_dynamic_library_symbol_handle(openxr_loader_library_handle, "xrGetInstanceProcAddr", (void *&)xrGetInstanceProcAddr); + ERR_FAIL_COND_V_MSG(error_code != OK, false, "Symbol xrGetInstanceProcAddr not found in OpenXR Loader library."); + } +#endif + + // Resolve the symbols that don't require an instance + OPENXR_API_INIT_XR_FUNC_V(xrCreateInstance); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateApiLayerProperties); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateInstanceExtensionProperties); + + return true; +} + +bool OpenXRAPI::resolve_instance_openxr_symbols() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + OPENXR_API_INIT_XR_FUNC_V(xrAcquireSwapchainImage); + OPENXR_API_INIT_XR_FUNC_V(xrApplyHapticFeedback); + OPENXR_API_INIT_XR_FUNC_V(xrAttachSessionActionSets); + OPENXR_API_INIT_XR_FUNC_V(xrBeginFrame); + OPENXR_API_INIT_XR_FUNC_V(xrBeginSession); + OPENXR_API_INIT_XR_FUNC_V(xrCreateAction); + OPENXR_API_INIT_XR_FUNC_V(xrCreateActionSet); + OPENXR_API_INIT_XR_FUNC_V(xrCreateActionSpace); + OPENXR_API_INIT_XR_FUNC_V(xrCreateReferenceSpace); + OPENXR_API_INIT_XR_FUNC_V(xrCreateSession); + OPENXR_API_INIT_XR_FUNC_V(xrCreateSwapchain); + OPENXR_API_INIT_XR_FUNC_V(xrDestroyAction); + OPENXR_API_INIT_XR_FUNC_V(xrDestroyActionSet); + OPENXR_API_INIT_XR_FUNC_V(xrDestroyInstance); + OPENXR_API_INIT_XR_FUNC_V(xrDestroySession); + OPENXR_API_INIT_XR_FUNC_V(xrDestroySpace); + OPENXR_API_INIT_XR_FUNC_V(xrDestroySwapchain); + OPENXR_API_INIT_XR_FUNC_V(xrEndFrame); + OPENXR_API_INIT_XR_FUNC_V(xrEndSession); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateReferenceSpaces); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateSwapchainFormats); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateViewConfigurations); + OPENXR_API_INIT_XR_FUNC_V(xrEnumerateViewConfigurationViews); + OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateBoolean); + OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateFloat); + OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateVector2f); + OPENXR_API_INIT_XR_FUNC_V(xrGetCurrentInteractionProfile); + OPENXR_API_INIT_XR_FUNC_V(xrGetSystem); + OPENXR_API_INIT_XR_FUNC_V(xrGetSystemProperties); + OPENXR_API_INIT_XR_FUNC_V(xrLocateViews); + OPENXR_API_INIT_XR_FUNC_V(xrLocateSpace); + OPENXR_API_INIT_XR_FUNC_V(xrPathToString); + OPENXR_API_INIT_XR_FUNC_V(xrPollEvent); + OPENXR_API_INIT_XR_FUNC_V(xrReleaseSwapchainImage); + OPENXR_API_INIT_XR_FUNC_V(xrResultToString); + OPENXR_API_INIT_XR_FUNC_V(xrStringToPath); + OPENXR_API_INIT_XR_FUNC_V(xrSuggestInteractionProfileBindings); + OPENXR_API_INIT_XR_FUNC_V(xrSyncActions); + OPENXR_API_INIT_XR_FUNC_V(xrWaitFrame); + OPENXR_API_INIT_XR_FUNC_V(xrWaitSwapchainImage); + + return true; +} + +XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) { + XrResult result = xrGetInstanceProcAddr(instance, p_name, p_addr); + + if (result != XR_SUCCESS) { + String error_message = String("Symbol ") + p_name + " not found in OpenXR instance."; + ERR_FAIL_COND_V_MSG(true, result, error_message.utf8().get_data()); + } + + return result; +} + bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created"); + if (!openxr_loader_init()) { + return false; + } + if (p_rendering_driver == "vulkan") { #ifdef VULKAN_ENABLED graphics_extension = memnew(OpenXRVulkanExtension(this)); @@ -1005,9 +1172,8 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { #endif } else if (p_rendering_driver == "opengl3") { #ifdef GLES3_ENABLED - // graphics_extension = memnew(OpenXROpenGLExtension(this)); - // register_extension_wrapper(graphics_extension); - ERR_FAIL_V_MSG(false, "OpenXR: OpenGL is not supported at this time."); + graphics_extension = memnew(OpenXROpenGLExtension(this)); + register_extension_wrapper(graphics_extension); #else // shouldn't be possible... ERR_FAIL_V(false); @@ -1017,6 +1183,10 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { } // initialize + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_before_instance_created(); + } + if (!load_layer_properties()) { destroy_instance(); return false; @@ -1032,6 +1202,11 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { return false; } + if (!resolve_instance_openxr_symbols()) { + destroy_instance(); + return false; + } + if (!get_system_info()) { destroy_instance(); return false; @@ -1144,12 +1319,10 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, head_pose_confidence = confidence; if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { print_line("OpenXR head space location not valid (check tracking?)"); -#ifdef DEBUG } else if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_LOW) { - print_line("OpenVR Head pose now tracking with low confidence"); + print_verbose("OpenVR Head pose now tracking with low confidence"); } else { - print_line("OpenVR Head pose now tracking with high confidence"); -#endif + print_verbose("OpenVR Head pose now tracking with high confidence"); } } @@ -1189,6 +1362,15 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z 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++) { + depth_views[i].nearZ = p_z_near; + depth_views[i].farZ = p_z_far; + } + } + + // now update our projection return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); } @@ -1228,7 +1410,7 @@ bool OpenXRAPI::poll_events() { // TODO We get this event if we're about to loose our OpenXR instance. // We should queue exiting Godot at this point. - print_verbose("OpenXR EVENT: instance loss pending at " + itos(event->lossTime)); + print_verbose(String("OpenXR EVENT: instance loss pending at ") + itos(event->lossTime)); return false; } break; case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { @@ -1236,9 +1418,9 @@ bool OpenXRAPI::poll_events() { session_state = event->state; if (session_state >= XR_SESSION_STATE_MAX_ENUM) { - print_verbose("OpenXR EVENT: session state changed to UNKNOWN - " + itos(session_state)); + print_verbose(String("OpenXR EVENT: session state changed to UNKNOWN - ") + itos(session_state)); } else { - print_verbose("OpenXR EVENT: session state changed to " + OpenXRUtil::get_session_state_name(session_state)); + print_verbose(String("OpenXR EVENT: session state changed to ") + OpenXRUtil::get_session_state_name(session_state)); switch (session_state) { case XR_SESSION_STATE_IDLE: @@ -1273,7 +1455,7 @@ bool OpenXRAPI::poll_events() { case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; - print_verbose("OpenXR EVENT: reference space type " + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); if (event->poseValid && xr_interface) { xr_interface->on_pose_recentered(); } @@ -1292,7 +1474,7 @@ bool OpenXRAPI::poll_events() { } break; default: if (!handled) { - print_verbose("OpenXR Unhandled event type " + OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + print_verbose(String("OpenXR Unhandled event type ") + OpenXRUtil::get_structure_type_name(runtimeEvent.type)); } break; } @@ -1327,15 +1509,15 @@ bool OpenXRAPI::process() { return true; } -bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) { - ERR_FAIL_COND_V(image_acquired, true); // this was not released when it should be, error out and re-use... +bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { + ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // this was not released when it should be, error out and re-use... XrResult result; XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type nullptr // next }; - result = xrAcquireSwapchainImage(p_swapchain, &swapchain_image_acquire_info, &r_image_index); + result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); if (XR_FAILED(result)) { print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); return false; @@ -1347,7 +1529,7 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) 17000000 // timeout in nanoseconds }; - result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info); + result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info); if (XR_FAILED(result)) { print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); return false; @@ -1356,12 +1538,12 @@ bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) return true; } -bool OpenXRAPI::release_image(XrSwapchain p_swapchain) { +bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) { XrSwapchainImageReleaseInfo swapchain_image_release_info = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type nullptr // next }; - XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + XrResult result = xrReleaseSwapchainImage(p_swapchain.swapchain, &swapchain_image_release_info); if (XR_FAILED(result)) { print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); return false; @@ -1382,6 +1564,10 @@ void OpenXRAPI::pre_render() { // 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 XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr }; + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + frame_state.shouldRender = false; + XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); if (XR_FAILED(result)) { print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); @@ -1396,7 +1582,7 @@ void OpenXRAPI::pre_render() { if (frame_state.predictedDisplayPeriod > 500000000) { // display period more then 0.5 seconds? must be wrong data - print_verbose("OpenXR resetting invalid display period " + rtos(frame_state.predictedDisplayPeriod)); + print_verbose(String("OpenXR resetting invalid display period ") + rtos(frame_state.predictedDisplayPeriod)); frame_state.predictedDisplayPeriod = 0; } @@ -1443,13 +1629,11 @@ void OpenXRAPI::pre_render() { } if (view_pose_valid != pose_valid) { view_pose_valid = pose_valid; -#ifdef DEBUG if (!view_pose_valid) { - print_line("OpenXR View pose became invalid"); + print_verbose("OpenXR View pose became invalid"); } else { - print_line("OpenXR View pose became valid"); + print_verbose("OpenXR View pose became valid"); } -#endif } // let's start our frame.. @@ -1471,28 +1655,41 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { // 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 (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) { + if (!acquire_image(swapchains[i])) { + return false; + } + swapchains[i].image_acquired = true; + } + } + return true; } +RID OpenXRAPI::get_color_texture() { + if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { + return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index); + } else { + return RID(); + } +} + +RID OpenXRAPI::get_depth_texture() { + if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); + } else { + return RID(); + } +} + void OpenXRAPI::post_draw_viewport(RID p_render_target) { if (!can_render()) { return; } - // TODO: at some point in time we may support multiple viewports in which case we need to handle that... - - // TODO: if we can get PR 51179 to work properly we can change away from this approach and move this into get_external_texture or something - if (!image_acquired) { - if (!acquire_image(swapchain, image_index)) { - return; - } - image_acquired = true; - - // print_line("OpenXR: acquired image " + itos(image_index) + ", copying..."); - - // Copy our buffer into our swap chain (remove once PR 51179 is done) - graphics_extension->copy_render_target_to_image(p_render_target, swapchain_graphics_data, image_index); - } + // Nothing to do here at this point in time... }; void OpenXRAPI::end_frame() { @@ -1504,7 +1701,7 @@ void OpenXRAPI::end_frame() { return; } - if (frame_state.shouldRender && view_pose_valid && !image_acquired) { + if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); } @@ -1512,7 +1709,7 @@ void OpenXRAPI::end_frame() { // - shouldRender 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 || !image_acquired) { + if (!frame_state.shouldRender || !view_pose_valid || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { // submit 0 layers when we shouldn't render XrFrameEndInfo frame_end_info = { XR_TYPE_FRAME_END_INFO, // type @@ -1533,10 +1730,12 @@ void OpenXRAPI::end_frame() { } // release our swapchain image if we acquired it - if (image_acquired) { - image_acquired = false; // whether we succeed or not, consider this released. + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + if (swapchains[i].image_acquired) { + swapchains[i].image_acquired = false; // whether we succeed or not, consider this released. - release_image(swapchain); + release_image(swapchains[i]); + } } for (uint32_t eye = 0; eye < view_count; eye++) { @@ -1557,7 +1756,7 @@ void OpenXRAPI::end_frame() { XrCompositionLayerProjection projection_layer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type nullptr, // next - layers_list.size() > 1 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags + layers_list.size() > 0 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags play_space, // space view_count, // viewCount projection_views, // views @@ -1579,6 +1778,31 @@ void OpenXRAPI::end_frame() { } } +float OpenXRAPI::get_display_refresh_rate() const { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext) { + return drrext->get_refresh_rate(); + } + + return 0.0; +} + +void OpenXRAPI::set_display_refresh_rate(float p_refresh_rate) { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext != nullptr) { + drrext->set_refresh_rate(p_refresh_rate); + } +} + +Array OpenXRAPI::get_available_display_refresh_rates() const { + OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton(); + if (drrext != nullptr) { + return drrext->get_available_refresh_rates(); + } + + return Array(); +} + OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. singleton = this; @@ -1643,7 +1867,12 @@ OpenXRAPI::OpenXRAPI() { #endif // register our other extensions + register_extension_wrapper(memnew(OpenXRPalmPoseExtension(this))); + register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension(this))); register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension(this))); + register_extension_wrapper(memnew(OpenXRHandTrackingExtension(this))); + register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper(this))); + register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension(this))); } OpenXRAPI::~OpenXRAPI() { @@ -1669,6 +1898,13 @@ OpenXRAPI::~OpenXRAPI() { layer_properties = nullptr; } +#ifdef ANDROID_ENABLED + if (openxr_loader_library_handle) { + OS::get_singleton()->close_dynamic_library(openxr_loader_library_handle); + openxr_loader_library_handle = nullptr; + } +#endif + singleton = nullptr; } @@ -1745,6 +1981,18 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l } } +bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const { + if (XR_SUCCEEDED(result)) + return true; + + char resultString[XR_MAX_RESULT_STRING_SIZE]; + xrResultToString(instance, result, resultString); + + print_error(String("OpenXR ") + String(format).format(args) + String(" [") + String(resultString) + String("]")); + + return false; +} + RID OpenXRAPI::get_tracker_rid(XrPath p_path) { List<RID> current; tracker_owner.get_owned_list(¤t); @@ -1850,8 +2098,6 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE); - // print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); - XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle); if (XR_FAILED(result)) { print_line("OpenXR: failed to create action set ", p_name, "! [", get_error_string(result), "]"); @@ -2005,8 +2251,6 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE); copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE); - // print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); - XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle); if (XR_FAILED(result)) { print_line("OpenXR: failed to create action ", p_name, "! [", get_error_string(result), "]"); @@ -2063,6 +2307,11 @@ XrPath OpenXRAPI::get_interaction_profile_path(RID p_interaction_profile) { } RID OpenXRAPI::interaction_profile_create(const String p_name) { + if (!is_path_supported(p_name)) { + // The extension enabling this path must not be active, we will silently skip this interaction profile + return RID(); + } + InteractionProfile new_interaction_profile; XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_interaction_profile.path); @@ -2099,6 +2348,11 @@ void OpenXRAPI::interaction_profile_clear_bindings(RID p_interaction_profile) { } bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path) { + if (!is_path_supported(p_path)) { + // The extension enabling this path must not be active, we will silently skip this binding + return false; + } + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); ERR_FAIL_NULL_V(ip, false); @@ -2426,3 +2680,11 @@ bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_tracker, float p_freque return true; } + +void OpenXRAPI::register_composition_layer_provider(OpenXRCompositionLayerProvider *provider) { + composition_layer_providers.append(provider); +} + +void OpenXRAPI::unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider) { + composition_layer_providers.erase(provider); +} diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index dc224c4237..5dce749351 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -36,6 +36,7 @@ #include "core/math/transform_3d.h" #include "core/math/vector2.h" #include "core/os/memory.h" +#include "core/string/print_string.h" #include "core/string/ustring.h" #include "core/templates/rb_map.h" #include "core/templates/rid_owner.h" @@ -50,6 +51,8 @@ #include "extensions/openxr_composition_layer_provider.h" #include "extensions/openxr_extension_wrapper.h" +#include "util.h" + // Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initialising 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. @@ -100,7 +103,7 @@ private: XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; - XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + // XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; // state XrInstance instance = XR_NULL_HANDLE; @@ -115,15 +118,28 @@ private: OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; XrSystemGraphicsProperties graphics_properties; - void *swapchain_graphics_data = nullptr; - uint32_t image_index = 0; - bool image_acquired = false; uint32_t view_count = 0; XrViewConfigurationView *view_configuration_views = nullptr; XrView *views = nullptr; XrCompositionLayerProjectionView *projection_views = nullptr; - XrSwapchain swapchain = XR_NULL_HANDLE; + XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available + + enum OpenXRSwapChainTypes { + OPENXR_SWAPCHAIN_COLOR, + OPENXR_SWAPCHAIN_DEPTH, + // OPENXR_SWAPCHAIN_VELOCITY, + OPENXR_SWAPCHAIN_MAX + }; + + struct OpenXRSwapChainInfo { + XrSwapchain swapchain = XR_NULL_HANDLE; + void *swapchain_graphics_data = nullptr; + uint32_t image_index = 0; + bool image_acquired = false; + }; + + OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; XrSpace play_space = XR_NULL_HANDLE; XrSpace view_space = XR_NULL_HANDLE; @@ -134,6 +150,64 @@ private: bool load_supported_extensions(); bool is_extension_supported(const String &p_extension) const; + bool openxr_loader_init(); + bool resolve_instance_openxr_symbols(); + +#ifdef ANDROID_ENABLED + // On Android we keep tracker of our external OpenXR loader + void *openxr_loader_library_handle = nullptr; +#endif + + // function pointers +#ifdef ANDROID_ENABLED + // On non-Android platforms we use the OpenXR symbol linked into the engine binary. + PFN_xrGetInstanceProcAddr xrGetInstanceProcAddr = nullptr; +#endif + EXT_PROTO_XRRESULT_FUNC3(xrAcquireSwapchainImage, (XrSwapchain), swapchain, (const XrSwapchainImageAcquireInfo *), acquireInfo, (uint32_t *), index) + EXT_PROTO_XRRESULT_FUNC3(xrApplyHapticFeedback, (XrSession), session, (const XrHapticActionInfo *), hapticActionInfo, (const XrHapticBaseHeader *), hapticFeedback) + EXT_PROTO_XRRESULT_FUNC2(xrAttachSessionActionSets, (XrSession), session, (const XrSessionActionSetsAttachInfo *), attachInfo) + EXT_PROTO_XRRESULT_FUNC2(xrBeginFrame, (XrSession), session, (const XrFrameBeginInfo *), frameBeginInfo) + EXT_PROTO_XRRESULT_FUNC2(xrBeginSession, (XrSession), session, (const XrSessionBeginInfo *), beginInfo) + EXT_PROTO_XRRESULT_FUNC3(xrCreateAction, (XrActionSet), actionSet, (const XrActionCreateInfo *), createInfo, (XrAction *), action) + EXT_PROTO_XRRESULT_FUNC3(xrCreateActionSet, (XrInstance), instance, (const XrActionSetCreateInfo *), createInfo, (XrActionSet *), actionSet) + EXT_PROTO_XRRESULT_FUNC3(xrCreateActionSpace, (XrSession), session, (const XrActionSpaceCreateInfo *), createInfo, (XrSpace *), space) + EXT_PROTO_XRRESULT_FUNC2(xrCreateInstance, (const XrInstanceCreateInfo *), createInfo, (XrInstance *), instance) + EXT_PROTO_XRRESULT_FUNC3(xrCreateReferenceSpace, (XrSession), session, (const XrReferenceSpaceCreateInfo *), createInfo, (XrSpace *), space) + EXT_PROTO_XRRESULT_FUNC3(xrCreateSession, (XrInstance), instance, (const XrSessionCreateInfo *), createInfo, (XrSession *), session) + EXT_PROTO_XRRESULT_FUNC3(xrCreateSwapchain, (XrSession), session, (const XrSwapchainCreateInfo *), createInfo, (XrSwapchain *), swapchain) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyAction, (XrAction), action) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyActionSet, (XrActionSet), actionSet) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyInstance, (XrInstance), instance) + EXT_PROTO_XRRESULT_FUNC1(xrDestroySession, (XrSession), session) + EXT_PROTO_XRRESULT_FUNC1(xrDestroySpace, (XrSpace), space) + EXT_PROTO_XRRESULT_FUNC1(xrDestroySwapchain, (XrSwapchain), swapchain) + EXT_PROTO_XRRESULT_FUNC2(xrEndFrame, (XrSession), session, (const XrFrameEndInfo *), frameEndInfo) + EXT_PROTO_XRRESULT_FUNC1(xrEndSession, (XrSession), session) + EXT_PROTO_XRRESULT_FUNC3(xrEnumerateApiLayerProperties, (uint32_t), propertyCapacityInput, (uint32_t *), propertyCountOutput, (XrApiLayerProperties *), properties) + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateInstanceExtensionProperties, (const char *), layerName, (uint32_t), propertyCapacityInput, (uint32_t *), propertyCountOutput, (XrExtensionProperties *), properties) + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateReferenceSpaces, (XrSession), session, (uint32_t), spaceCapacityInput, (uint32_t *), spaceCountOutput, (XrReferenceSpaceType *), spaces) + EXT_PROTO_XRRESULT_FUNC4(xrEnumerateSwapchainFormats, (XrSession), session, (uint32_t), formatCapacityInput, (uint32_t *), formatCountOutput, (int64_t *), formats) + EXT_PROTO_XRRESULT_FUNC5(xrEnumerateViewConfigurations, (XrInstance), instance, (XrSystemId), systemId, (uint32_t), viewConfigurationTypeCapacityInput, (uint32_t *), viewConfigurationTypeCountOutput, (XrViewConfigurationType *), viewConfigurationTypes) + EXT_PROTO_XRRESULT_FUNC6(xrEnumerateViewConfigurationViews, (XrInstance), instance, (XrSystemId), systemId, (XrViewConfigurationType), viewConfigurationType, (uint32_t), viewCapacityInput, (uint32_t *), viewCountOutput, (XrViewConfigurationView *), views) + EXT_PROTO_XRRESULT_FUNC3(xrGetActionStateBoolean, (XrSession), session, (const XrActionStateGetInfo *), getInfo, (XrActionStateBoolean *), state) + EXT_PROTO_XRRESULT_FUNC3(xrGetActionStateFloat, (XrSession), session, (const XrActionStateGetInfo *), getInfo, (XrActionStateFloat *), state) + EXT_PROTO_XRRESULT_FUNC3(xrGetActionStateVector2f, (XrSession), session, (const XrActionStateGetInfo *), getInfo, (XrActionStateVector2f *), state) + EXT_PROTO_XRRESULT_FUNC3(xrGetCurrentInteractionProfile, (XrSession), session, (XrPath), topLevelUserPath, (XrInteractionProfileState *), interactionProfile) + EXT_PROTO_XRRESULT_FUNC2(xrGetInstanceProperties, (XrInstance), instance, (XrInstanceProperties *), instanceProperties) + EXT_PROTO_XRRESULT_FUNC3(xrGetSystem, (XrInstance), instance, (const XrSystemGetInfo *), getInfo, (XrSystemId *), systemId) + EXT_PROTO_XRRESULT_FUNC3(xrGetSystemProperties, (XrInstance), instance, (XrSystemId), systemId, (XrSystemProperties *), properties) + EXT_PROTO_XRRESULT_FUNC4(xrLocateSpace, (XrSpace), space, (XrSpace), baseSpace, (XrTime), time, (XrSpaceLocation *), location) + EXT_PROTO_XRRESULT_FUNC6(xrLocateViews, (XrSession), session, (const XrViewLocateInfo *), viewLocateInfo, (XrViewState *), viewState, (uint32_t), viewCapacityInput, (uint32_t *), viewCountOutput, (XrView *), views) + EXT_PROTO_XRRESULT_FUNC5(xrPathToString, (XrInstance), instance, (XrPath), path, (uint32_t), bufferCapacityInput, (uint32_t *), bufferCountOutput, (char *), buffer) + EXT_PROTO_XRRESULT_FUNC2(xrPollEvent, (XrInstance), instance, (XrEventDataBuffer *), eventData) + EXT_PROTO_XRRESULT_FUNC2(xrReleaseSwapchainImage, (XrSwapchain), swapchain, (const XrSwapchainImageReleaseInfo *), releaseInfo) + EXT_PROTO_XRRESULT_FUNC3(xrResultToString, (XrInstance), instance, (XrResult), value, (char *), buffer) + EXT_PROTO_XRRESULT_FUNC3(xrStringToPath, (XrInstance), instance, (const char *), pathString, (XrPath *), path) + EXT_PROTO_XRRESULT_FUNC2(xrSuggestInteractionProfileBindings, (XrInstance), instance, (const XrInteractionProfileSuggestedBinding *), suggestedBindings) + EXT_PROTO_XRRESULT_FUNC2(xrSyncActions, (XrSession), session, (const XrActionsSyncInfo *), syncInfo) + EXT_PROTO_XRRESULT_FUNC3(xrWaitFrame, (XrSession), session, (const XrFrameWaitInfo *), frameWaitInfo, (XrFrameState *), frameState) + EXT_PROTO_XRRESULT_FUNC2(xrWaitSwapchainImage, (XrSwapchain), swapchain, (const XrSwapchainImageWaitInfo *), waitInfo) + // instance bool create_instance(); bool get_system_info(); @@ -149,13 +223,13 @@ private: bool setup_spaces(); bool load_supported_swapchain_formats(); bool is_swapchain_format_supported(int64_t p_swapchain_format); - bool create_main_swapchain(); + bool create_swapchains(); void destroy_session(); // swapchains - bool create_swapchain(int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); - bool acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index); - bool release_image(XrSwapchain p_swapchain); + bool create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); + bool acquire_image(OpenXRSwapChainInfo &p_swapchain); + bool release_image(OpenXRSwapChainInfo &p_swapchain); // action map struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. @@ -212,9 +286,7 @@ private: // convencience void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); -protected: - friend class OpenXRVulkanExtension; - +public: XrInstance get_instance() const { return instance; }; XrSystemId get_system_id() const { return system_id; }; XrSession get_session() const { return session; }; @@ -227,10 +299,13 @@ protected: XRPose::TrackingConfidence transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform); void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); -public: + bool xr_result(XrResult result, const char *format, Array args = Array()) const; + bool is_path_supported(const String &p_path); + static bool openxr_is_enabled(bool p_check_run_in_editor = true); static OpenXRAPI *get_singleton(); + XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr); String get_error_string(XrResult result); String get_swapchain_format_name(int64_t p_swapchain_format) const; @@ -243,8 +318,9 @@ public: bool initialize_session(); void finish(); - 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; }; + 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; } Size2 get_recommended_target_size(); XRPose::TrackingConfidence get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); @@ -254,9 +330,16 @@ public: void pre_render(); bool pre_draw_viewport(RID p_render_target); + RID get_color_texture(); + RID get_depth_texture(); void post_draw_viewport(RID p_render_target); void end_frame(); + // Display refresh rate + float get_display_refresh_rate() const; + void set_display_refresh_rate(float p_refresh_rate); + Array get_available_display_refresh_rates() const; + // action map String get_default_action_map_resource_name(); @@ -288,6 +371,9 @@ public: XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); bool trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + void register_composition_layer_provider(OpenXRCompositionLayerProvider *provider); + void unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider); + OpenXRAPI(); ~OpenXRAPI(); }; diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 6c2f08e21d..77660eb6f0 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -41,6 +41,13 @@ void OpenXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("session_focussed")); ADD_SIGNAL(MethodInfo("session_visible")); ADD_SIGNAL(MethodInfo("pose_recentered")); + + // Display refresh rate + ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &OpenXRInterface::get_display_refresh_rate); + ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + + ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } StringName OpenXRInterface::get_name() const { @@ -89,7 +96,7 @@ void OpenXRInterface::_load_action_map() { // This may seem a bit duplicitous to a little bit of background info here. // OpenXRActionMap (with all its sub resource classes) is a class that allows us to configure and store an action map in. - // This gives the user the ability to edit the action map in a UI and customise the actions. + // This gives the user the ability to edit the action map in a UI and customize the actions. // OpenXR however requires us to submit an action map and it takes over from that point and we can no longer change it. // This system does that push and we store the info needed to then work with this action map going forward. @@ -132,10 +139,10 @@ void OpenXRInterface::_load_action_map() { if (action_map.is_valid()) { HashMap<Ref<OpenXRAction>, Action *> xr_actions; - Array action_sets = action_map->get_action_sets(); - for (int i = 0; i < action_sets.size(); i++) { + Array action_set_array = action_map->get_action_sets(); + for (int i = 0; i < action_set_array.size(); i++) { // Create our action set - Ref<OpenXRActionSet> xr_action_set = action_sets[i]; + Ref<OpenXRActionSet> xr_action_set = action_set_array[i]; ActionSet *action_set = create_action_set(xr_action_set->get_name(), xr_action_set->get_localized_name(), xr_action_set->get_priority()); if (!action_set) { continue; @@ -147,62 +154,65 @@ void OpenXRInterface::_load_action_map() { Ref<OpenXRAction> xr_action = actions[j]; PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); - Vector<Tracker *> trackers; + Vector<Tracker *> trackers_for_action; for (int k = 0; k < toplevel_paths.size(); k++) { - Tracker *tracker = find_tracker(toplevel_paths[k], true); - if (tracker) { - trackers.push_back(tracker); + // Only check for our tracker if our path is supported. + if (openxr_api->is_path_supported(toplevel_paths[k])) { + Tracker *tracker = find_tracker(toplevel_paths[k], true); + if (tracker) { + trackers_for_action.push_back(tracker); + } } } - Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers); - if (action) { - // we link our actions back to our trackers so we know which actions to check when we're processing our trackers - for (int t = 0; t < trackers.size(); t++) { - link_action_to_tracker(trackers[t], action); + // Only add our action if we have at least one valid toplevel path + if (trackers_for_action.size() > 0) { + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers_for_action); + if (action) { + // add this to our map for creating our interaction profiles + xr_actions[xr_action] = action; } - - // add this to our map for creating our interaction profiles - xr_actions[xr_action] = action; } } } // now do our suggestions - Array interaction_profiles = action_map->get_interaction_profiles(); - for (int i = 0; i < interaction_profiles.size(); i++) { - Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i]; + Array interaction_profile_array = action_map->get_interaction_profiles(); + for (int i = 0; i < interaction_profile_array.size(); i++) { + Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profile_array[i]; // Note, we can only have one entry per interaction profile so if it already exists we clear it out RID ip = openxr_api->interaction_profile_create(xr_interaction_profile->get_interaction_profile_path()); - openxr_api->interaction_profile_clear_bindings(ip); - - Array xr_bindings = xr_interaction_profile->get_bindings(); - for (int j = 0; j < xr_bindings.size(); j++) { - Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; - Ref<OpenXRAction> xr_action = xr_binding->get_action(); - - Action *action = nullptr; - if (xr_actions.has(xr_action)) { - action = xr_actions[xr_action]; - } else { - print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); - continue; - } + if (ip.is_valid()) { + openxr_api->interaction_profile_clear_bindings(ip); + + Array xr_bindings = xr_interaction_profile->get_bindings(); + for (int j = 0; j < xr_bindings.size(); j++) { + Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; + Ref<OpenXRAction> xr_action = xr_binding->get_action(); + + Action *action = nullptr; + if (xr_actions.has(xr_action)) { + action = xr_actions[xr_action]; + } else { + print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); + continue; + } - PackedStringArray paths = xr_binding->get_paths(); - for (int k = 0; k < paths.size(); k++) { - openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]); + PackedStringArray paths = xr_binding->get_paths(); + for (int k = 0; k < paths.size(); k++) { + openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]); + } } - } - // Now submit our suggestions - openxr_api->interaction_profile_suggest_bindings(ip); + // Now submit our suggestions + openxr_api->interaction_profile_suggest_bindings(ip); - // And record it in our array so we can clean it up later on - if (interaction_profiles.has(ip)) { - interaction_profiles.push_back(ip); + // And record it in our array so we can clean it up later on + if (interaction_profile_array.has(ip)) { + interaction_profile_array.push_back(ip); + } } } } @@ -280,6 +290,13 @@ OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); p_action_set->actions.push_back(action); + // we link our actions back to our trackers so we know which actions to check when we're processing our trackers + for (int i = 0; i < p_trackers.size(); i++) { + if (p_trackers[i]->actions.find(action) == -1) { + p_trackers[i]->actions.push_back(action); + } + } + return action; } @@ -328,6 +345,8 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_ return nullptr; } + ERR_FAIL_COND_V(!openxr_api->is_path_supported(p_tracker_name), nullptr); + // Create our RID RID tracker_rid = openxr_api->tracker_create(p_tracker_name); ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); @@ -336,7 +355,7 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_ Ref<XRPositionalTracker> positional_tracker; positional_tracker.instantiate(); - // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. + // We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these. if (p_tracker_name == "/user/hand/left") { positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); positional_tracker->set_tracker_name("left_hand"); @@ -387,12 +406,6 @@ void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_p } } -void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { - if (p_tracker->actions.find(p_action) == -1) { - p_tracker->actions.push_back(p_action); - } -} - void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); @@ -445,9 +458,18 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) { ERR_FAIL_NULL(openxr_api); + Action *action = find_action(p_action_name); ERR_FAIL_NULL(action); - Tracker *tracker = find_tracker(p_tracker_name); + + // We need to map our tracker name to our OpenXR name for our inbuild names. + String tracker_name = p_tracker_name; + if (tracker_name == "left_hand") { + tracker_name = "/user/hand/left"; + } else if (tracker_name == "right_hand") { + tracker_name = "/user/hand/right"; + } + Tracker *tracker = find_tracker(tracker_name); ERR_FAIL_NULL(tracker); // TODO OpenXR does not support delay, so we may need to add support for that somehow... @@ -569,6 +591,36 @@ bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { return false; } +float OpenXRInterface::get_display_refresh_rate() const { + if (openxr_api == nullptr) { + return 0.0; + } else if (!openxr_api->is_initialized()) { + return 0.0; + } else { + return openxr_api->get_display_refresh_rate(); + } +} + +void OpenXRInterface::set_display_refresh_rate(float p_refresh_rate) { + if (openxr_api == nullptr) { + return; + } else if (!openxr_api->is_initialized()) { + return; + } else { + openxr_api->set_display_refresh_rate(p_refresh_rate); + } +} + +Array OpenXRInterface::get_available_display_refresh_rates() const { + if (openxr_api == nullptr) { + return Array(); + } else if (!openxr_api->is_initialized()) { + return Array(); + } else { + return openxr_api->get_available_display_refresh_rates(); + } +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); @@ -614,6 +666,7 @@ Transform3D OpenXRInterface::get_camera_transform() { Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, Transform3D()); + ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), Transform3D(), "View index outside bounds."); Transform3D t; if (openxr_api && openxr_api->get_view_transform(p_view, t)) { @@ -633,6 +686,7 @@ Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Trans Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { Projection cm; + ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), cm, "View index outside bounds."); if (openxr_api) { if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { @@ -646,6 +700,22 @@ Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_as return cm; } +RID OpenXRInterface::get_color_texture() { + if (openxr_api) { + return openxr_api->get_color_texture(); + } else { + return RID(); + } +} + +RID OpenXRInterface::get_depth_texture() { + if (openxr_api) { + return openxr_api->get_depth_texture(); + } else { + return RID(); + } +} + void OpenXRInterface::process() { if (openxr_api) { // do our normal process @@ -705,6 +775,7 @@ bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; +#ifndef ANDROID_ENABLED // If separate HMD we should output one eye to screen if (p_screen_rect != Rect2()) { BlitToScreen blit; @@ -730,6 +801,7 @@ Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, co blit.dst_rect = dst_rect; blit_to_screen.push_back(blit); } +#endif if (openxr_api) { openxr_api->post_draw_viewport(p_render_target); @@ -744,6 +816,24 @@ void OpenXRInterface::end_frame() { } } +bool OpenXRInterface::is_passthrough_supported() { + return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported(); +} + +bool OpenXRInterface::is_passthrough_enabled() { + return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled(); +} + +bool OpenXRInterface::start_passthrough() { + return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough(); +} + +void OpenXRInterface::stop_passthrough() { + if (passthrough_wrapper) { + passthrough_wrapper->stop_passthrough(); + } +} + void OpenXRInterface::on_state_ready() { emit_signal(SNAME("session_begun")); } @@ -774,6 +864,8 @@ OpenXRInterface::OpenXRInterface() { _set_default_pos(head_transform, 1.0, 0); _set_default_pos(transform_for_view[0], 1.0, 1); _set_default_pos(transform_for_view[1], 1.0, 2); + + passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton(); } OpenXRInterface::~OpenXRInterface() { diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index a99012fd1d..454612346f 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -37,6 +37,8 @@ #include "action_map/openxr_action_map.h" #include "openxr_api.h" +#include "extensions/openxr_fb_passthrough_extension_wrapper.h" + // declare some default strings #define INTERACTION_PROFILE_NONE "/interaction_profiles/none" @@ -47,6 +49,7 @@ private: OpenXRAPI *openxr_api = nullptr; bool initialized = false; XRInterface::TrackingStatus tracking_state; + OpenXRFbPassthroughExtensionWrapper *passthrough_wrapper = nullptr; // At a minimum we need a tracker for our head Ref<XRPositionalTracker> head; @@ -88,7 +91,6 @@ private: void free_actions(ActionSet *p_action_set); Tracker *find_tracker(const String &p_tracker_name, bool p_create = false); - void link_action_to_tracker(Tracker *p_tracker, Action *p_action); void handle_tracker(Tracker *p_tracker); void free_trackers(); @@ -117,18 +119,30 @@ public: virtual XRInterface::PlayAreaMode get_play_area_mode() const override; virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + float get_display_refresh_rate() const; + void set_display_refresh_rate(float p_refresh_rate); + Array get_available_display_refresh_rates() const; + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + virtual RID get_color_texture() override; + virtual RID get_depth_texture() override; + virtual void process() override; virtual void pre_render() override; bool pre_draw_viewport(RID p_render_target) override; virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void end_frame() override; + virtual bool is_passthrough_supported() override; + virtual bool is_passthrough_enabled() override; + virtual bool start_passthrough() override; + virtual void stop_passthrough() override; + void on_state_ready(); void on_state_visible(); void on_state_focused(); diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index c765f169dc..5694fffe39 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -38,6 +38,8 @@ #include "action_map/openxr_action_set.h" #include "action_map/openxr_interaction_profile.h" +#include "scene/openxr_hand.h" + #ifdef TOOLS_ENABLED #include "editor/editor_node.h" @@ -82,6 +84,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OpenXRIPBinding); GDREGISTER_CLASS(OpenXRInteractionProfile); + GDREGISTER_CLASS(OpenXRHand); + XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { openxr_interface.instantiate(); diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp new file mode 100644 index 0000000000..588b818148 --- /dev/null +++ b/modules/openxr/scene/openxr_hand.cpp @@ -0,0 +1,307 @@ +/*************************************************************************/ +/* openxr_hand.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "../extensions/openxr_hand_tracking_extension.h" +#include "../openxr_api.h" + +#include "openxr_hand.h" +#include "scene/3d/skeleton_3d.h" +#include "servers/xr_server.h" + +void OpenXRHand::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_hand", "hand"), &OpenXRHand::set_hand); + ClassDB::bind_method(D_METHOD("get_hand"), &OpenXRHand::get_hand); + + ClassDB::bind_method(D_METHOD("set_hand_skeleton", "hand_skeleton"), &OpenXRHand::set_hand_skeleton); + ClassDB::bind_method(D_METHOD("get_hand_skeleton"), &OpenXRHand::get_hand_skeleton); + + ClassDB::bind_method(D_METHOD("set_motion_range", "motion_range"), &OpenXRHand::set_motion_range); + ClassDB::bind_method(D_METHOD("get_motion_range"), &OpenXRHand::get_motion_range); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton"); + + BIND_ENUM_CONSTANT(HAND_LEFT); + BIND_ENUM_CONSTANT(HAND_RIGHT); + BIND_ENUM_CONSTANT(HAND_MAX); + + BIND_ENUM_CONSTANT(MOTION_RANGE_UNOBSTRUCTED); + BIND_ENUM_CONSTANT(MOTION_RANGE_CONFORM_TO_CONTROLLER); + BIND_ENUM_CONSTANT(MOTION_RANGE_MAX); +} + +OpenXRHand::OpenXRHand() { + openxr_api = OpenXRAPI::get_singleton(); + hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); +} + +void OpenXRHand::set_hand(const Hands p_hand) { + ERR_FAIL_INDEX(p_hand, HAND_MAX); + + hand = p_hand; +} + +OpenXRHand::Hands OpenXRHand::get_hand() const { + return hand; +} + +void OpenXRHand::set_hand_skeleton(const NodePath &p_hand_skeleton) { + hand_skeleton = p_hand_skeleton; + + // TODO if inside tree call _get_bones() +} + +void OpenXRHand::set_motion_range(const MotionRange p_motion_range) { + ERR_FAIL_INDEX(p_motion_range, MOTION_RANGE_MAX); + motion_range = p_motion_range; + + _set_motion_range(); +} + +OpenXRHand::MotionRange OpenXRHand::get_motion_range() const { + return motion_range; +} + +NodePath OpenXRHand::get_hand_skeleton() const { + return hand_skeleton; +} + +void OpenXRHand::_set_motion_range() { + if (!hand_tracking_ext) { + return; + } + + XrHandJointsMotionRangeEXT xr_motion_range; + switch (motion_range) { + case MOTION_RANGE_UNOBSTRUCTED: + xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; + break; + case MOTION_RANGE_CONFORM_TO_CONTROLLER: + xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; + break; + default: + xr_motion_range = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; + break; + } + + hand_tracking_ext->set_motion_range(hand, xr_motion_range); +} + +Skeleton3D *OpenXRHand::get_skeleton() { + if (!has_node(hand_skeleton)) { + return nullptr; + } + + Node *node = get_node(hand_skeleton); + if (!node) { + return nullptr; + } + + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); + return skeleton; +} + +void OpenXRHand::_get_bones() { + const char *bone_names[XR_HAND_JOINT_COUNT_EXT] = { + "Palm", + "Wrist", + "Thumb_Metacarpal", + "Thumb_Proximal", + "Thumb_Distal", + "Thumb_Tip", + "Index_Metacarpal", + "Index_Proximal", + "Index_Intermediate", + "Index_Distal", + "Index_Tip", + "Middle_Metacarpal", + "Middle_Proximal", + "Middle_Intermediate", + "Middle_Distal", + "Middle_Tip", + "Ring_Metacarpal", + "Ring_Proximal", + "Ring_Intermediate", + "Ring_Distal", + "Ring_Tip", + "Little_Metacarpal", + "Little_Proximal", + "Little_Intermediate", + "Little_Distal", + "Little_Tip", + }; + + // reset JIC + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + bones[i] = -1; + } + + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + + // We cast to spatials which should allow us to use any subclass of that. + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + String bone_name = bone_names[i]; + if (hand == 0) { + bone_name += String("_L"); + } else { + bone_name += String("_R"); + } + + bones[i] = skeleton->find_bone(bone_name); + if (bones[i] == -1) { + print_line("Couldn't obtain bone for", bone_name); + } + } +} + +void OpenXRHand::_update_skeleton() { + if (openxr_api == nullptr || !openxr_api->is_initialized()) { + return; + } else if (hand_tracking_ext == nullptr || !hand_tracking_ext->get_active()) { + return; + } + + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + + // we cache our transforms so we can quickly calculate local transforms + XRPose::TrackingConfidence confidences[XR_HAND_JOINT_COUNT_EXT]; + Quaternion quaternions[XR_HAND_JOINT_COUNT_EXT]; + Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT]; + Vector3 positions[XR_HAND_JOINT_COUNT_EXT]; + + const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand); + const float ws = XRServer::get_singleton()->get_world_scale(); + + if (hand_tracker->is_initialized && hand_tracker->locations.isActive) { + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + confidences[i] = XRPose::XR_TRACKING_CONFIDENCE_NONE; + quaternions[i] = Quaternion(); + positions[i] = Vector3(); + + const auto &location = hand_tracker->joint_locations[i]; + const auto &pose = location.pose; + + if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + if (pose.orientation.x != 0 || pose.orientation.y != 0 || pose.orientation.y != 0 || pose.orientation.w != 0) { + quaternions[i] = Quaternion(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w); + inv_quaternions[i] = quaternions[i].inverse(); + + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + confidences[i] = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + positions[i] = Vector3(pose.position.x * ws, pose.position.y * ws, pose.position.z * ws); + + // TODO get inverse of position, we'll do this later. For now we're ignoring bone positions which generally works better anyway + } else { + confidences[i] = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } + } + } + } + + if (confidences[XR_HAND_JOINT_PALM_EXT] != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // now update our skeleton + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + if (bones[i] != -1) { + int bone = bones[i]; + int parent = skeleton->get_bone_parent(bone); + + // Get our target quaternion + Quaternion q = quaternions[i]; + + // get local translation, parent should already be processed + if (parent == -1) { + // use our palm location here, that is what we are tracking + q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q; + } else { + int found = false; + for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) { + if (bones[b] == parent) { + q = inv_quaternions[b] * q; + found = true; + } + } + } + + // And get the movement from our rest position + // Transform3D rest = skeleton->get_bone_rest(bones[i]); + // q = rest.basis.get_quaternion().inverse() * q; + + // and set our pose + // skeleton->set_bone_pose_position(bones[i], v); + skeleton->set_bone_pose_rotation(bones[i], q); + } + } + + Transform3D t; + t.basis = Basis(quaternions[XR_HAND_JOINT_PALM_EXT]); + t.origin = positions[XR_HAND_JOINT_PALM_EXT]; + set_transform(t); + + // show it + set_visible(true); + } else { + // hide it + set_visible(false); + } + } else { + // hide it + set_visible(false); + } +} + +void OpenXRHand::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _get_bones(); + + set_process_internal(true); + } break; + case NOTIFICATION_EXIT_TREE: { + set_process_internal(false); + + // reset + for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { + bones[i] = -1; + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _update_skeleton(); + } break; + default: { + } break; + } +} diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h new file mode 100644 index 0000000000..f8499cc172 --- /dev/null +++ b/modules/openxr/scene/openxr_hand.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* openxr_hand.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_H +#define OPENXR_HAND_H + +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" + +class OpenXRAPI; +class OpenXRHandTrackingExtension; + +class OpenXRHand : public Node3D { + GDCLASS(OpenXRHand, Node3D); + +public: + enum Hands { + HAND_LEFT, + HAND_RIGHT, + HAND_MAX + }; + + enum MotionRange { + MOTION_RANGE_UNOBSTRUCTED, + MOTION_RANGE_CONFORM_TO_CONTROLLER, + MOTION_RANGE_MAX + }; + +private: + OpenXRAPI *openxr_api = nullptr; + OpenXRHandTrackingExtension *hand_tracking_ext = nullptr; + + Hands hand = HAND_LEFT; + MotionRange motion_range = MOTION_RANGE_UNOBSTRUCTED; + NodePath hand_skeleton; + + int64_t bones[XR_HAND_JOINT_COUNT_EXT]; + + void _set_motion_range(); + + Skeleton3D *get_skeleton(); + void _get_bones(); + void _update_skeleton(); + +protected: + static void _bind_methods(); + +public: + OpenXRHand(); + + void set_hand(const Hands p_hand); + Hands get_hand() const; + + void set_motion_range(const MotionRange p_motion_range); + MotionRange get_motion_range() const; + + void set_hand_skeleton(const NodePath &p_hand_skeleton); + NodePath get_hand_skeleton() const; + + void _notification(int p_what); +}; + +VARIANT_ENUM_CAST(OpenXRHand::Hands) +VARIANT_ENUM_CAST(OpenXRHand::MotionRange) + +#endif // OPENXR_HAND_H diff --git a/modules/openxr/util.h b/modules/openxr/util.h new file mode 100644 index 0000000000..1b5e0451dc --- /dev/null +++ b/modules/openxr/util.h @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* util.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 UTIL_H +#define UTIL_H + +#define UNPACK(...) __VA_ARGS__ + +#define INIT_XR_FUNC_V(openxr_api, name) \ + do { \ + XrResult get_instance_proc_addr_result; \ + get_instance_proc_addr_result = openxr_api->get_instance_proc_addr(#name, (PFN_xrVoidFunction *)&name##_ptr); \ + ERR_FAIL_COND_V(XR_FAILED(get_instance_proc_addr_result), false); \ + } while (0) + +#define EXT_INIT_XR_FUNC_V(name) INIT_XR_FUNC_V(openxr_api, name) +#define OPENXR_API_INIT_XR_FUNC_V(name) INIT_XR_FUNC_V(this, name) + +#define INIT_XR_FUNC(openxr_api, name) \ + do { \ + XrResult get_instance_proc_addr_result; \ + get_instance_proc_addr_result = openxr_api->get_instance_proc_addr(#name, (PFN_xrVoidFunction *)&name##_ptr); \ + ERR_FAIL_COND(XR_FAILED(get_instance_proc_addr_result)); \ + } while (0) + +#define EXT_INIT_XR_FUNC(name) INIT_XR_FUNC(openxr_api, name) +#define OPENXR_API_INIT_XR_FUNC(name) INIT_XR_FUNC(this, name) + +#define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1); \ + } + +#define EXT_PROTO_XRRESULT_FUNC2(func_name, arg1_type, arg1, arg2_type, arg2) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1, p_##arg2); \ + } + +#define EXT_PROTO_XRRESULT_FUNC3(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3); \ + } + +#define EXT_PROTO_XRRESULT_FUNC4(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4); \ + } + +#define EXT_PROTO_XRRESULT_FUNC5(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4, UNPACK arg5_type p_##arg5) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4, p_##arg5); \ + } + +#define EXT_PROTO_XRRESULT_FUNC6(func_name, arg1_type, arg1, arg2_type, arg2, arg3_type, arg3, arg4_type, arg4, arg5_type, arg5, arg6_type, arg6) \ + PFN_##func_name func_name##_ptr = nullptr; \ + XRAPI_ATTR XrResult XRAPI_CALL func_name(UNPACK arg1_type p_##arg1, UNPACK arg2_type p_##arg2, UNPACK arg3_type p_##arg3, UNPACK arg4_type p_##arg4, UNPACK arg5_type p_##arg5, UNPACK arg6_type p_##arg6) const { \ + if (!func_name##_ptr) { \ + return XR_ERROR_HANDLE_INVALID; \ + } \ + return (*func_name##_ptr)(p_##arg1, p_##arg2, p_##arg3, p_##arg4, p_##arg5, p_##arg6); \ + } + +#endif // UTIL_H |