diff options
Diffstat (limited to 'modules/openxr/openxr_api.cpp')
-rw-r--r-- | modules/openxr/openxr_api.cpp | 333 |
1 files changed, 239 insertions, 94 deletions
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 8a67462613..6b8f140923 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* openxr_api.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. */ -/*************************************************************************/ +/**************************************************************************/ +/* openxr_api.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "openxr_api.h" #include "openxr_util.h" @@ -42,22 +42,49 @@ #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_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 // ANDROID_ENABLED +#ifdef X11_ENABLED +#include OPENGL_INCLUDE_H +#define GL_GLEXT_PROTOTYPES 1 +#define GL3_PROTOTYPES 1 +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/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" OpenXRAPI *OpenXRAPI::singleton = nullptr; +Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers; bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) { // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" @@ -74,10 +101,6 @@ bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) { } } -OpenXRAPI *OpenXRAPI::get_singleton() { - return singleton; -} - String OpenXRAPI::get_default_action_map_resource_name() { String name = GLOBAL_GET("xr/openxr/default_action_map"); @@ -178,17 +201,79 @@ bool OpenXRAPI::is_extension_supported(const String &p_extension) const { 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. +bool OpenXRAPI::is_extension_enabled(const String &p_extension) const { + CharString extension = p_extension.ascii(); - for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { - if (!wrapper->is_path_supported(p_path)) { - return false; + for (int i = 0; i < enabled_extensions.size(); i++) { + if (strcmp(enabled_extensions[i].ptr(), extension.ptr()) == 0) { + return true; } } + return false; +} + +bool OpenXRAPI::is_top_level_path_supported(const String &p_toplevel_path) { + String required_extension = OpenXRInteractionProfileMetaData::get_singleton()->get_top_level_extension(p_toplevel_path); + + // If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error. + ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported toplevel path " + p_toplevel_path); + + if (required_extension == "") { + // no extension needed, core top level are always "supported", they just won't be used if not really supported + return true; + } + + if (!is_extension_enabled(required_extension)) { + // It is very likely we have top level paths for which the extension is not available so don't flood the logs with unnecessary spam. + print_verbose("OpenXR: Top level path " + p_toplevel_path + " requires extension " + required_extension); + return false; + } + + return true; +} + +bool OpenXRAPI::is_interaction_profile_supported(const String &p_ip_path) { + String required_extension = OpenXRInteractionProfileMetaData::get_singleton()->get_interaction_profile_extension(p_ip_path); + + // If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error. + ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported interaction profile " + p_ip_path); + + if (required_extension == "") { + // no extension needed, core interaction profiles are always "supported", they just won't be used if not really supported + return true; + } + + if (!is_extension_enabled(required_extension)) { + // It is very likely we have interaction profiles for which the extension is not available so don't flood the logs with unnecessary spam. + print_verbose("OpenXR: Interaction profile " + p_ip_path + " requires extension " + required_extension); + return false; + } + + return true; +} + +bool OpenXRAPI::interaction_profile_supports_io_path(const String &p_ip_path, const String &p_io_path) { + if (!is_interaction_profile_supported(p_ip_path)) { + return false; + } + + const OpenXRInteractionProfileMetaData::IOPath *io_path = OpenXRInteractionProfileMetaData::get_singleton()->get_io_path(p_ip_path, p_io_path); + + // If the io_path is not part of our meta data we've likely got a misspelled name or a bad action map, report + ERR_FAIL_NULL_V_MSG(io_path, false, "OpenXR: Unsupported io path " + String(p_ip_path) + String(p_io_path)); + + if (io_path->openxr_extension_name == "") { + // no extension needed, core io paths are always "supported", they just won't be used if not really supported + return true; + } + + if (!is_extension_enabled(io_path->openxr_extension_name)) { + // It is very likely we have io paths for which the extension is not available so don't flood the logs with unnecessary spam. + print_verbose("OpenXR: IO path " + String(p_ip_path) + String(p_io_path) + " requires extension " + io_path->openxr_extension_name); + return false; + } + return true; } @@ -211,7 +296,7 @@ bool OpenXRAPI::create_instance() { // Append the extensions requested by the registered extension wrappers. HashMap<String, bool *> requested_extensions; for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { - const HashMap<String, bool *> &wrapper_request_extensions = wrapper->get_request_extensions(); + const HashMap<String, bool *> &wrapper_request_extensions = wrapper->get_requested_extensions(); // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end()); for (auto &requested_extension : wrapper_request_extensions) { @@ -219,14 +304,6 @@ bool OpenXRAPI::create_instance() { } } - // Add optional extensions for controllers that may be supported. - // Overkill to create extension classes for this. - requested_extensions[XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME] = &ext_hp_mixed_reality_available; - requested_extensions[XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME] = &ext_samsung_odyssey_available; - requested_extensions[XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_vive_cosmos_available; - requested_extensions[XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_vive_focus3_available; - requested_extensions[XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME] = &ext_huawei_controller_available; - // Check which extensions are supported enabled_extensions.clear(); @@ -253,6 +330,7 @@ bool OpenXRAPI::create_instance() { Vector<const char *> extension_ptrs; for (int i = 0; i < enabled_extensions.size(); i++) { + print_verbose(String("OpenXR: Enabling extension ") + String(enabled_extensions[i])); extension_ptrs.push_back(enabled_extensions[i].get_data()); } @@ -268,9 +346,17 @@ bool OpenXRAPI::create_instance() { XR_CURRENT_API_VERSION // apiVersion }; + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_instance_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + XrInstanceCreateInfo instance_create_info = { XR_TYPE_INSTANCE_CREATE_INFO, // type - nullptr, // next + next_pointer, // next 0, // createFlags application_info, // applicationInfo 0, // enabledApiLayerCount, need to find out if we need support for this? @@ -443,12 +529,12 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType for (uint32_t i = 0; i < view_count; i++) { print_verbose("OpenXR: Found supported view configuration view"); - print_verbose(String(" - width: ") + view_configuration_views[i].maxImageRectWidth); - print_verbose(String(" - height: ") + view_configuration_views[i].maxImageRectHeight); - print_verbose(String(" - sample count: ") + view_configuration_views[i].maxSwapchainSampleCount); - print_verbose(String(" - recommended render width: ") + view_configuration_views[i].recommendedImageRectWidth); - print_verbose(String(" - recommended render height: ") + view_configuration_views[i].recommendedImageRectHeight); - print_verbose(String(" - recommended render sample count: ") + view_configuration_views[i].recommendedSwapchainSampleCount); + 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)); } return true; @@ -474,6 +560,12 @@ void OpenXRAPI::destroy_instance() { instance = XR_NULL_HANDLE; } enabled_extensions.clear(); + + if (graphics_extension != nullptr) { + unregister_extension_wrapper(graphics_extension); + memdelete(graphics_extension); + graphics_extension = nullptr; + } } bool OpenXRAPI::create_session() { @@ -690,7 +782,7 @@ bool OpenXRAPI::create_swapchains() { print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); } - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_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)) { + 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; } } @@ -702,9 +794,10 @@ bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); // We create our depth swapchain if: + // - we've enabled submitting depth buffer // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (submit_depth_buffer && 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; @@ -752,13 +845,13 @@ bool OpenXRAPI::create_swapchains() { 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) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; - depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = recommended_size.width; @@ -1028,6 +1121,30 @@ bool OpenXRAPI::on_state_exiting() { return true; } +void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { + ERR_FAIL_COND(is_initialized()); + + form_factor = p_form_factor; +} + +void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { + ERR_FAIL_COND(is_initialized()); + + view_configuration = p_view_configuration; +} + +void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_COND(is_initialized()); + + reference_space = p_reference_space; +} + +void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { + ERR_FAIL_COND(is_initialized()); + + submit_depth_buffer = p_submit_depth_buffer; +} + bool OpenXRAPI::is_initialized() { return (instance != XR_NULL_HANDLE); } @@ -1113,8 +1230,12 @@ bool OpenXRAPI::resolve_instance_openxr_symbols() { return true; } +XrResult OpenXRAPI::try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) { + return xrGetInstanceProcAddr(instance, p_name, p_addr); +} + XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) { - XrResult result = xrGetInstanceProcAddr(instance, p_name, p_addr); + XrResult result = try_get_instance_proc_addr(p_name, p_addr); if (result != XR_SUCCESS) { String error_message = String("Symbol ") + p_name + " not found in OpenXR instance."; @@ -1133,7 +1254,7 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { if (p_rendering_driver == "vulkan") { #ifdef VULKAN_ENABLED - graphics_extension = memnew(OpenXRVulkanExtension(this)); + graphics_extension = memnew(OpenXRVulkanExtension); register_extension_wrapper(graphics_extension); #else // shouldn't be possible... @@ -1141,9 +1262,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); + register_extension_wrapper(graphics_extension); #else // shouldn't be possible... ERR_FAIL_V(false); @@ -1233,6 +1353,23 @@ void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_w registered_extension_wrappers.push_back(p_extension_wrapper); } +void OpenXRAPI::unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { + registered_extension_wrappers.erase(p_extension_wrapper); +} + +void OpenXRAPI::register_extension_metadata() { + for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { + extension_wrapper->on_register_metadata(); + } +} + +void OpenXRAPI::cleanup_extension_wrappers() { + for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { + memdelete(extension_wrapper); + } + registered_extension_wrappers.clear(); +} + Size2 OpenXRAPI::get_recommended_target_size() { ERR_FAIL_NULL_V(view_configuration_views, Size2()); @@ -1647,7 +1784,7 @@ RID OpenXRAPI::get_color_texture() { } RID OpenXRAPI::get_depth_texture() { - if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + if (submit_depth_buffer && 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(); @@ -1748,6 +1885,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; @@ -1800,23 +1962,13 @@ OpenXRAPI::OpenXRAPI() { default: break; } + + submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } // reset a few things that can't be done in our class definition frame_state.predictedDisplayTime = 0; frame_state.predictedDisplayPeriod = 0; - -#ifdef ANDROID_ENABLED - // our android wrapper will initialize our android loader at this point - register_extension_wrapper(memnew(OpenXRAndroidExtension(this))); -#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))); } OpenXRAPI::~OpenXRAPI() { @@ -1826,12 +1978,6 @@ OpenXRAPI::~OpenXRAPI() { } composition_layer_providers.clear(); - // cleanup our extension wrappers - for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { - memdelete(extension_wrapper); - } - registered_extension_wrappers.clear(); - if (supported_extensions != nullptr) { memfree(supported_extensions); supported_extensions = nullptr; @@ -2251,7 +2397,7 @@ 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)) { + if (!is_interaction_profile_supported(p_name)) { // The extension enabling this path must not be active, we will silently skip this interaction profile return RID(); } @@ -2292,14 +2438,13 @@ 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); + if (!interaction_profile_supports_io_path(ip->name, p_path)) { + return false; + } + XrActionSuggestedBinding binding; Action *action = action_owner.get_or_null(p_action); |