diff options
Diffstat (limited to 'modules/openxr')
47 files changed, 9365 insertions, 0 deletions
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub new file mode 100644 index 0000000000..ff320236a7 --- /dev/null +++ b/modules/openxr/SCsub @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_openxr = env_modules.Clone() + +################################################# +# Add in our Khronos OpenXR loader + +thirdparty_obj = [] +thirdparty_dir = "#thirdparty/openxr" + +env_openxr.Prepend( + CPPPATH=[ + thirdparty_dir, + thirdparty_dir + "/include", + 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"]) + + # may need to include java parts of the openxr loader +elif env["platform"] == "linuxbsd": + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX", "XR_USE_PLATFORM_XLIB"]) + # FIXME: Review what needs to be set for Android and macOS. + env_thirdparty.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) +elif env["platform"] == "windows": + env_thirdparty.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") + +# 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") + +# add in external jsoncpp dependency +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_reader.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_value.cpp") +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") + +env.modules_sources += thirdparty_obj + +################################################# +# And include our module source + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") +env_openxr.add_source_files(module_obj, "action_map/*.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") + +env.modules_sources += module_obj + +if env["tools"]: + SConscript("editor/SCsub") + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp new file mode 100644 index 0000000000..359975a480 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.cpp @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* openxr_action.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_action.h" +#include "openxr_action_set.h" + +void OpenXRAction::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRAction::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRAction::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_action_type", "action_type"), &OpenXRAction::set_action_type); + ClassDB::bind_method(D_METHOD("get_action_type"), &OpenXRAction::get_action_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "action_type", PROPERTY_HINT_ENUM, "bool,float,vector2,pose"), "set_action_type", "get_action_type"); + + ClassDB::bind_method(D_METHOD("set_toplevel_paths", "toplevel_paths"), &OpenXRAction::set_toplevel_paths); + ClassDB::bind_method(D_METHOD("get_toplevel_paths"), &OpenXRAction::get_toplevel_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "toplevel_paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_toplevel_paths", "get_toplevel_paths"); + + BIND_ENUM_CONSTANT(OPENXR_ACTION_BOOL); + BIND_ENUM_CONSTANT(OPENXR_ACTION_FLOAT); + BIND_ENUM_CONSTANT(OPENXR_ACTION_VECTOR2); + BIND_ENUM_CONSTANT(OPENXR_ACTION_POSE); +} + +Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> action; + action.instantiate(); + action->set_name(String(p_name)); + action->set_localized_name(String(p_localized_name)); + action->set_action_type(p_action_type); + action->parse_toplevel_paths(String(p_toplevel_paths)); + + return action; +} + +String OpenXRAction::get_name_with_set() const { + String name = get_name(); + + if (action_set != nullptr) { + name = action_set->get_name() + "/" + name; + } + + return name; +} + +void OpenXRAction::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRAction::get_localized_name() const { + return localized_name; +} + +void OpenXRAction::set_action_type(const OpenXRAction::ActionType p_action_type) { + action_type = p_action_type; +} + +OpenXRAction::ActionType OpenXRAction::get_action_type() const { + return action_type; +} + +void OpenXRAction::set_toplevel_paths(const PackedStringArray p_toplevel_paths) { + toplevel_paths = p_toplevel_paths; +} + +PackedStringArray OpenXRAction::get_toplevel_paths() const { + return toplevel_paths; +} + +void OpenXRAction::add_toplevel_path(const String p_toplevel_path) { + if (!toplevel_paths.has(p_toplevel_path)) { + toplevel_paths.push_back(p_toplevel_path); + } +} + +void OpenXRAction::rem_toplevel_path(const String p_toplevel_path) { + if (toplevel_paths.has(p_toplevel_path)) { + toplevel_paths.erase(p_toplevel_path); + } +} + +void OpenXRAction::parse_toplevel_paths(const String p_toplevel_paths) { + toplevel_paths = p_toplevel_paths.split(",", false); +} diff --git a/modules/openxr/action_map/openxr_action.h b/modules/openxr/action_map/openxr_action.h new file mode 100644 index 0000000000..5e57f89133 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.h @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* openxr_action.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_ACTION_H +#define OPENXR_ACTION_H + +#include "core/io/resource.h" + +class OpenXRActionSet; + +class OpenXRAction : public Resource { + GDCLASS(OpenXRAction, Resource); + +public: + enum ActionType { + OPENXR_ACTION_BOOL, + OPENXR_ACTION_FLOAT, + OPENXR_ACTION_VECTOR2, + OPENXR_ACTION_POSE, + OPENXR_ACTION_HAPTIC, + OPENXR_ACTION_MAX + }; + +private: + String localized_name; + ActionType action_type = OPENXR_ACTION_FLOAT; + + PackedStringArray toplevel_paths; + +protected: + friend class OpenXRActionSet; + + OpenXRActionSet *action_set = nullptr; // action belongs to this action set. + + static void _bind_methods(); + +public: + static Ref<OpenXRAction> new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths); // Helper function to add and configure an action + OpenXRActionSet *get_action_set() const { return action_set; } // Get the action set this action belongs to + + String get_name_with_set() const; // Retrieve the name of this action as <action_set>/<action> + + void set_localized_name(const String p_localized_name); // Set the localized name of this action + String get_localized_name() const; // Get the localized name of this action + + void set_action_type(const ActionType p_action_type); // Set the type of this action + ActionType get_action_type() const; // Get the type of this action + + void set_toplevel_paths(const PackedStringArray p_toplevel_paths); // Set the toplevel paths of this action + PackedStringArray get_toplevel_paths() const; // Get the toplevel paths of this action + + void add_toplevel_path(const String p_toplevel_path); // Add a top level path to this action + void rem_toplevel_path(const String p_toplevel_path); // Remove a toplevel path from this action + + void parse_toplevel_paths(const String p_toplevel_paths); // Parse and set the top level paths from a comma separated string +}; + +VARIANT_ENUM_CAST(OpenXRAction::ActionType); + +#endif // !OPENXR_ACTION_H diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp new file mode 100644 index 0000000000..2ba33419d7 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -0,0 +1,461 @@ +/*************************************************************************/ +/* openxr_action_map.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_action_map.h" + +void OpenXRActionMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action_sets", "action_sets"), &OpenXRActionMap::set_action_sets); + ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRActionMap::get_action_sets); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "action_sets", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRActionSet", PROPERTY_USAGE_NO_EDITOR), "set_action_sets", "get_action_sets"); + + ClassDB::bind_method(D_METHOD("get_action_set_count"), &OpenXRActionMap::get_action_set_count); + ClassDB::bind_method(D_METHOD("find_action_set", "name"), &OpenXRActionMap::find_action_set); + ClassDB::bind_method(D_METHOD("get_action_set", "idx"), &OpenXRActionMap::get_action_set); + ClassDB::bind_method(D_METHOD("add_action_set", "action_set"), &OpenXRActionMap::add_action_set); + ClassDB::bind_method(D_METHOD("remove_action_set", "action_set"), &OpenXRActionMap::remove_action_set); + + ClassDB::bind_method(D_METHOD("set_interaction_profiles", "interaction_profiles"), &OpenXRActionMap::set_interaction_profiles); + ClassDB::bind_method(D_METHOD("get_interaction_profiles"), &OpenXRActionMap::get_interaction_profiles); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "interaction_profiles", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRInteractionProfile", PROPERTY_USAGE_NO_EDITOR), "set_interaction_profiles", "get_interaction_profiles"); + + ClassDB::bind_method(D_METHOD("get_interaction_profile_count"), &OpenXRActionMap::get_interaction_profile_count); + ClassDB::bind_method(D_METHOD("find_interaction_profile", "name"), &OpenXRActionMap::find_interaction_profile); + ClassDB::bind_method(D_METHOD("get_interaction_profile", "idx"), &OpenXRActionMap::get_interaction_profile); + ClassDB::bind_method(D_METHOD("add_interaction_profile", "interaction_profile"), &OpenXRActionMap::add_interaction_profile); + ClassDB::bind_method(D_METHOD("remove_interaction_profile", "interaction_profile"), &OpenXRActionMap::remove_interaction_profile); + + ClassDB::bind_method(D_METHOD("create_default_action_sets"), &OpenXRActionMap::create_default_action_sets); +} + +void OpenXRActionMap::set_action_sets(Array p_action_sets) { + action_sets = p_action_sets; +} + +Array OpenXRActionMap::get_action_sets() const { + return action_sets; +} + +int OpenXRActionMap::get_action_set_count() const { + return action_sets.size(); +} + +Ref<OpenXRActionSet> OpenXRActionMap::find_action_set(String p_name) const { + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + if (action_set->get_name() == p_name) { + return action_set; + } + } + + return Ref<OpenXRActionSet>(); +} + +Ref<OpenXRActionSet> OpenXRActionMap::get_action_set(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, action_sets.size(), Ref<OpenXRActionSet>()); + + return action_sets[p_idx]; +} + +void OpenXRActionMap::add_action_set(Ref<OpenXRActionSet> p_action_set) { + ERR_FAIL_COND(p_action_set.is_null()); + + if (action_sets.find(p_action_set) == -1) { + action_sets.push_back(p_action_set); + } +} + +void OpenXRActionMap::remove_action_set(Ref<OpenXRActionSet> p_action_set) { + int idx = action_sets.find(p_action_set); + if (idx != -1) { + action_sets.remove_at(idx); + } +} + +void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { + interaction_profiles = p_interaction_profiles; +} + +Array OpenXRActionMap::get_interaction_profiles() const { + return interaction_profiles; +} + +int OpenXRActionMap::get_interaction_profile_count() const { + return interaction_profiles.size(); +} + +Ref<OpenXRInteractionProfile> OpenXRActionMap::find_interaction_profile(String p_path) const { + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = interaction_profiles[i]; + if (interaction_profile->get_interaction_profile_path() == p_path) { + return interaction_profile; + } + } + + return Ref<OpenXRInteractionProfile>(); +} + +Ref<OpenXRInteractionProfile> OpenXRActionMap::get_interaction_profile(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, interaction_profiles.size(), Ref<OpenXRInteractionProfile>()); + + return interaction_profiles[p_idx]; +} + +void OpenXRActionMap::add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + ERR_FAIL_COND(p_interaction_profile.is_null()); + + if (interaction_profiles.find(p_interaction_profile) == -1) { + interaction_profiles.push_back(p_interaction_profile); + } +} + +void OpenXRActionMap::remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + int idx = interaction_profiles.find(p_interaction_profile); + if (idx != -1) { + interaction_profiles.remove_at(idx); + } +} + +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. + + // Create our Godot action set + Ref<OpenXRActionSet> action_set = OpenXRActionSet::new_action_set("godot", "Godot action set"); + add_action_set(action_set); + + // Create our actions + Ref<OpenXRAction> trigger = action_set->add_new_action("trigger", "Trigger", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_click = action_set->add_new_action("trigger_click", "Trigger click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_touch = action_set->add_new_action("trigger_touch", "Trigger touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_touch = action_set->add_new_action("primary_touch", "Primary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary = action_set->add_new_action("secondary", "Secondary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_click = action_set->add_new_action("secondary_click", "Secondary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_touch = action_set->add_new_action("secondary_touch", "Secondary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> menu_button = action_set->add_new_action("menu_button", "Menu button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> select_button = action_set->add_new_action("select_button", "Select button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_button = action_set->add_new_action("ax_button", "A/X button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_touch = action_set->add_new_action("ax_touch", "A/X touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_button = action_set->add_new_action("by_button", "B/Y button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_touch = action_set->add_new_action("by_touch", "B/Y touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + 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> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left,/user/hand/right"); + + // 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(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(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 + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/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(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 + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our vive controller is our trackpad + profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + // vive controllers have no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our WMR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/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"); + // 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 + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will convert float to bool + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our wmr controller is our thumbstick, no touch + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // secondary on our wmr controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Meta touch controller profile + 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(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"); + // touch controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/system/click"); // right hand system click may not be available + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our touch controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // touch controller has no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Valve index controller profile + 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(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"); + // index controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); + profile->add_new_binding(ax_button, "/user/hand/left/input/a/click,/user/hand/right/input/a/click"); // a on both controllers + profile->add_new_binding(ax_touch, "/user/hand/left/input/a/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/b/click,/user/hand/right/input/b/click"); // b on both controllers + profile->add_new_binding(by_touch, "/user/hand/left/input/b/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion + // primary on our index controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // secondary on our index controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/force,/user/hand/right/input/trackpad/force"); // not sure if this will work but doesn't seem to support click... + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // 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(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"); + // hpmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // hpmr controllers only register click, not touch, on our a/b/x/y buttons + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our hpmr controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // No secondary on our hpmr controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // 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(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"); + // 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 + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Odyssey controller is our thumbstick, no touch + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // secondary on our Odyssey controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive Cosmos controller + 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(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(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 + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Cosmos controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // No secondary on our cosmos controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive Focus 3 controller + // 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(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(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 + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our Focus 3 controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // We only have a thumb rest + profile->add_new_binding(secondary_touch, "/user/hand/left/input/thumbrest/touch,/user/hand/right/input/thumbrest/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Huawei controller + 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(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(menu_button, "/user/hand/left/input/home/click,/user/hand/right/input/home/click"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + // primary on our Huawei controller is our trackpad + profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); +} + +void OpenXRActionMap::create_editor_action_sets() { + // TODO implement +} + +Ref<OpenXRAction> OpenXRActionMap::get_action(const String p_path) const { + PackedStringArray paths = p_path.split("/", false); + ERR_FAIL_COND_V(paths.size() != 2, Ref<OpenXRAction>()); + + Ref<OpenXRActionSet> action_set = find_action_set(paths[0]); + if (action_set.is_valid()) { + return action_set->get_action(paths[1]); + } + + return Ref<OpenXRAction>(); +} + +void OpenXRActionMap::remove_action(const String p_path) { + Ref<OpenXRAction> action = get_action(p_path); + if (action.is_valid()) { + OpenXRActionSet *action_set = action->get_action_set(); + if (action_set != nullptr) { + // Remove the action from this action set + action_set->remove_action(action); + } + + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = interaction_profiles[i]; + + // Remove any bindings for this action + interaction_profile->remove_binding_for_action(action); + } + } +} + +PackedStringArray OpenXRActionMap::get_top_level_paths(Ref<OpenXRAction> p_action) { + PackedStringArray arr; + + for (int i = 0; i < interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> ip = interaction_profiles[i]; + const OpenXRDefs::InteractionProfile *profile = OpenXRDefs::get_profile(ip->get_interaction_profile_path()); + + if (profile != nullptr) { + for (int j = 0; j < ip->get_binding_count(); j++) { + Ref<OpenXRIPBinding> binding = ip->get_binding(j); + if (binding->get_action() == p_action) { + PackedStringArray paths = binding->get_paths(); + + for (int k = 0; k < paths.size(); k++) { + const OpenXRDefs::IOPath *io_path = profile->get_io_path(paths[k]); + if (io_path != nullptr) { + String top_path = String(io_path->top_level_path->openxr_path); + + if (!arr.has(top_path)) { + arr.push_back(top_path); + } + } + } + } + } + } + } + + print_line("Toplevel paths for", p_action->get_name_with_set(), "are", arr); + + return arr; +} + +OpenXRActionMap::~OpenXRActionMap() { + action_sets.clear(); + interaction_profiles.clear(); +} diff --git a/modules/openxr/action_map/openxr_action_map.h b/modules/openxr/action_map/openxr_action_map.h new file mode 100644 index 0000000000..dcd8fc71aa --- /dev/null +++ b/modules/openxr/action_map/openxr_action_map.h @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* openxr_action_map.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_ACTION_SETS_H +#define OPENXR_ACTION_SETS_H + +#include "core/io/resource.h" + +#include "openxr_action.h" +#include "openxr_action_set.h" +#include "openxr_interaction_profile.h" + +class OpenXRActionMap : public Resource { + GDCLASS(OpenXRActionMap, Resource); + +private: + Array action_sets; + Array interaction_profiles; + +protected: + static void _bind_methods(); + +public: + void set_action_sets(Array p_action_sets); // Set our actions sets by providing an array with action sets (for loading from resource) + Array get_action_sets() const; // Get our action sets as an array (for saving to resource) + + int get_action_set_count() const; // Retrieve the number of action sets we have + Ref<OpenXRActionSet> find_action_set(String p_name) const; // Find an action set by name + Ref<OpenXRActionSet> get_action_set(int p_idx) const; // Retrieve an action set by index + void add_action_set(Ref<OpenXRActionSet> p_action_set); // Add an action set to our action map + void remove_action_set(Ref<OpenXRActionSet> p_action_set); // Remove an action set from our action map + + void set_interaction_profiles(Array p_interaction_profiles); // Set our interaction profiles by providing an array (for loading from resource) + Array get_interaction_profiles() const; // Get our interaction profiles as an array (for saving to resource) + + int get_interaction_profile_count() const; // Retrieve the number of interaction profiles we have + Ref<OpenXRInteractionProfile> find_interaction_profile(String p_path) const; // Find an interaction profile by path + Ref<OpenXRInteractionProfile> get_interaction_profile(int p_idx) const; // Retrieve an interaction profile by index + void add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); // Add an interaction profile to our action map + void remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); // remove an interaction profile from our action map + + void create_default_action_sets(); // Create our default action set for runtime + void create_editor_action_sets(); // Create our action set for the editor + + // Helper functions for editor + Ref<OpenXRAction> get_action(const String p_path) const; // Retrieve an action using <action name>/<action> as our parameter + void remove_action(const String p_path); // Remove action from action set, also removes it from interaction profiles + PackedStringArray get_top_level_paths(Ref<OpenXRAction> p_action); // Determines the top level paths based on where an action is bound in interaction profiles + + // TODO add validation to display in the interface that checks if we have action sets with the same name or if we have interaction profiles for the same path + + ~OpenXRActionMap(); +}; + +#endif // !OPENXR_ACTION_SETS_H diff --git a/modules/openxr/action_map/openxr_action_set.cpp b/modules/openxr/action_map/openxr_action_set.cpp new file mode 100644 index 0000000000..be45218300 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.cpp @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* openxr_action_set.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_action_set.h" + +void OpenXRActionSet::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRActionSet::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRActionSet::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &OpenXRActionSet::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &OpenXRActionSet::get_priority); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority"), "set_priority", "get_priority"); + + ClassDB::bind_method(D_METHOD("get_action_count"), &OpenXRActionSet::get_action_count); + ClassDB::bind_method(D_METHOD("set_actions", "actions"), &OpenXRActionSet::set_actions); + ClassDB::bind_method(D_METHOD("get_actions"), &OpenXRActionSet::get_actions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "actions", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction", PROPERTY_USAGE_NO_EDITOR), "set_actions", "get_actions"); + + ClassDB::bind_method(D_METHOD("add_action", "action"), &OpenXRActionSet::add_action); + ClassDB::bind_method(D_METHOD("remove_action", "action"), &OpenXRActionSet::remove_action); +} + +Ref<OpenXRActionSet> OpenXRActionSet::new_action_set(const char *p_name, const char *p_localized_name, const int p_priority) { + // This is a helper function to help build our default action sets + + Ref<OpenXRActionSet> action_set; + action_set.instantiate(); + action_set->set_name(String(p_name)); + action_set->set_localized_name(p_localized_name); + action_set->set_priority(p_priority); + + return action_set; +} + +void OpenXRActionSet::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRActionSet::get_localized_name() const { + return localized_name; +} + +void OpenXRActionSet::set_priority(const int p_priority) { + priority = p_priority; +} + +int OpenXRActionSet::get_priority() const { + return priority; +} + +int OpenXRActionSet::get_action_count() const { + return actions.size(); +} + +void OpenXRActionSet::clear_actions() { + // Actions held within our action set should be released and destroyed but just in case they are still used some where else + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + action->action_set = nullptr; + } + actions.clear(); +} + +void OpenXRActionSet::set_actions(Array p_actions) { + // Any actions not retained in p_actions should be freed automatically, those held within our Array will have be relinked to our action set. + clear_actions(); + + for (int i = 0; i < p_actions.size(); i++) { + // add them anew so we verify our action_set pointer + add_action(p_actions[i]); + } +} + +Array OpenXRActionSet::get_actions() const { + return actions; +} + +Ref<OpenXRAction> OpenXRActionSet::get_action(const String p_name) const { + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + if (action->get_name() == p_name) { + return action; + } + } + + return Ref<OpenXRAction>(); +} + +void OpenXRActionSet::add_action(Ref<OpenXRAction> p_action) { + ERR_FAIL_COND(p_action.is_null()); + + if (actions.find(p_action) == -1) { + if (p_action->action_set && p_action->action_set != this) { + // action should only relate to our action set + p_action->action_set->remove_action(p_action); + } + + p_action->action_set = this; + actions.push_back(p_action); + } +} + +void OpenXRActionSet::remove_action(Ref<OpenXRAction> p_action) { + int idx = actions.find(p_action); + if (idx != -1) { + actions.remove_at(idx); + + ERR_FAIL_COND_MSG(p_action->action_set != this, "Removing action that belongs to this action set but had incorrect action set pointer."); // this should never happen! + p_action->action_set = nullptr; + } +} + +Ref<OpenXRAction> OpenXRActionSet::add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> new_action = OpenXRAction::new_action(p_name, p_localized_name, p_action_type, p_toplevel_paths); + add_action(new_action); + return new_action; +} + +OpenXRActionSet::~OpenXRActionSet() { + clear_actions(); +} diff --git a/modules/openxr/action_map/openxr_action_set.h b/modules/openxr/action_map/openxr_action_set.h new file mode 100644 index 0000000000..b1d7168894 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.h @@ -0,0 +1,75 @@ +/*************************************************************************/ +/* openxr_action_set.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_ACTION_SET_H +#define OPENXR_ACTION_SET_H + +#include "core/io/resource.h" + +#include "openxr_action.h" + +class OpenXRActionSet : public Resource { + GDCLASS(OpenXRActionSet, Resource); + +private: + String localized_name; + int priority = 0; + + Array actions; + void clear_actions(); + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRActionSet> new_action_set(const char *p_name, const char *p_localized_name, const int p_priority = 0); // Helper function for adding and setting up an action set + + void set_localized_name(const String p_localized_name); // Set the localized name of this action set + String get_localized_name() const; // Get the localized name of this action set + + void set_priority(const int p_priority); // Set the priority of this action set + int get_priority() const; // Get the priority of this action set + + int get_action_count() const; // Retrieve the number of actions in our action set + void set_actions(Array p_actions); // Set our actions using an array of actions (for loading a resource) + Array get_actions() const; // Get our actions as an array (for saving a resource) + + Ref<OpenXRAction> get_action(const String p_name) const; // Retrieve an action by name + void add_action(Ref<OpenXRAction> p_action); // Add a new action to our action set + void remove_action(Ref<OpenXRAction> p_action); // remove a action from our action set + + Ref<OpenXRAction> add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths); // Helper function for adding and setting up an action + + // TODO add validation to display in the interface that checks if we have duplicate action names within our action set + + ~OpenXRActionSet(); +}; + +#endif // !OPENXR_ACTION_SET_H diff --git a/modules/openxr/action_map/openxr_defs.cpp b/modules/openxr/action_map/openxr_defs.cpp new file mode 100644 index 0000000000..e10326449c --- /dev/null +++ b/modules/openxr/action_map/openxr_defs.cpp @@ -0,0 +1,493 @@ +/*************************************************************************/ +/* openxr_defs.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_defs.h" + +// Our top level paths to which devices can be bound +OpenXRDefs::TopLevelPath OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_TOP_LEVEL_PATH_MAX] = { + { "Left hand controller", "/user/hand/left" }, + { "Right hand controller", "/user/hand/right" }, +}; + +// Fallback Khronos simple controller +OpenXRDefs::IOPath OpenXRDefs::simple_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + { "Select click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/select/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Select click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/select/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Original HTC Vive wands +OpenXRDefs::IOPath OpenXRDefs::vive_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + { "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 }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Microsoft motion controller (original WMR controllers) +OpenXRDefs::IOPath OpenXRDefs::motion_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// HP MR controller (newer G2 controllers) +OpenXRDefs::IOPath OpenXRDefs::hpmr_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Meta touch controller (original touch controllers, Quest 1 and Quest 2 controllers) +OpenXRDefs::IOPath OpenXRDefs::touch_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "X touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Valve index controller +OpenXRDefs::IOPath OpenXRDefs::index_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Squeeze", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad force", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/force", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad force", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/force", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Samsung odyssey controller +OpenXRDefs::IOPath OpenXRDefs::odyssey_io_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Vive Cosmos controller +OpenXRDefs::IOPath OpenXRDefs::vive_cosmos_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Shoulder click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/right/input/shoulder/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Shoulder click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/shoulder/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Vive Focus 3 controller +OpenXRDefs::IOPath OpenXRDefs::vive_focus3_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + + { "X click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/x/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Y click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/y/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "A click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/a/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "B click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/b/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/touch ", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/squeeze/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Squeeze touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/squeeze/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Thumbstick click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Thumbstick touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbstick/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Thumbrest touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/thumbrest/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +// Huawei controller +OpenXRDefs::IOPath OpenXRDefs::huawei_controller_paths[] = { + { "Grip pose", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/grip/pose", OpenXRAction::OPENXR_ACTION_POSE }, + { "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 }, + + { "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 }, + { "Back click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/back/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Back click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/back/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Volume up click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/volume_up/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume up click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/volume_up/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume down click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/volume_down/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Volume down click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/volume_down/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trigger", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/value", OpenXRAction::OPENXR_ACTION_FLOAT }, + { "Trigger click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trigger/click", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2 }, + { "Trackpad click", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/click", OpenXRAction::OPENXR_ACTION_BOOL }, + { "Trackpad touch", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/input/trackpad/touch", OpenXRAction::OPENXR_ACTION_BOOL }, + + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_LEFT_HAND], "/user/hand/left/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, + { "Haptic output", &OpenXRDefs::available_top_level_paths[OpenXRDefs::OPENXR_RIGHT_HAND], "/user/hand/right/output/haptic", OpenXRAction::OPENXR_ACTION_HAPTIC }, +}; + +OpenXRDefs::InteractionProfile OpenXRDefs::available_interaction_profiles[] = { + { + "Simple controller", // display_name + "/interaction_profiles/khr/simple_controller", // openxr_path + simple_io_paths, // io_paths + sizeof(simple_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "HTC Vive wand", // display_name + "/interaction_profiles/htc/vive_controller", // openxr_path + vive_io_paths, // io_paths + sizeof(vive_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "MS Motion controller", // display_name + "/interaction_profiles/microsoft/motion_controller", // openxr_path + motion_io_paths, // io_paths + sizeof(motion_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "HPMR controller", // display_name + "/interaction_profiles/hp/mixed_reality_controller", // openxr_path + hpmr_io_paths, // io_paths + sizeof(hpmr_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Touch controller", // display_name + "/interaction_profiles/oculus/touch_controller", // openxr_path + touch_io_paths, // io_paths + sizeof(touch_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Index controller", // display_name + "/interaction_profiles/valve/index_controller", // openxr_path + index_io_paths, // io_paths + sizeof(index_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Samsung Odyssey controller", // display_name + "/interaction_profiles/samsung/odyssey_controller", // openxr_path + odyssey_io_paths, // io_paths + sizeof(odyssey_io_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Vive Cosmos controller", // display_name + "/interaction_profiles/htc/vive_cosmos_controller", // openxr_path + vive_cosmos_paths, // io_paths + sizeof(vive_cosmos_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Vive Focus 3 controller", // display_name + "/interaction_profiles/htc/vive_focus3_controller", // openxr_path + vive_focus3_paths, // io_paths + sizeof(vive_focus3_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, + { + "Huawei controller", // display_name + "/interaction_profiles/huawei/controller", // openxr_path + huawei_controller_paths, // io_paths + sizeof(huawei_controller_paths) / sizeof(OpenXRDefs::IOPath) // io_path_count + }, +}; + +int OpenXRDefs::available_interaction_profile_count = sizeof(OpenXRDefs::available_interaction_profiles) / sizeof(OpenXRDefs::InteractionProfile); + +const OpenXRDefs::TopLevelPath *OpenXRDefs::get_top_level_path(const String p_top_level_path) { + for (int i = 0; i < OPENXR_TOP_LEVEL_PATH_MAX; i++) { + if (available_top_level_paths[i].openxr_path == p_top_level_path) { + return &OpenXRDefs::available_top_level_paths[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::InteractionProfile *OpenXRDefs::get_profile(const String p_interaction_profile_path) { + for (int i = 0; i < available_interaction_profile_count; i++) { + if (available_interaction_profiles[i].openxr_path == p_interaction_profile_path) { + return &available_interaction_profiles[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::IOPath *OpenXRDefs::InteractionProfile::get_io_path(const String p_io_path) const { + for (int i = 0; i < available_interaction_profiles[i].io_path_count; i++) { + if (io_paths[i].openxr_path == p_io_path) { + return &io_paths[i]; + } + } + + return nullptr; +} + +const OpenXRDefs::IOPath *OpenXRDefs::get_io_path(const String p_interaction_profile_path, const String p_io_path) { + const OpenXRDefs::InteractionProfile *profile = OpenXRDefs::get_profile(p_interaction_profile_path); + if (profile != nullptr) { + return profile->get_io_path(p_io_path); + } + + return nullptr; +} + +PackedStringArray OpenXRDefs::get_interaction_profile_paths() { + PackedStringArray arr; + + for (int i = 0; i < available_interaction_profile_count; i++) { + arr.push_back(available_interaction_profiles[i].openxr_path); + } + + return arr; +} diff --git a/modules/openxr/action_map/openxr_defs.h b/modules/openxr/action_map/openxr_defs.h new file mode 100644 index 0000000000..dbda4757f1 --- /dev/null +++ b/modules/openxr/action_map/openxr_defs.h @@ -0,0 +1,103 @@ +/*************************************************************************/ +/* openxr_defs.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_DEFS_H +#define OPENXR_DEFS_H + +#include "openxr_action.h" + +/////////////////////////////////////////////////////////////////////////// +// Stores available interaction profiles +// +// OpenXR defines and hardcodes all the supported input devices and their +// paths as part of the OpenXR spec. When support for new devices is +// introduced this often starts life as extensions that need to be enabled +// until they are adopted into the core. As there is no interface to +// enumerate the possibly paths, and that any OpenXR runtime would likely +// limit such enumeration to those input devices supported by that runtime +// there is no other option than to hardcode this. +// +// Note on action type that automatic conversions between boolean and float +// are supported but otherwise action types should match between action and +// input/output paths. + +class OpenXRDefs { +public: + enum TOP_LEVEL_PATH { + OPENXR_LEFT_HAND, + OPENXR_RIGHT_HAND, + OPENXR_TOP_LEVEL_PATH_MAX + }; + + struct TopLevelPath { + const char *display_name; // User friendly display name (i.e. Left controller) + const char *openxr_path; // Path in OpenXR (i.e. /user/hand/left) + }; + + struct IOPath { + const char *display_name; // User friendly display name (i.e. Grip pose (left controller)) + const TopLevelPath *top_level_path; // Top level path identifying the usage of the device in relation to this input/output + const char *openxr_path; // Path in OpenXR (i.e. /user/hand/left/input/grip/pose) + const OpenXRAction::ActionType action_type; // Type of input/output + }; + + struct InteractionProfile { + const char *display_name; // User friendly display name (i.e. Simple controller) + const char *openxr_path; // Path in OpenXR (i.e. /interaction_profiles/khr/simple_controller) + const IOPath *io_paths; // Inputs and outputs for this device + const int io_path_count; // Number of inputs and outputs for this device + + const IOPath *get_io_path(const String p_io_path) const; + }; + +private: + static TopLevelPath available_top_level_paths[OPENXR_TOP_LEVEL_PATH_MAX]; + static IOPath simple_io_paths[]; + static IOPath vive_io_paths[]; + static IOPath motion_io_paths[]; + static IOPath hpmr_io_paths[]; + static IOPath touch_io_paths[]; + static IOPath index_io_paths[]; + static IOPath odyssey_io_paths[]; + static IOPath vive_cosmos_paths[]; + static IOPath vive_focus3_paths[]; + static IOPath huawei_controller_paths[]; + static InteractionProfile available_interaction_profiles[]; + static int available_interaction_profile_count; + +public: + static const TopLevelPath *get_top_level_path(const String p_top_level_path); + static const InteractionProfile *get_profile(const String p_interaction_profile_path); + static const IOPath *get_io_path(const String p_interaction_profile_path, const String p_io_path); + + static PackedStringArray get_interaction_profile_paths(); +}; + +#endif // !OPENXR_DEFS_H diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp new file mode 100644 index 0000000000..342c36cdff --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -0,0 +1,197 @@ +/*************************************************************************/ +/* openxr_interaction_profile.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_interaction_profile.h" + +void OpenXRIPBinding::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action", "action"), &OpenXRIPBinding::set_action); + ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action"); + + ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count); + ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); + ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths"); + + ClassDB::bind_method(D_METHOD("has_path"), &OpenXRIPBinding::has_path); + ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path); + ClassDB::bind_method(D_METHOD("remove_path", "path"), &OpenXRIPBinding::remove_path); +} + +Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding; + binding.instantiate(); + binding->set_action(p_action); + binding->parse_paths(String(p_paths)); + + return binding; +} + +void OpenXRIPBinding::set_action(const Ref<OpenXRAction> p_action) { + action = p_action; +} + +Ref<OpenXRAction> OpenXRIPBinding::get_action() const { + return action; +} + +int OpenXRIPBinding::get_path_count() const { + return paths.size(); +} + +void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { + paths = p_paths; +} + +PackedStringArray OpenXRIPBinding::get_paths() const { + return paths; +} + +void OpenXRIPBinding::parse_paths(const String p_paths) { + paths = p_paths.split(",", false); +} + +bool OpenXRIPBinding::has_path(const String p_path) const { + return paths.has(p_path); +} + +void OpenXRIPBinding::add_path(const String p_path) { + if (!paths.has(p_path)) { + paths.push_back(p_path); + } +} + +void OpenXRIPBinding::remove_path(const String p_path) { + if (paths.has(p_path)) { + paths.erase(p_path); + } +} + +OpenXRIPBinding::~OpenXRIPBinding() { + action.unref(); +} + +void OpenXRInteractionProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_interaction_profile_path", "interaction_profile_path"), &OpenXRInteractionProfile::set_interaction_profile_path); + ClassDB::bind_method(D_METHOD("get_interaction_profile_path"), &OpenXRInteractionProfile::get_interaction_profile_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "interaction_profile_path"), "set_interaction_profile_path", "get_interaction_profile_path"); + + ClassDB::bind_method(D_METHOD("get_binding_count"), &OpenXRInteractionProfile::get_binding_count); + ClassDB::bind_method(D_METHOD("get_binding", "index"), &OpenXRInteractionProfile::get_binding); + ClassDB::bind_method(D_METHOD("set_bindings", "bindings"), &OpenXRInteractionProfile::set_bindings); + ClassDB::bind_method(D_METHOD("get_bindings"), &OpenXRInteractionProfile::get_bindings); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bindings", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRIPBinding", PROPERTY_USAGE_NO_EDITOR), "set_bindings", "get_bindings"); +} + +Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char *p_input_profile_path) { + Ref<OpenXRInteractionProfile> profile; + profile.instantiate(); + profile->set_interaction_profile_path(String(p_input_profile_path)); + + return profile; +} + +void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { + interaction_profile_path = p_input_profile_path; +} + +String OpenXRInteractionProfile::get_interaction_profile_path() const { + return interaction_profile_path; +} + +int OpenXRInteractionProfile::get_binding_count() const { + return bindings.size(); +} + +Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bindings.size(), Ref<OpenXRIPBinding>()); + + return bindings[p_index]; +} + +void OpenXRInteractionProfile::set_bindings(Array p_bindings) { + bindings = p_bindings; + + // TODO add check here that our bindings don't contain duplicate actions +} + +Array OpenXRInteractionProfile::get_bindings() const { + return bindings; +} + +Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<OpenXRAction> p_action) const { + for (int i = 0; i < bindings.size(); i++) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->get_action() == p_action) { + return binding; + } + } + + return Ref<OpenXRIPBinding>(); +} + +void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) { + ERR_FAIL_COND(p_binding.is_null()); + + if (bindings.find(p_binding) == -1) { + ERR_FAIL_COND_MSG(get_binding_for_action(p_binding->get_action()).is_valid(), "There is already a binding for this action in this interaction profile"); + + bindings.push_back(p_binding); + } +} + +void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) { + int idx = bindings.find(p_binding); + if (idx != -1) { + bindings.remove_at(idx); + } +} + +void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths); + add_binding(binding); +} + +void OpenXRInteractionProfile::remove_binding_for_action(const Ref<OpenXRAction> p_action) { + for (int i = bindings.size() - 1; i >= 0; i--) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->get_action() == p_action) { + remove_binding(binding); + } + } +} + +OpenXRInteractionProfile::~OpenXRInteractionProfile() { + bindings.clear(); +} diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h new file mode 100644 index 0000000000..46b1bda50f --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* openxr_interaction_profile.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_INTERACTION_PROFILE_H +#define OPENXR_INTERACTION_PROFILE_H + +#include "core/io/resource.h" + +#include "openxr_action.h" +#include "openxr_defs.h" + +class OpenXRIPBinding : public Resource { + GDCLASS(OpenXRIPBinding, Resource); + +private: + Ref<OpenXRAction> action; + PackedStringArray paths; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Helper function for adding a new binding + + void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding + Ref<OpenXRAction> get_action() const; // Get the action for this binding + + int get_path_count() const; // Get the number of io paths + void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource) + PackedStringArray get_paths() const; // Get our paths (for saving to resource) + + void parse_paths(const String p_paths); // Parse a comma separated string of io paths. + + bool has_path(const String p_path) const; // Has this io path + void add_path(const String p_path); // Add an io path + void remove_path(const String p_path); // Remove an io path + + // TODO add validation that we can display in the interface that checks if no two paths belong to the same top level path + + ~OpenXRIPBinding(); +}; + +class OpenXRInteractionProfile : public Resource { + GDCLASS(OpenXRInteractionProfile, Resource); + +private: + String interaction_profile_path; + Array bindings; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRInteractionProfile> new_profile(const char *p_input_profile_path); // Helper function to create a new interaction profile + + void set_interaction_profile_path(const String p_input_profile_path); // Set our input profile path + String get_interaction_profile_path() const; // get our input profile path + + int get_binding_count() const; // Retrieve the number of bindings in this profile path + Ref<OpenXRIPBinding> get_binding(int p_index) const; + void set_bindings(Array p_bindings); // Set the bindings (for loading from a resource) + Array get_bindings() const; // Get the bindings (for saving to a resource) + + Ref<OpenXRIPBinding> get_binding_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action + void add_binding(Ref<OpenXRIPBinding> p_binding); // Add a binding object + void remove_binding(Ref<OpenXRIPBinding> p_binding); // Remove a binding object + + void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Create a new binding for this profile + void remove_binding_for_action(const Ref<OpenXRAction> p_action); // Remove all bindings for this action + + ~OpenXRInteractionProfile(); +}; + +#endif // !OPENXR_INTERACTION_PROFILE_H diff --git a/modules/openxr/config.py b/modules/openxr/config.py new file mode 100644 index 0000000000..f91cb1359f --- /dev/null +++ b/modules/openxr/config.py @@ -0,0 +1,27 @@ +def can_build(env, platform): + if ( + platform == "linuxbsd" or platform == "windows" + ): # or platform == "android" -- temporarily disabled android support + return env["openxr"] + else: + # not supported on these platforms + return False + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "OpenXRInterface", + "OpenXRAction", + "OpenXRActionSet", + "OpenXRActionMap", + "OpenXRInteractionProfile", + "OpenXRIPBinding", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml new file mode 100644 index 0000000000..6ff8c1ad26 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRAction" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + An OpenXR action. + </brief_description> + <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[/core] 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. + Note that the name of the resource is used to register the action with. + </description> + <tutorials> + </tutorials> + <members> + <member name="action_type" type="int" setter="set_action_type" getter="get_action_type" enum="OpenXRAction.ActionType" default="1"> + 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. + </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. + </member> + </members> + <constants> + <constant name="OPENXR_ACTION_BOOL" value="0" enum="ActionType"> + This action provides a boolean value. + </constant> + <constant name="OPENXR_ACTION_FLOAT" value="1" enum="ActionType"> + This action provides a float value between [code]0.0[/code] and [code]1.0[/code] for any analogue input such as triggers. + </constant> + <constant name="OPENXR_ACTION_VECTOR2" value="2" enum="ActionType"> + This action provides a vector2 value and can be bound to embedded trackpads and joysticks + </constant> + <constant name="OPENXR_ACTION_POSE" value="3" enum="ActionType"> + </constant> + </constants> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionMap.xml b/modules/openxr/doc_classes/OpenXRActionMap.xml new file mode 100644 index 0000000000..a29d10be41 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionMap.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionMap" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRActionSet] and [OpenXRInteractionProfile] resources for the OpenXR module. + </brief_description> + <description> + OpenXR uses an action system similar to Godots Input map system to bind inputs and outputs on various types of XR controllers to named actions. OpenXR specifies more detail on these inputs and outputs than Godot supports. + Another important distinction is that OpenXR offers no control over these bindings. The bindings we register are suggestions, it is up to the XR runtime to offer users the ability to change these bindings. This allows the XR runtime to fill in the gaps if new hardware becomes available. + The action map therefore needs to be loaded at startup and can't be changed afterwards. This resource is a container for the entire action map. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Add an action set. + </description> + </method> + <method name="add_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Add an interaction profile. + </description> + </method> + <method name="create_default_action_sets"> + <return type="void" /> + <description> + Setup this action set with our default actions. + </description> + </method> + <method name="find_action_set" qualifiers="const"> + <return type="OpenXRActionSet" /> + <argument index="0" name="name" type="String" /> + <description> + Retrieve an action set by name. + </description> + </method> + <method name="find_interaction_profile" qualifiers="const"> + <return type="OpenXRInteractionProfile" /> + <argument index="0" name="name" type="String" /> + <description> + Find an interaction profile by its name (path). + </description> + </method> + <method name="get_action_set" qualifiers="const"> + <return type="OpenXRActionSet" /> + <argument index="0" name="idx" type="int" /> + <description> + Retrieve the action set at this index. + </description> + </method> + <method name="get_action_set_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of actions sets in our action map. + </description> + </method> + <method name="get_interaction_profile" qualifiers="const"> + <return type="OpenXRInteractionProfile" /> + <argument index="0" name="idx" type="int" /> + <description> + Get the interaction profile at this index. + </description> + </method> + <method name="get_interaction_profile_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of interaction profiles in our action map. + </description> + </method> + <method name="remove_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Remove an action set. + </description> + </method> + <method name="remove_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Remove an interaction profile. + </description> + </method> + </methods> + <members> + <member name="action_sets" type="Array" setter="set_action_sets" getter="get_action_sets" default="[]"> + Collection of [OpenXRActionSet]s that are part of this action map. + </member> + <member name="interaction_profiles" type="Array" setter="set_interaction_profiles" getter="get_interaction_profiles" default="[]"> + Collection of [OpenXRInteractionProfile]s that are part of this action map. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml new file mode 100644 index 0000000000..55cc0aaad4 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionSet.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionSet" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRAction] resources that make up an action set. + </brief_description> + <description> + Action sets in OpenXR define a collection of actions that can be activated in unison. This allows games to easily change between different states that require different inputs or need to reinterpret inputs. For instance we could have an action set that is active when a menu is open, an action set that is active when the player is freely walking around and an action set that is active when the player is controlling a vehicle. + Action sets can contain the same action with the same name, if such action sets are active at the same time the action set with the highest priority defines which binding is active. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Add an action to this action set. + </description> + </method> + <method name="get_action_count" qualifiers="const"> + <return type="int" /> + <description> + Retrieve the number of actions in our action set. + </description> + </method> + <method name="remove_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Remove an action from this action set. + </description> + </method> + </methods> + <members> + <member name="actions" type="Array" setter="set_actions" getter="get_actions" default="[]"> + 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. + </member> + <member name="priority" type="int" setter="set_priority" getter="get_priority" default="0"> + The priority for this action set. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml new file mode 100644 index 0000000000..9e1176874a --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRIPBinding" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Defines a binding between an [OpenXRAction] and an XR input or output. + </brief_description> + <description> + This binding resource binds an [OpenXRAction] to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_path"> + <return type="void" /> + <argument index="0" name="path" type="String" /> + <description> + Add an input/output path to this binding. + </description> + </method> + <method name="get_path_count" qualifiers="const"> + <return type="int" /> + <description> + Get the number of input/output paths in this binding. + </description> + </method> + <method name="has_path" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="arg0" type="String" /> + <description> + Returns [code]true[/code] if this input/output path is part of this binding. + </description> + </method> + <method name="remove_path"> + <return type="void" /> + <argument index="0" name="path" type="String" /> + <description> + Removes this input/output path from this binding. + </description> + </method> + </methods> + <members> + <member name="action" type="OpenXRAction" setter="set_action" getter="get_action"> + [OpenXRAction] that is bound to these paths. + </member> + <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()"> + Paths that define the inputs or outputs bound on the device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml new file mode 100644 index 0000000000..71c0db44ed --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInteractionProfile" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Suggested bindings object for OpenXR. + </brief_description> + <description> + This object stores suggested bindings for an interaction profile. Interaction profiles define the meta data for a tracked XR device such as an XR controller. + For more information see the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-interaction-profiles]interaction profiles info in the OpenXR specification[/url]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_binding" qualifiers="const"> + <return type="OpenXRIPBinding" /> + <argument index="0" name="index" type="int" /> + <description> + Retrieve the binding at this index. + </description> + </method> + <method name="get_binding_count" qualifiers="const"> + <return type="int" /> + <description> + Get the number of bindings in this interaction profile. + </description> + </method> + </methods> + <members> + <member name="bindings" type="Array" setter="set_bindings" getter="get_bindings" default="[]"> + Action bindings for this interaction profile. + </member> + <member name="interaction_profile_path" type="String" setter="set_interaction_profile_path" getter="get_interaction_profile_path" default=""""> + The interaction profile path identifying the XR device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml new file mode 100644 index 0000000000..74f708bc95 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Our OpenXR interface. + </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 then 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. + </description> + <tutorials> + <link title="OpenXR documentation">$DOCS_URL/tutorials/vr/openxr/index.html</link> + </tutorials> + <signals> + <signal name="pose_recentered"> + <description> + Informs the user queued a recenter of the player position. + </description> + </signal> + <signal name="session_begun"> + <description> + Informs our OpenXR session has been started. + </description> + </signal> + <signal name="session_focussed"> + <description> + Informs our OpenXR session now has focus. + </description> + </signal> + <signal name="session_stopping"> + <description> + Informs our OpenXR session is stopping. + </description> + </signal> + <signal name="session_visible"> + <description> + Informs our OpenXR session is now visible (output is being sent to the HMD). + </description> + </signal> + </signals> +</class> diff --git a/modules/openxr/editor/SCsub b/modules/openxr/editor/SCsub new file mode 100644 index 0000000000..ccf67a80d0 --- /dev/null +++ b/modules/openxr/editor/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/openxr/editor/openxr_action_editor.cpp b/modules/openxr/editor/openxr_action_editor.cpp new file mode 100644 index 0000000000..e2a4f67f16 --- /dev/null +++ b/modules/openxr/editor/openxr_action_editor.cpp @@ -0,0 +1,112 @@ +/*************************************************************************/ +/* openxr_action_editor.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_action_editor.h" + +void OpenXRActionEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::OBJECT, "action_editor"))); +} + +void OpenXRActionEditor::_theme_changed() { + rem_action->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); +} + +void OpenXRActionEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + } break; + } +} + +void OpenXRActionEditor::_on_action_name_changed(const String p_new_text) { + // TODO validate if entry is allowed + + // If our localized name matches our action name, set this too + if (action->get_name() == action->get_localized_name()) { + action->set_localized_name(p_new_text); + action_localized_name->set_text(p_new_text); + } + action->set_name(p_new_text); +} + +void OpenXRActionEditor::_on_action_localized_name_changed(const String p_new_text) { + action->set_localized_name(p_new_text); +} + +void OpenXRActionEditor::_on_item_selected(int p_idx) { + ERR_FAIL_COND(p_idx < 0); + ERR_FAIL_COND(p_idx >= OpenXRAction::OPENXR_ACTION_MAX); + + action->set_action_type(OpenXRAction::ActionType(p_idx)); +} + +void OpenXRActionEditor::_on_remove_action() { + emit_signal("remove", this); +} + +OpenXRActionEditor::OpenXRActionEditor(Ref<OpenXRAction> p_action) { + action = p_action; + + set_h_size_flags(Control::SIZE_EXPAND_FILL); + + action_name = memnew(LineEdit); + action_name->set_text(action->get_name()); + action_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_name_changed)); + add_child(action_name); + + action_localized_name = memnew(LineEdit); + action_localized_name->set_text(action->get_localized_name()); + action_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionEditor::_on_action_localized_name_changed)); + add_child(action_localized_name); + + action_type = memnew(OptionButton); + action_type->add_item("Bool", OpenXRAction::OPENXR_ACTION_BOOL); + action_type->add_item("Float", OpenXRAction::OPENXR_ACTION_FLOAT); + action_type->add_item("Vector2", OpenXRAction::OPENXR_ACTION_VECTOR2); + action_type->add_item("Pose", OpenXRAction::OPENXR_ACTION_POSE); + action_type->add_item("Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC); + action_type->select(int(action->get_action_type())); + action_type->set_custom_minimum_size(Size2(100.0, 0.0)); + action_type->connect("item_selected", callable_mp(this, &OpenXRActionEditor::_on_item_selected)); + add_child(action_type); + + // maybe add dropdown to edit our toplevel paths, or do we deduce them from our suggested bindings? + + rem_action = memnew(Button); + rem_action->set_tooltip(TTR("Remove action")); + rem_action->connect("pressed", callable_mp(this, &OpenXRActionEditor::_on_remove_action)); + rem_action->set_flat(true); + add_child(rem_action); +} diff --git a/modules/openxr/editor/openxr_action_editor.h b/modules/openxr/editor/openxr_action_editor.h new file mode 100644 index 0000000000..6e1b7ab779 --- /dev/null +++ b/modules/openxr/editor/openxr_action_editor.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* openxr_action_editor.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_ACTION_EDITOR_H +#define OPENXR_ACTION_EDITOR_H + +#include "../action_map/openxr_action.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/text_edit.h" + +class OpenXRActionEditor : public HBoxContainer { + GDCLASS(OpenXRActionEditor, HBoxContainer); + +private: + Ref<OpenXRAction> action; + + LineEdit *action_name = nullptr; + LineEdit *action_localized_name = nullptr; + OptionButton *action_type = nullptr; + Button *rem_action = nullptr; + + void _theme_changed(); + void _on_action_name_changed(const String p_new_text); + void _on_action_localized_name_changed(const String p_new_text); + void _on_item_selected(int p_idx); + void _on_remove_action(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Ref<OpenXRAction> get_action() { return action; }; + OpenXRActionEditor(Ref<OpenXRAction> p_action); +}; + +#endif // !OPENXR_ACTION_EDITOR_H diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp new file mode 100644 index 0000000000..6e9a2e1b61 --- /dev/null +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -0,0 +1,370 @@ +/*************************************************************************/ +/* openxr_action_map_editor.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_action_map_editor.h" + +#include "core/config/project_settings.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +// TODO implement redo/undo system + +void OpenXRActionMapEditor::_bind_methods() { + ClassDB::bind_method("_add_action_set_editor", &OpenXRActionMapEditor::_add_action_set_editor); + ClassDB::bind_method("_update_action_sets", &OpenXRActionMapEditor::_update_action_sets); + + ClassDB::bind_method("_add_interaction_profile_editor", &OpenXRActionMapEditor::_add_interaction_profile_editor); + ClassDB::bind_method("_update_interaction_profiles", &OpenXRActionMapEditor::_update_interaction_profiles); + + ClassDB::bind_method(D_METHOD("_add_action_set", "name"), &OpenXRActionMapEditor::_add_action_set); + ClassDB::bind_method(D_METHOD("_set_focus_on_action_set", "action_set"), &OpenXRActionMapEditor::_set_focus_on_action_set); + ClassDB::bind_method(D_METHOD("_remove_action_set", "name"), &OpenXRActionMapEditor::_remove_action_set); +} + +void OpenXRActionMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < tabs->get_child_count(); i++) { + Control *tab = static_cast<Control *>(tabs->get_child(i)); + if (tab) { + tab->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } + } + } break; + + case NOTIFICATION_READY: { + _update_action_sets(); + _update_interaction_profiles(); + } break; + } +} + +OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set_editor(Ref<OpenXRActionSet> p_action_set) { + ERR_FAIL_COND_V(p_action_set.is_null(), nullptr); + + OpenXRActionSetEditor *action_set_editor = memnew(OpenXRActionSetEditor(action_map, p_action_set)); + action_set_editor->connect("remove", callable_mp(this, &OpenXRActionMapEditor::_on_remove_action_set)); + action_set_editor->connect("action_removed", callable_mp(this, &OpenXRActionMapEditor::_on_action_removed)); + actionsets_vb->add_child(action_set_editor); + + return action_set_editor; +} + +void OpenXRActionMapEditor::_update_action_sets() { + // out with the old... + while (actionsets_vb->get_child_count() > 0) { + memdelete(actionsets_vb->get_child(0)); + } + + // in with the new... + if (action_map.is_valid()) { + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + _add_action_set_editor(action_set); + } + } +} + +OpenXRInteractionProfileEditorBase *OpenXRActionMapEditor::_add_interaction_profile_editor(Ref<OpenXRInteractionProfile> p_interaction_profile) { + ERR_FAIL_COND_V(p_interaction_profile.is_null(), nullptr); + + String profile_path = p_interaction_profile->get_interaction_profile_path(); + + // need to instance the correct editor for our profile + OpenXRInteractionProfileEditorBase *new_profile_editor = nullptr; + if (profile_path == "placeholder_text") { + // instance specific editor for this type + } else { + // instance generic editor + new_profile_editor = memnew(OpenXRInteractionProfileEditor(action_map, p_interaction_profile)); + } + + // now add it in.. + ERR_FAIL_NULL_V(new_profile_editor, nullptr); + tabs->add_child(new_profile_editor); + new_profile_editor->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tabs->set_tab_button_icon(tabs->get_tab_count() - 1, get_theme_icon(SNAME("close"), SNAME("TabBar"))); + + interaction_profiles.push_back(new_profile_editor); + + return new_profile_editor; +} + +void OpenXRActionMapEditor::_update_interaction_profiles() { + // out with the old... + while (interaction_profiles.size() > 0) { + Node *interaction_profile = interaction_profiles[0]; + interaction_profiles.remove_at(0); + + tabs->remove_child(interaction_profile); + interaction_profile->queue_delete(); + } + + // in with the new... + if (action_map.is_valid()) { + Array new_interaction_profiles = action_map->get_interaction_profiles(); + for (int i = 0; i < new_interaction_profiles.size(); i++) { + Ref<OpenXRInteractionProfile> interaction_profile = new_interaction_profiles[i]; + _add_interaction_profile_editor(interaction_profile); + } + } +} + +OpenXRActionSetEditor *OpenXRActionMapEditor::_add_action_set(String p_name) { + ERR_FAIL_COND_V(action_map.is_null(), nullptr); + Ref<OpenXRActionSet> new_action_set; + + // add our new action set + new_action_set.instantiate(); + new_action_set->set_name(p_name); + new_action_set->set_localized_name(p_name); + action_map->add_action_set(new_action_set); + + // update our editor right away + return _add_action_set_editor(new_action_set); +} + +void OpenXRActionMapEditor::_remove_action_set(String p_name) { + ERR_FAIL_COND(action_map.is_null()); + Ref<OpenXRActionSet> action_set = action_map->find_action_set(p_name); + ERR_FAIL_COND(action_set.is_null()); + + if (action_set->get_action_count() > 0) { + // we should remove these and add to our redo/undo step before calling _remove_action_set + WARN_PRINT("Action set still has associated actions before being removed!"); + } + + // now we remove it + action_map->remove_action_set(action_set); +} + +void OpenXRActionMapEditor::_on_add_action_set() { + ERR_FAIL_COND(action_map.is_null()); + String new_name = "New"; + int count = 0; + + while (action_map->find_action_set(new_name).is_valid()) { + new_name = "New_" + itos(count++); + } + + OpenXRActionSetEditor *new_action_set_editor = _add_action_set(new_name); + + // Make sure our action set is the current tab + tabs->set_current_tab(0); + + call_deferred("_set_focus_on_action_set", new_action_set_editor); +} + +void OpenXRActionMapEditor::_set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor) { + // Scroll down to our new entry + actionsets_scroll->ensure_control_visible(p_action_set_editor); + + // Set focus on this entry + p_action_set_editor->set_focus_on_entry(); +} + +void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { + ERR_FAIL_COND(action_map.is_null()); + + OpenXRActionSetEditor *action_set_editor = Object::cast_to<OpenXRActionSetEditor>(p_action_set_editor); + ERR_FAIL_NULL(action_set_editor); + ERR_FAIL_COND(action_set_editor->get_parent() != actionsets_vb); + Ref<OpenXRActionSet> action_set = action_set_editor->get_action_set(); + ERR_FAIL_COND(action_set.is_null()); + + action_map->remove_action_set(action_set); + actionsets_vb->remove_child(action_set_editor); + action_set_editor->queue_delete(); +} + +void OpenXRActionMapEditor::_on_action_removed() { + // make sure our interaction profiles are updated + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_add_interaction_profile() { + ERR_FAIL_COND(action_map.is_null()); + + PackedStringArray already_selected; + + for (int i = 0; i < action_map->get_interaction_profile_count(); i++) { + already_selected.push_back(action_map->get_interaction_profile(i)->get_interaction_profile_path()); + } + + select_interaction_profile_dialog->open(already_selected); +} + +void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + + Ref<OpenXRInteractionProfile> new_profile; + new_profile.instantiate(); + new_profile->set_interaction_profile_path(p_path); + action_map->add_interaction_profile(new_profile); + + _add_interaction_profile_editor(new_profile); + + tabs->set_current_tab(tabs->get_tab_count() - 1); +} + +void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) { + action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + if (action_map.is_null()) { + if (p_create_new_if_missing) { + action_map.instantiate(); + action_map->create_default_action_sets(); + } else { + EditorNode::get_singleton()->show_warning(TTR("Invalid file, not an OpenXR action map.")); + + edited_path = ""; + header_label->set_text(""); + return; + } + } + + edited_path = p_path; + header_label->set_text(TTR("OpenXR Action map:") + " " + p_path.get_file()); +} + +void OpenXRActionMapEditor::_on_save_action_map() { + Error err = ResourceSaver::save(edited_path, action_map); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), edited_path)); + return; + } + + _update_action_sets(); + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_reset_to_default_layout() { + // create a new one + action_map.unref(); + action_map.instantiate(); + action_map->create_default_action_sets(); + + _update_action_sets(); + _update_interaction_profiles(); +} + +void OpenXRActionMapEditor::_on_tabs_tab_changed(int p_tab) { +} + +void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) { + OpenXRInteractionProfileEditorBase *profile_editor = static_cast<OpenXRInteractionProfileEditorBase *>(tabs->get_tab_control(p_tab)); + ERR_FAIL_NULL(profile_editor); + + Ref<OpenXRInteractionProfile> interaction_profile = profile_editor->get_interaction_profile(); + ERR_FAIL_COND(interaction_profile.is_null()); + + action_map->remove_interaction_profile(interaction_profile); + tabs->remove_child(profile_editor); + profile_editor->queue_delete(); +} + +void OpenXRActionMapEditor::open_action_map(String p_path) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); + + _load_action_map(p_path); + + _update_action_sets(); + _update_interaction_profiles(); +} + +OpenXRActionMapEditor::OpenXRActionMapEditor() { + set_custom_minimum_size(Size2(0.0, 300.0)); + + top_hb = memnew(HBoxContainer); + add_child(top_hb); + + header_label = memnew(Label); + header_label->set_text(String(TTR("Action Map"))); + header_label->set_clip_text(true); + header_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + top_hb->add_child(header_label); + + add_action_set = memnew(Button); + add_action_set->set_text(TTR("Add Action Set")); + add_action_set->set_tooltip(TTR("Add an action set.")); + add_action_set->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_add_action_set)); + top_hb->add_child(add_action_set); + + add_interaction_profile = memnew(Button); + add_interaction_profile->set_text(TTR("Add profile")); + add_interaction_profile->set_tooltip(TTR("Add an interaction profile.")); + add_interaction_profile->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_add_interaction_profile)); + top_hb->add_child(add_interaction_profile); + + VSeparator *vseparator = memnew(VSeparator); + top_hb->add_child(vseparator); + + save_as = memnew(Button); + save_as->set_text(TTR("Save")); + save_as->set_tooltip(TTR("Save this OpenXR action map.")); + save_as->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_save_action_map)); + top_hb->add_child(save_as); + + _default = memnew(Button); + _default->set_text(TTR("Reset to Default")); + _default->set_tooltip(TTR("Reset to default OpenXR action map.")); + _default->connect("pressed", callable_mp(this, &OpenXRActionMapEditor::_on_reset_to_default_layout)); + top_hb->add_child(_default); + + tabs = memnew(TabContainer); + tabs->set_h_size_flags(SIZE_EXPAND_FILL); + tabs->set_v_size_flags(SIZE_EXPAND_FILL); + tabs->connect("tab_changed", callable_mp(this, &OpenXRActionMapEditor::_on_tabs_tab_changed)); + tabs->connect("tab_button_pressed", callable_mp(this, &OpenXRActionMapEditor::_on_tab_button_pressed)); + add_child(tabs); + + actionsets_scroll = memnew(ScrollContainer); + actionsets_scroll->set_h_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + tabs->add_child(actionsets_scroll); + actionsets_scroll->set_name(TTR("Action Sets")); + + actionsets_vb = memnew(VBoxContainer); + actionsets_vb->set_h_size_flags(SIZE_EXPAND_FILL); + actionsets_scroll->add_child(actionsets_vb); + + select_interaction_profile_dialog = memnew(OpenXRSelectInteractionProfileDialog); + 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")); +} + +OpenXRActionMapEditor::~OpenXRActionMapEditor() { +} diff --git a/modules/openxr/editor/openxr_action_map_editor.h b/modules/openxr/editor/openxr_action_map_editor.h new file mode 100644 index 0000000000..dfc941b500 --- /dev/null +++ b/modules/openxr/editor/openxr_action_map_editor.h @@ -0,0 +1,100 @@ +/*************************************************************************/ +/* openxr_action_map_editor.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_ACTION_MAP_EDITOR_H +#define OPENXR_ACTION_MAP_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../editor/openxr_action_set_editor.h" +#include "../editor/openxr_interaction_profile_editor.h" +#include "../editor/openxr_select_interaction_profile_dialog.h" + +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/tab_container.h" + +class OpenXRActionMapEditor : public VBoxContainer { + GDCLASS(OpenXRActionMapEditor, VBoxContainer); + +private: + String edited_path; + Ref<OpenXRActionMap> action_map; + Vector<Node *> interaction_profiles; + + HBoxContainer *top_hb = nullptr; + Label *header_label = nullptr; + Button *add_action_set = nullptr; + Button *add_interaction_profile = nullptr; + Button *load = nullptr; + Button *save_as = nullptr; + Button *_default = nullptr; + TabContainer *tabs = nullptr; + ScrollContainer *actionsets_scroll = nullptr; + VBoxContainer *actionsets_vb = nullptr; + OpenXRSelectInteractionProfileDialog *select_interaction_profile_dialog = nullptr; + + OpenXRActionSetEditor *_add_action_set_editor(Ref<OpenXRActionSet> p_action_set); + void _update_action_sets(); + OpenXRInteractionProfileEditorBase *_add_interaction_profile_editor(Ref<OpenXRInteractionProfile> p_interaction_profile); + void _update_interaction_profiles(); + + OpenXRActionSetEditor *_add_action_set(String p_name); + void _remove_action_set(String p_name); + + void _on_add_action_set(); + void _set_focus_on_action_set(OpenXRActionSetEditor *p_action_set_editor); + void _on_remove_action_set(Object *p_action_set_editor); + void _on_action_removed(); + + void _on_add_interaction_profile(); + void _on_interaction_profile_selected(const String p_path); + + void _load_action_map(const String p_path, bool p_create_new_if_missing = false); + void _on_save_action_map(); + void _on_reset_to_default_layout(); + + void _on_tabs_tab_changed(int p_tab); + void _on_tab_button_pressed(int p_tab); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void open_action_map(String p_path); + + OpenXRActionMapEditor(); + ~OpenXRActionMapEditor(); +}; + +#endif // !OPENXR_ACTION_MAP_EDITOR_H diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp new file mode 100644 index 0000000000..7bf8557c5b --- /dev/null +++ b/modules/openxr/editor/openxr_action_set_editor.cpp @@ -0,0 +1,218 @@ +/*************************************************************************/ +/* openxr_action_set_editor.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_action_set_editor.h" +#include "openxr_action_editor.h" + +void OpenXRActionSetEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("remove", PropertyInfo(Variant::OBJECT, "action_set_editor"))); + ADD_SIGNAL(MethodInfo("action_removed")); +} + +void OpenXRActionSetEditor::_set_fold_icon() { + if (is_expanded) { + fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); + } else { + fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); + } +} + +void OpenXRActionSetEditor::_theme_changed() { + _set_fold_icon(); + add_action->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + rem_action_set->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); +} + +void OpenXRActionSetEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + } break; + } +} + +OpenXRActionEditor *OpenXRActionSetEditor::_add_action_editor(Ref<OpenXRAction> p_action) { + OpenXRActionEditor *action_editor = memnew(OpenXRActionEditor(p_action)); + action_editor->connect("remove", callable_mp(this, &OpenXRActionSetEditor::_on_remove_action)); + actions_vb->add_child(action_editor); + + return action_editor; +} + +void OpenXRActionSetEditor::_update_actions() { + // out with the old... + while (actions_vb->get_child_count() > 0) { + memdelete(actions_vb->get_child(0)); + } + + // in with the new... + Array actions = action_set->get_actions(); + for (int i = 0; i < actions.size(); i++) { + Ref<OpenXRAction> action = actions[i]; + _add_action_editor(action); + } +} + +void OpenXRActionSetEditor::_on_toggle_expand() { + is_expanded = !is_expanded; + actions_vb->set_visible(is_expanded); + _set_fold_icon(); +} + +void OpenXRActionSetEditor::_on_action_set_name_changed(const String p_new_text) { + // TODO validate if entry is allowed + + // If our localized name matches our action set name, set this too + if (action_set->get_name() == action_set->get_localized_name()) { + action_set->set_localized_name(p_new_text); + action_set_localized_name->set_text(p_new_text); + } + action_set->set_name(p_new_text); +} + +void OpenXRActionSetEditor::_on_action_set_localized_name_changed(const String p_new_text) { + action_set->set_localized_name(p_new_text); +} + +void OpenXRActionSetEditor::_on_action_set_priority_changed(const String p_new_text) { + int64_t value = p_new_text.to_int(); + + action_set->set_priority(value); +} + +void OpenXRActionSetEditor::_on_add_action() { + Ref<OpenXRAction> new_action; + + new_action.instantiate(); + new_action->set_name("New"); + new_action->set_localized_name("New"); + action_set->add_action(new_action); + + _add_action_editor(new_action); + + // TODO handle focus +} + +void OpenXRActionSetEditor::_on_remove_action_set() { + emit_signal("remove", this); +} + +void OpenXRActionSetEditor::_on_remove_action(Object *p_action_editor) { + OpenXRActionEditor *action_editor = Object::cast_to<OpenXRActionEditor>(p_action_editor); + ERR_FAIL_NULL(action_editor); + ERR_FAIL_COND(action_editor->get_parent() != actions_vb); + Ref<OpenXRAction> action = action_editor->get_action(); + ERR_FAIL_COND(action.is_null()); + + // TODO add undo/redo action + + // TODO find where this action is used by our interaction profiles and remove it there + + // 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(); + + // Let action map editor know so we can update our interaction profiles + emit_signal("action_removed"); +} + +void OpenXRActionSetEditor::set_focus_on_entry() { + ERR_FAIL_NULL(action_set_name); + action_set_name->grab_focus(); +} + +OpenXRActionSetEditor::OpenXRActionSetEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRActionSet> p_action_set) { + action_map = p_action_map; + action_set = p_action_set; + + set_h_size_flags(Control::SIZE_EXPAND_FILL); + + panel = memnew(PanelContainer); + panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + add_child(panel); + + HBoxContainer *panel_hb = memnew(HBoxContainer); + panel_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + panel->add_child(panel_hb); + + fold_btn = memnew(Button); + fold_btn->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + fold_btn->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_toggle_expand)); + fold_btn->set_flat(true); + panel_hb->add_child(fold_btn); + + main_vb = memnew(VBoxContainer); + main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + panel_hb->add_child(main_vb); + + action_set_hb = memnew(HBoxContainer); + action_set_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(action_set_hb); + + action_set_name = memnew(LineEdit); + action_set_name->set_text(action_set->get_name()); + action_set_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_set_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_name_changed)); + action_set_hb->add_child(action_set_name); + + action_set_localized_name = memnew(LineEdit); + action_set_localized_name->set_text(action_set->get_localized_name()); + action_set_localized_name->set_custom_minimum_size(Size2(150.0, 0.0)); + action_set_localized_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_set_localized_name->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_localized_name_changed)); + action_set_hb->add_child(action_set_localized_name); + + action_set_priority = memnew(TextEdit); + action_set_priority->set_text(itos(action_set->get_priority())); + action_set_priority->set_custom_minimum_size(Size2(50.0, 0.0)); + action_set_priority->connect("text_changed", callable_mp(this, &OpenXRActionSetEditor::_on_action_set_priority_changed)); + action_set_hb->add_child(action_set_priority); + + add_action = memnew(Button); + add_action->set_tooltip("Add Action."); + add_action->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_add_action)); + add_action->set_flat(true); + action_set_hb->add_child(add_action); + + rem_action_set = memnew(Button); + rem_action_set->set_tooltip("Remove Action Set."); + rem_action_set->connect("pressed", callable_mp(this, &OpenXRActionSetEditor::_on_remove_action_set)); + rem_action_set->set_flat(true); + action_set_hb->add_child(rem_action_set); + + actions_vb = memnew(VBoxContainer); + actions_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + main_vb->add_child(actions_vb); + + _update_actions(); +} diff --git a/modules/openxr/editor/openxr_action_set_editor.h b/modules/openxr/editor/openxr_action_set_editor.h new file mode 100644 index 0000000000..f3960dcbf9 --- /dev/null +++ b/modules/openxr/editor/openxr_action_set_editor.h @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* openxr_action_set_editor.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_ACTION_SET_EDITOR_H +#define OPENXR_ACTION_SET_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../action_map/openxr_action_set.h" +#include "openxr_action_editor.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/text_edit.h" + +class OpenXRActionSetEditor : public HBoxContainer { + GDCLASS(OpenXRActionSetEditor, HBoxContainer); + +private: + Ref<OpenXRActionMap> action_map; + Ref<OpenXRActionSet> action_set; + + bool is_expanded = true; + + PanelContainer *panel = nullptr; + Button *fold_btn = nullptr; + VBoxContainer *main_vb = nullptr; + HBoxContainer *action_set_hb = nullptr; + LineEdit *action_set_name = nullptr; + LineEdit *action_set_localized_name = nullptr; + TextEdit *action_set_priority = nullptr; + Button *add_action = nullptr; + Button *rem_action_set = nullptr; + VBoxContainer *actions_vb = nullptr; + + void _set_fold_icon(); + void _theme_changed(); + OpenXRActionEditor *_add_action_editor(Ref<OpenXRAction> p_action); + void _update_actions(); + + void _on_toggle_expand(); + void _on_action_set_name_changed(const String p_new_text); + void _on_action_set_localized_name_changed(const String p_new_text); + void _on_action_set_priority_changed(const String p_new_text); + void _on_add_action(); + void _on_remove_action_set(); + + void _on_remove_action(Object *p_action_editor); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + Ref<OpenXRActionSet> get_action_set() { return action_set; }; + void set_focus_on_entry(); + + OpenXRActionSetEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRActionSet> p_action_set); +}; + +#endif // !OPENXR_ACTION_SET_EDITOR_H diff --git a/modules/openxr/editor/openxr_editor_plugin.cpp b/modules/openxr/editor/openxr_editor_plugin.cpp new file mode 100644 index 0000000000..b87b538511 --- /dev/null +++ b/modules/openxr/editor/openxr_editor_plugin.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* openxr_editor_plugin.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_editor_plugin.h" + +#include "../action_map/openxr_action_map.h" +#include "editor/editor_node.h" + +void OpenXREditorPlugin::edit(Object *p_node) { + if (Object::cast_to<OpenXRActionMap>(p_node)) { + String path = Object::cast_to<OpenXRActionMap>(p_node)->get_path(); + if (path.is_resource_file()) { + action_map_editor->open_action_map(path); + } + } +} + +bool OpenXREditorPlugin::handles(Object *p_node) const { + return (Object::cast_to<OpenXRActionMap>(p_node) != nullptr); +} + +void OpenXREditorPlugin::make_visible(bool p_visible) { +} + +OpenXREditorPlugin::OpenXREditorPlugin() { + action_map_editor = memnew(OpenXRActionMapEditor); + EditorNode::get_singleton()->add_bottom_panel_item(TTR("OpenXR Action Map"), action_map_editor); +} + +OpenXREditorPlugin::~OpenXREditorPlugin() { +} diff --git a/modules/openxr/editor/openxr_editor_plugin.h b/modules/openxr/editor/openxr_editor_plugin.h new file mode 100644 index 0000000000..af8ee7d54c --- /dev/null +++ b/modules/openxr/editor/openxr_editor_plugin.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* openxr_editor_plugin.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_EDITOR_PLUGIN_H +#define OPENXR_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "openxr_action_map_editor.h" + +class OpenXREditorPlugin : public EditorPlugin { + GDCLASS(OpenXREditorPlugin, EditorPlugin); + + OpenXRActionMapEditor *action_map_editor = nullptr; + +public: + virtual String get_name() const override { return "OpenXRPlugin"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_node) override; + virtual bool handles(Object *p_node) const override; + virtual void make_visible(bool p_visible) override; + + OpenXREditorPlugin(); + ~OpenXREditorPlugin(); +}; + +#endif // !OPENXR_EDITOR_PLUGIN_H diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp new file mode 100644 index 0000000000..24ac5494dd --- /dev/null +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -0,0 +1,274 @@ +/*************************************************************************/ +/* openxr_interaction_profile_editor.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_interaction_profile_editor.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +/////////////////////////////////////////////////////////////////////////// +// Interaction profile editor base + +void OpenXRInteractionProfileEditorBase::_bind_methods() { + ClassDB::bind_method(D_METHOD("_add_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_add_binding); + ClassDB::bind_method(D_METHOD("_remove_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_remove_binding); + ClassDB::bind_method(D_METHOD("_update_interaction_profile"), &OpenXRInteractionProfileEditorBase::_update_interaction_profile); +} + +void OpenXRInteractionProfileEditorBase::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_interaction_profile(); + } break; + + case NOTIFICATION_THEME_CHANGED: { + _theme_changed(); + } break; + } +} + +void OpenXRInteractionProfileEditorBase::_add_binding(const String p_action, const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + ERR_FAIL_COND(interaction_profile.is_null()); + + Ref<OpenXRAction> action = action_map->get_action(p_action); + ERR_FAIL_COND(action.is_null()); + + Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action); + if (binding.is_null()) { + // create a new binding + binding.instantiate(); + binding->set_action(action); + interaction_profile->add_binding(binding); + } + + binding->add_path(p_path); + + // Update our toplevel paths + action->set_toplevel_paths(action_map->get_top_level_paths(action)); + + call_deferred("_update_interaction_profile"); +} + +void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action, const String p_path) { + ERR_FAIL_COND(action_map.is_null()); + ERR_FAIL_COND(interaction_profile.is_null()); + + Ref<OpenXRAction> action = action_map->get_action(p_action); + ERR_FAIL_COND(action.is_null()); + + Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action); + if (binding.is_valid()) { + binding->remove_path(p_path); + + if (binding->get_path_count() == 0) { + interaction_profile->remove_binding(binding); + } + + // Update our toplevel paths + action->set_toplevel_paths(action_map->get_top_level_paths(action)); + + call_deferred("_update_interaction_profile"); + } +} + +OpenXRInteractionProfileEditorBase::OpenXRInteractionProfileEditorBase(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile) { + action_map = p_action_map; + interaction_profile = p_interaction_profile; + String profile_path = interaction_profile->get_interaction_profile_path(); + String profile_name = profile_path; + + profile_def = OpenXRDefs::get_profile(profile_path); + if (profile_def != nullptr) { + profile_name = profile_def->display_name; + } + + set_name(profile_name); + set_h_size_flags(SIZE_EXPAND_FILL); + set_v_size_flags(SIZE_EXPAND_FILL); +} + +/////////////////////////////////////////////////////////////////////////// +// Default interaction profile editor + +void OpenXRInteractionProfileEditor::select_action_for(const String p_io_path) { + selecting_for_io_path = p_io_path; + select_action_dialog->open(); +} + +void OpenXRInteractionProfileEditor::action_selected(const String p_action) { + _add_binding(p_action, selecting_for_io_path); + selecting_for_io_path = ""; +} + +void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, const OpenXRDefs::IOPath *p_io_path) { + HBoxContainer *path_hb = memnew(HBoxContainer); + path_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + p_container->add_child(path_hb); + + Label *path_label = memnew(Label); + path_label->set_text(p_io_path->display_name); + path_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + path_hb->add_child(path_label); + + Label *type_label = memnew(Label); + switch (p_io_path->action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: { + type_label->set_text(TTR("Boolean")); + } break; + case OpenXRAction::OPENXR_ACTION_FLOAT: { + type_label->set_text(TTR("Float")); + } break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: { + type_label->set_text(TTR("Vector2")); + } break; + case OpenXRAction::OPENXR_ACTION_POSE: { + type_label->set_text(TTR("Pose")); + } break; + case OpenXRAction::OPENXR_ACTION_HAPTIC: { + type_label->set_text(TTR("Haptic")); + } break; + default: { + type_label->set_text(TTR("Unknown")); + } break; + } + type_label->set_custom_minimum_size(Size2(50.0, 0.0)); + path_hb->add_child(type_label); + + Button *path_add = memnew(Button); + path_add->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + path_add->set_flat(true); + Vector<Variant> add_binds; + add_binds.push_back(String(p_io_path->openxr_path)); + path_add->connect("pressed", callable_mp(this, &OpenXRInteractionProfileEditor::select_action_for), add_binds); + path_hb->add_child(path_add); + + if (interaction_profile.is_valid()) { + String io_path = String(p_io_path->openxr_path); + Array bindings = interaction_profile->get_bindings(); + for (int i = 0; i < bindings.size(); i++) { + Ref<OpenXRIPBinding> binding = bindings[i]; + if (binding->has_path(io_path)) { + Ref<OpenXRAction> action = binding->get_action(); + + HBoxContainer *action_hb = memnew(HBoxContainer); + action_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + p_container->add_child(action_hb); + + Control *indent_node = memnew(Control); + indent_node->set_custom_minimum_size(Size2(10.0, 0.0)); + action_hb->add_child(indent_node); + + Label *action_label = memnew(Label); + action_label->set_text(action->get_name_with_set() + ": " + action->get_localized_name()); + action_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + action_hb->add_child(action_label); + + Button *action_rem = memnew(Button); + action_rem->set_flat(true); + action_rem->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + Vector<Variant> remove_binds; + remove_binds.push_back(action->get_name_with_set()); + remove_binds.push_back(String(p_io_path->openxr_path)); + action_rem->connect("pressed", callable_mp((OpenXRInteractionProfileEditorBase *)this, &OpenXRInteractionProfileEditorBase::_remove_binding), remove_binds); + action_hb->add_child(action_rem); + } + } + } +} + +void OpenXRInteractionProfileEditor::_update_interaction_profile() { + ERR_FAIL_NULL(profile_def); + + // out with the old... + while (main_hb->get_child_count() > 0) { + memdelete(main_hb->get_child(0)); + } + + // in with the new... + + // Determine toplevel paths + Vector<const OpenXRDefs::TopLevelPath *> top_level_paths; + for (int i = 0; i < profile_def->io_path_count; i++) { + const OpenXRDefs::IOPath *io_path = &profile_def->io_paths[i]; + + if (!top_level_paths.has(io_path->top_level_path)) { + top_level_paths.push_back(io_path->top_level_path); + } + } + + for (int i = 0; i < top_level_paths.size(); i++) { + PanelContainer *panel = memnew(PanelContainer); + panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_hb->add_child(panel); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + + VBoxContainer *container = memnew(VBoxContainer); + panel->add_child(container); + + Label *label = memnew(Label); + label->set_text(top_level_paths[i]->display_name); + container->add_child(label); + + for (int j = 0; j < profile_def->io_path_count; j++) { + const OpenXRDefs::IOPath *io_path = &profile_def->io_paths[j]; + if (io_path->top_level_path == top_level_paths[i]) { + _add_io_path(container, io_path); + } + } + } +} + +void OpenXRInteractionProfileEditor::_theme_changed() { + for (int i = 0; i < main_hb->get_child_count(); i++) { + Control *panel = static_cast<Control *>(main_hb->get_child(i)); + if (panel) { + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + } + } +} + +OpenXRInteractionProfileEditor::OpenXRInteractionProfileEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile) : + OpenXRInteractionProfileEditorBase(p_action_map, p_interaction_profile) { + // TODO background of scrollbox should be darker with our VBoxContainers we're adding in _update_interaction_profile the normal color + + main_hb = memnew(HBoxContainer); + add_child(main_hb); + + select_action_dialog = memnew(OpenXRSelectActionDialog(p_action_map)); + select_action_dialog->connect("action_selected", callable_mp(this, &OpenXRInteractionProfileEditor::action_selected)); + add_child(select_action_dialog); + + _update_interaction_profile(); +} diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.h b/modules/openxr/editor/openxr_interaction_profile_editor.h new file mode 100644 index 0000000000..f50da1a003 --- /dev/null +++ b/modules/openxr/editor/openxr_interaction_profile_editor.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* openxr_interaction_profile_editor.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_INTERACTION_PROFILE_EDITOR_H +#define OPENXR_INTERACTION_PROFILE_EDITOR_H + +#include "../action_map/openxr_action_map.h" +#include "../action_map/openxr_defs.h" +#include "../action_map/openxr_interaction_profile.h" +#include "scene/gui/scroll_container.h" + +#include "openxr_select_action_dialog.h" + +class OpenXRInteractionProfileEditorBase : public ScrollContainer { + GDCLASS(OpenXRInteractionProfileEditorBase, ScrollContainer); + +protected: + Ref<OpenXRInteractionProfile> interaction_profile; + Ref<OpenXRActionMap> action_map; + + static void _bind_methods(); + void _notification(int p_what); + + const OpenXRDefs::InteractionProfile *profile_def = nullptr; + +public: + Ref<OpenXRInteractionProfile> get_interaction_profile() { return interaction_profile; } + + virtual void _update_interaction_profile() {} + virtual void _theme_changed() {} + void _add_binding(const String p_action, const String p_path); + void _remove_binding(const String p_action, const String p_path); + + OpenXRInteractionProfileEditorBase(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile); +}; + +class OpenXRInteractionProfileEditor : public OpenXRInteractionProfileEditorBase { + GDCLASS(OpenXRInteractionProfileEditor, OpenXRInteractionProfileEditorBase); + +private: + String selecting_for_io_path; + HBoxContainer *main_hb = nullptr; + OpenXRSelectActionDialog *select_action_dialog = nullptr; + + void _add_io_path(VBoxContainer *p_container, const OpenXRDefs::IOPath *p_io_path); + +public: + void select_action_for(const String p_io_path); + void action_selected(const String p_action); + + virtual void _update_interaction_profile() override; + virtual void _theme_changed() override; + OpenXRInteractionProfileEditor(Ref<OpenXRActionMap> p_action_map, Ref<OpenXRInteractionProfile> p_interaction_profile); +}; + +#endif // !OPENXR_INTERACTION_PROFILE_EDITOR_H diff --git a/modules/openxr/editor/openxr_select_action_dialog.cpp b/modules/openxr/editor/openxr_select_action_dialog.cpp new file mode 100644 index 0000000000..c2a2965200 --- /dev/null +++ b/modules/openxr/editor/openxr_select_action_dialog.cpp @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* openxr_select_action_dialog.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_select_action_dialog.h" +#include "editor/editor_node.h" + +void OpenXRSelectActionDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("action_selected", PropertyInfo(Variant::STRING, "action"))); +} + +void OpenXRSelectActionDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void OpenXRSelectActionDialog::_on_select_action(const String p_action) { + if (selected_action != "") { + NodePath button_path = action_buttons[selected_action]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(true); + } + } + + selected_action = p_action; + + if (selected_action != "") { + NodePath button_path = action_buttons[selected_action]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(false); + } + } +} + +void OpenXRSelectActionDialog::open() { + ERR_FAIL_COND(action_map.is_null()); + + // out with the old... + while (main_vb->get_child_count() > 0) { + memdelete(main_vb->get_child(0)); + } + + selected_action = ""; + action_buttons.clear(); + + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + Ref<OpenXRActionSet> action_set = action_sets[i]; + + Label *action_set_label = memnew(Label); + action_set_label->set_text(action_set->get_localized_name()); + main_vb->add_child(action_set_label); + + Array actions = action_set->get_actions(); + for (int j = 0; j < actions.size(); j++) { + Ref<OpenXRAction> action = actions[j]; + + HBoxContainer *action_hb = memnew(HBoxContainer); + main_vb->add_child(action_hb); + + Control *indent_node = memnew(Control); + indent_node->set_custom_minimum_size(Size2(10.0, 0.0)); + action_hb->add_child(indent_node); + + Button *action_button = memnew(Button); + String action_name = action->get_name_with_set(); + Vector<Variant> binds; + binds.push_back(action_name); + action_button->set_flat(true); + action_button->set_text(action->get_name() + ": " + action->get_localized_name()); + action_button->connect("pressed", callable_mp(this, &OpenXRSelectActionDialog::_on_select_action), binds); + action_hb->add_child(action_button); + + action_buttons[action_name] = action_button->get_path(); + } + } + + popup_centered(); +} + +void OpenXRSelectActionDialog::ok_pressed() { + if (selected_action == "") { + return; + } + + emit_signal("action_selected", selected_action); + + hide(); +} + +OpenXRSelectActionDialog::OpenXRSelectActionDialog(Ref<OpenXRActionMap> p_action_map) { + action_map = p_action_map; + + set_title(TTR("Select an action")); + + scroll = memnew(ScrollContainer); + scroll->set_custom_minimum_size(Size2(600.0, 400.0)); + add_child(scroll); + + main_vb = memnew(VBoxContainer); + main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll->add_child(main_vb); +} diff --git a/modules/openxr/editor/openxr_select_action_dialog.h b/modules/openxr/editor/openxr_select_action_dialog.h new file mode 100644 index 0000000000..ea2c30373b --- /dev/null +++ b/modules/openxr/editor/openxr_select_action_dialog.h @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* openxr_select_action_dialog.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_SELECT_ACTION_DIALOG_H +#define OPENXR_SELECT_ACTION_DIALOG_H + +#include "../action_map/openxr_action_map.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +class OpenXRSelectActionDialog : public ConfirmationDialog { + GDCLASS(OpenXRSelectActionDialog, ConfirmationDialog); + +private: + Ref<OpenXRActionMap> action_map; + String selected_action; + Dictionary action_buttons; + + VBoxContainer *main_vb = nullptr; + ScrollContainer *scroll = nullptr; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void _on_select_action(const String p_action); + void open(); + virtual void ok_pressed() override; + + OpenXRSelectActionDialog(Ref<OpenXRActionMap> p_action_map); +}; + +#endif // !OPENXR_SELECT_ACTION_DIALOG_H diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp new file mode 100644 index 0000000000..12b110f146 --- /dev/null +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp @@ -0,0 +1,125 @@ +/*************************************************************************/ +/* openxr_select_interaction_profile_dialog.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_select_interaction_profile_dialog.h" + +void OpenXRSelectInteractionProfileDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("interaction_profile_selected", PropertyInfo(Variant::STRING, "interaction_profile"))); +} + +void OpenXRSelectInteractionProfileDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + } break; + } +} + +void OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile(const String p_interaction_profile) { + if (selected_interaction_profile != "") { + NodePath button_path = ip_buttons[selected_interaction_profile]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(true); + } + } + + selected_interaction_profile = p_interaction_profile; + + if (selected_interaction_profile != "") { + NodePath button_path = ip_buttons[selected_interaction_profile]; + Button *button = static_cast<Button *>(get_node(button_path)); + if (button != nullptr) { + button->set_flat(false); + } + } +} + +void OpenXRSelectInteractionProfileDialog::open(PackedStringArray p_do_not_include) { + int available_count = 0; + + // out with the old... + while (main_vb->get_child_count() > 0) { + memdelete(main_vb->get_child(0)); + } + + selected_interaction_profile = ""; + ip_buttons.clear(); + + // in with the new + PackedStringArray interaction_profiles = OpenXRDefs::get_interaction_profile_paths(); + for (int i = 0; i < interaction_profiles.size(); i++) { + String path = interaction_profiles[i]; + if (!p_do_not_include.has(path)) { + Button *ip_button = memnew(Button); + Vector<Variant> binds; + binds.push_back(path); + ip_button->set_flat(true); + ip_button->set_text(OpenXRDefs::get_profile(path)->display_name); + ip_button->connect("pressed", callable_mp(this, &OpenXRSelectInteractionProfileDialog::_on_select_interaction_profile), binds); + main_vb->add_child(ip_button); + + ip_buttons[path] = ip_button->get_path(); + available_count++; + } + } + + if (available_count == 0) { + // give warning that we have all profiles selected + + } else { + // TODO maybe if we only have one, auto select it? + + popup_centered(); + } +} + +void OpenXRSelectInteractionProfileDialog::ok_pressed() { + if (selected_interaction_profile == "") { + return; + } + + emit_signal("interaction_profile_selected", selected_interaction_profile); + + hide(); +} + +OpenXRSelectInteractionProfileDialog::OpenXRSelectInteractionProfileDialog() { + set_title("Select an interaction profile"); + + scroll = memnew(ScrollContainer); + scroll->set_custom_minimum_size(Size2(600.0, 400.0)); + add_child(scroll); + + main_vb = memnew(VBoxContainer); + // main_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll->add_child(main_vb); +} diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.h b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h new file mode 100644 index 0000000000..d177861ff3 --- /dev/null +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* openxr_select_interaction_profile_dialog.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_SELECT_INTERACTION_PROFILE_DIALOG_H +#define OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H + +#include "../action_map/openxr_defs.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/text_edit.h" + +class OpenXRSelectInteractionProfileDialog : public ConfirmationDialog { + GDCLASS(OpenXRSelectInteractionProfileDialog, ConfirmationDialog); + +private: + String selected_interaction_profile; + Dictionary ip_buttons; + + VBoxContainer *main_vb = nullptr; + ScrollContainer *scroll = nullptr; + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void _on_select_interaction_profile(const String p_interaction_profile); + void open(PackedStringArray p_do_not_include); + virtual void ok_pressed() override; + + OpenXRSelectInteractionProfileDialog(); +}; + +#endif // !OPENXR_SELECT_INTERACTION_PROFILE_DIALOG_H diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp new file mode 100644 index 0000000000..3bd4db169c --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* openxr_android_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_android_extension.h" + +#include <openxr/openxr.h> +#include <openxr/openxr_platform.h> + +OpenXRAndroidExtension *OpenXRAndroidExtension::singleton = nullptr; + +OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { + return 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"); + + // TODO fix this code, this is still code from GDNative! + JNIEnv *env = android_api->godot_android_get_env(); + JavaVM *vm; + env->GetJavaVM(&vm); + jobject activity_object = env->NewGlobalRef(android_api->godot_android_get_activity()); + + XrLoaderInitInfoAndroidKHR loader_init_info_android = { + .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, + .next = nullptr, + .applicationVM = vm, + .applicationContext = activity_object + }; + xrInitializeLoaderKHR((const XrLoaderInitInfoBaseHeaderKHR *)&loader_init_info_android); + ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR"); +} + +OpenXRAndroidExtension::~OpenXRAndroidExtension() { + singleton = nullptr; +} diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h new file mode 100644 index 0000000000..e102197a55 --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* openxr_android_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_ANDROID_EXTENSION_H +#define OPENXR_ANDROID_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRAndroidExtension : public OpenXRExtensionWrapper { +public: + static OpenXRAndroidExtension *get_singleton(); + + OpenXRAndroidExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRAndroidExtension() override; + +private: + static OpenXRAndroidExtension *singleton; +}; + +#endif // !OPENXR_ANDROID_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h new file mode 100644 index 0000000000..019dffa2a8 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_provider.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* openxr_composition_layer_provider.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_PROVIDER_H +#define OPENXR_COMPOSITION_LAYER_PROVIDER_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; }; +}; + +#endif // OPENXR_COMPOSITION_LAYER_PROVIDER_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h new file mode 100644 index 0000000000..f3064041b8 --- /dev/null +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -0,0 +1,107 @@ +/*************************************************************************/ +/* openxr_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_EXTENSION_WRAPPER_H +#define OPENXR_EXTENSION_WRAPPER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/templates/map.h" +#include "core/templates/rid.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +class OpenXRAPI; +class OpenXRActionMap; + +class OpenXRExtensionWrapper { +protected: + OpenXRAPI *openxr_api = nullptr; + + // Store extension we require. + // If bool pointer is a nullptr this means this extension is mandatory and initialisation will fail if it is not available + // If bool pointer is set, value will be set to true or false depending on whether extension is available + Map<const char *, bool *> request_extensions; + +public: + virtual Map<const char *, bool *> get_request_extensions() { + return request_extensions; + } + + // These functions allow an extension to add entries to a struct chain. + // `p_next_pointer` points to the last struct that was created for this chain + // and should be used as the value for the `pNext` pointer in the first struct you add. + // You should return the pointer to the last struct you define as your result. + // If you are not adding any structs, just return `p_next_pointer`. + // See existing extensions for examples of this implementation. + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + 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_instance_created(const XrInstance p_instance) {} + virtual void on_instance_destroyed() {} + virtual void on_session_created(const XrSession p_instance) {} + virtual void on_process() {} + virtual void on_pre_render() {} + virtual void on_session_destroyed() {} + + virtual void on_state_idle() {} + virtual void on_state_ready() {} + virtual void on_state_synchronized() {} + virtual void on_state_visible() {} + virtual void on_state_focused() {} + virtual void on_state_stopping() {} + virtual void on_state_loss_pending() {} + virtual void on_state_exiting() {} + + // Returns true if the event was handled, false otherwise. + virtual bool on_event_polled(const XrEventDataBuffer &event) { + return false; + } + + OpenXRExtensionWrapper(OpenXRAPI *p_openxr_api) { openxr_api = p_openxr_api; }; + virtual ~OpenXRExtensionWrapper() = default; +}; + +class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper { +public: + virtual void get_usable_swapchain_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, CameraMatrix &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; + + OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api){}; +}; + +#endif // ~OPENXR_EXTENSION_WRAPPER_H diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp new file mode 100644 index 0000000000..8736296f7a --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -0,0 +1,750 @@ +/*************************************************************************/ +/* openxr_vulkan_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 "core/string/print_string.h" + +#include "../extensions/openxr_vulkan_extension.h" +#include "../openxr_api.h" +#include "../openxr_util.h" +#include "servers/rendering/renderer_rd/renderer_storage_rd.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); + + request_extensions[XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME] = nullptr; // must be available + + ERR_FAIL_NULL(openxr_api); +} + +OpenXRVulkanExtension::~OpenXRVulkanExtension() { + VulkanContext::set_vulkan_hooks(nullptr); +} + +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); +} + +bool OpenXRVulkanExtension::check_graphics_api_support(XrVersion p_desired_version) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrGraphicsRequirementsVulkan2KHR vulkan_requirements = { + XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR, // type + nullptr, // next + 0, // minApiVersionSupported + 0 // maxApiVersionSupported + }; + + XrResult result = xrGetVulkanGraphicsRequirements2KHR(openxr_api->get_instance(), openxr_api->get_system_id(), &vulkan_requirements); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get vulkan graphics requirements [", openxr_api->get_error_string(result), "]"); + return false; + } + + // #ifdef DEBUG + print_line("OpenXR: XrGraphicsRequirementsVulkan2KHR:"); + print_line(" - minApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line(" - maxApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + // #endif + + if (p_desired_version < vulkan_requirements.minApiVersionSupported) { + print_line("OpenXR: Requested Vulkan 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(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + return false; + } + + if (p_desired_version > vulkan_requirements.maxApiVersionSupported) { + print_line("OpenXR: Requested Vulkan 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(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + } + + 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; + uint32_t major_version = VK_VERSION_MAJOR(vulkan_version); + uint32_t minor_version = VK_VERSION_MINOR(vulkan_version); + uint32_t patch_version = VK_VERSION_PATCH(vulkan_version); + XrVersion desired_version = XR_MAKE_VERSION(major_version, minor_version, patch_version); + + // check if this is supported + if (!check_graphics_api_support(desired_version)) { + return false; + } + + XrVulkanInstanceCreateInfoKHR xr_vulkan_instance_info = { + XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + p_vulkan_create_info, // vulkanCreateInfo + nullptr, // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanInstanceKHR(openxr_api->get_instance(), &xr_vulkan_instance_info, &vulkan_instance, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan instance [", openxr_api->get_error_string(result), "]"); + return false; + } + + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_INCOMPATIBLE_DRIVER, false, + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_EXTENSION_NOT_PRESENT, false, + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result, false, + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n" + "vkCreateInstance Failure"); + + *r_instance = vulkan_instance; + + 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); + + XrVulkanGraphicsDeviceGetInfoKHR get_info = { + XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + vulkan_instance, // vulkanInstance + }; + + XrResult result = xrGetVulkanGraphicsDevice2KHR(openxr_api->get_instance(), &get_info, &vulkan_physical_device); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain vulkan physical device [", openxr_api->get_error_string(result), "]"); + return false; + } + + *r_device = vulkan_physical_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); + + // the first entry in our queue list should be the one we need to remember... + vulkan_queue_family_index = p_device_create_info->pQueueCreateInfos[0].queueFamilyIndex; + vulkan_queue_index = 0; // ?? + + XrVulkanDeviceCreateInfoKHR create_info = { + XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + vulkan_physical_device, // vulkanPhysicalDevice + p_device_create_info, // vulkanCreateInfo + nullptr // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanDeviceKHR(openxr_api->get_instance(), &create_info, &vulkan_device, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan device [", openxr_api->get_error_string(result), "]"); + return false; + } + + if (vk_result != VK_SUCCESS) { + print_line("OpenXR: Failed to create vulkan device [vulkan error", vk_result, "]"); + } + + *r_device = vulkan_device; + + return true; +} + +XrGraphicsBindingVulkanKHR OpenXRVulkanExtension::graphics_binding_vulkan; + +void *OpenXRVulkanExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) { + graphics_binding_vulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; + graphics_binding_vulkan.next = p_next_pointer; + graphics_binding_vulkan.instance = vulkan_instance; + graphics_binding_vulkan.physicalDevice = vulkan_physical_device; + graphics_binding_vulkan.device = vulkan_device; + graphics_binding_vulkan.queueFamilyIndex = vulkan_queue_family_index; + graphics_binding_vulkan.queueIndex = vulkan_queue_index; + + return &graphics_binding_vulkan; +} + +void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { + // We might want to do more here especially if we keep things in linear color space + // Possibly add in R10G10B10A2 as an option if we're using the mobile renderer. + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_UINT); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_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; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL_V(rendering_server, false); + RenderingDevice *rendering_device = rendering_server->get_rendering_device(); + ERR_FAIL_NULL_V(rendering_device, 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; + } + + images = (XrSwapchainImageVulkanKHR *)memalloc(sizeof(XrSwapchainImageVulkanKHR) * swapchain_length); + 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++) { + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; + images[i].next = nullptr; + images[i].image = VK_NULL_HANDLE; + } + + 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 = (SwapchainGraphicsData *)memalloc(sizeof(SwapchainGraphicsData)); + 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); + + 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; + + switch (p_swapchain_format) { + case VK_FORMAT_R8G8B8A8_SRGB: + // Even though this is an sRGB framebuffer format we're using UNORM here. + // The reason here is because Godot does a linear to sRGB conversion while + // with the sRGB format, this conversion would be doubled by the hardware. + // This also means we're reading the values as is for our preview on screen. + // The OpenXR runtime however is still treating this as an sRGB format and + // will thus do an sRGB -> Linear conversion as expected. + // format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM; + break; + case VK_FORMAT_B8G8R8A8_SRGB: + // format = RenderingDevice::DATA_FORMAT_B8G8R8A8_SRGB; + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM; + break; + case VK_FORMAT_R8G8B8A8_UINT: + format = RenderingDevice::DATA_FORMAT_R8G8B8A8_UINT; + break; + case VK_FORMAT_B8G8R8A8_UINT: + format = RenderingDevice::DATA_FORMAT_B8G8R8A8_UINT; + break; + default: + // continue with our default value + print_line("Unsupported swapchain format ", p_swapchain_format); + break; + } + + switch (p_sample_count) { + case 1: + samples = RenderingDevice::TEXTURE_SAMPLES_1; + break; + case 2: + samples = RenderingDevice::TEXTURE_SAMPLES_2; + break; + case 4: + samples = RenderingDevice::TEXTURE_SAMPLES_4; + break; + case 8: + samples = RenderingDevice::TEXTURE_SAMPLES_8; + break; + case 16: + samples = RenderingDevice::TEXTURE_SAMPLES_16; + break; + case 32: + samples = RenderingDevice::TEXTURE_SAMPLES_32; + break; + case 64: + samples = RenderingDevice::TEXTURE_SAMPLES_64; + break; + default: + // continue with our default value + print_line("Unsupported sample count ", p_sample_count); + break; + } + + Vector<RID> image_rids; + Vector<RID> framebuffers; + + // create Godot texture objects for each entry in our swapchain + for (uint64_t i = 0; i < swapchain_length; i++) { + RID image_rid = rendering_device->texture_create_from_extension( + p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY, + format, + samples, + usage_flags, + (uint64_t)images[i].image, + p_width, + p_height, + 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); + } + } + + data->image_rids = image_rids; + data->framebuffers = framebuffers; + + memfree(images); + + return true; +} + +bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) { + // Even though this is a Vulkan renderer we're using OpenGL coordinate systems + 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.matrix[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) { + SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; + ERR_FAIL_NULL_V(data, false); + ERR_FAIL_COND_V(p_from_render_target.is_null(), false); + ERR_FAIL_NULL_V(RendererStorageRD::base_singleton, false); + + RID source_image = RendererStorageRD::base_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); + + // Our vulkan extension can only be used in conjunction with our vulkan renderer. + // We need access to the effects object in order to have access to our copy logic. + // Breaking all the rules but there is no nice way to do this. + EffectsRD *effects = RendererStorageRD::base_singleton->get_effects(); + ERR_FAIL_NULL_V(effects, false); + effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); + + return true; +} + +void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { + if (*p_swapchain_graphics_data == nullptr) { + return; + } + + SwapchainGraphicsData *data = (SwapchainGraphicsData *)*p_swapchain_graphics_data; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + 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++) { + // 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]); + } + data->framebuffers.clear(); + + memdelete(data); + *p_swapchain_graphics_data = nullptr; +} + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +String OpenXRVulkanExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This really should be in vulkan_context... + VkFormat format = VkFormat(p_swapchain_format); + switch (format) { + ENUM_TO_STRING_CASE(VK_FORMAT_UNDEFINED) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4_UNORM_PACK8) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4B4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B4G4R4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G6B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G6R5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G5B5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G5R5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_A1R5G5B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SRGB_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_B10G11R11_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_X8_D24_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D24_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_UFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_SFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_G8B8G8R8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8G8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G16B16G16R16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B16G16R16G16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_MAX_ENUM) + default: { + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); + } break; + } +} diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h new file mode 100644 index 0000000000..cf55ae264f --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* openxr_vulkan_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_VULKAN_EXTENSION_H +#define OPENXR_VULKAN_EXTENSION_H + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" + +#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 + +struct XrGraphicsRequirementsVulkanKHR; +struct XrVulkanInstanceCreateInfoKHR; +struct XrVulkanGraphicsDeviceGetInfoKHR; +struct XrVulkanDeviceCreateInfoKHR; +struct XrGraphicsBindingVulkanKHR; + +class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { +public: + OpenXRVulkanExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRVulkanExtension() 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 bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override; + virtual bool get_physical_device(VkPhysicalDevice *r_device) override; + 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 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, CameraMatrix &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; + +private: + static OpenXRVulkanExtension *singleton; + static XrGraphicsBindingVulkanKHR graphics_binding_vulkan; // declaring this as static so we don't need to know its size and we only need it once when creating our session + + struct SwapchainGraphicsData { + bool is_multiview; + Vector<RID> image_rids; + Vector<RID> framebuffers; + }; + + bool check_graphics_api_support(XrVersion p_desired_version); + + VkInstance vulkan_instance; + VkPhysicalDevice vulkan_physical_device; + VkDevice vulkan_device; + uint32_t vulkan_queue_family_index; + uint32_t vulkan_queue_index; + + 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); +}; + +#endif // !OPENXR_VULKAN_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp new file mode 100644 index 0000000000..ae3617938e --- /dev/null +++ b/modules/openxr/openxr_api.cpp @@ -0,0 +1,2422 @@ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#include "openxr_api.h" +#include "openxr_util.h" + +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/os/memory.h" +#include "core/version.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +#ifdef ANDROID_ENABLED +#include "extensions/openxr_android_extension.h" +#endif + +#ifdef VULKAN_ENABLED +#include "extensions/openxr_vulkan_extension.h" +#endif + +#include "openxr_interface.h" + +OpenXRAPI *OpenXRAPI::singleton = nullptr; + +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" + + if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + return false; + + // bool enabled = GLOBAL_GET("xr/openxr/in_editor"); // EDITOR_GET("xr/openxr/in_editor"); + // return enabled; +#else + // we should never get here, editor hint won't be true if the editor isn't compiled in. + return false; +#endif + } else { + bool enabled = GLOBAL_GET("xr/openxr/enabled"); + return enabled; + } +} + +OpenXRAPI *OpenXRAPI::get_singleton() { + return singleton; +} + +String OpenXRAPI::get_default_action_map_resource_name() { + String name = GLOBAL_GET("xr/openxr/default_action_map"); + + return name; +} + +String OpenXRAPI::get_error_string(XrResult result) { + if (XR_SUCCEEDED(result)) { + return String("Succeeded"); + } + + if (instance == XR_NULL_HANDLE) { + Array args; + args.push_back(Variant(result)); + return String("Error code {0}").format(args); + } + + char resultString[XR_MAX_RESULT_STRING_SIZE]; + xrResultToString(instance, result, resultString); + + return String(resultString); +} + +String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This is rendering engine dependent... + if (graphics_extension) { + return graphics_extension->get_swapchain_format_name(p_swapchain_format); + } + + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); +} + +bool OpenXRAPI::load_layer_properties() { + // This queries additional layers that are available and can be initialized when we create our OpenXR instance + if (layer_properties != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateApiLayerProperties(0, &num_layer_properties, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of api layer properties"); + + layer_properties = (XrApiLayerProperties *)memalloc(sizeof(XrApiLayerProperties) * num_layer_properties); + ERR_FAIL_NULL_V(layer_properties, false); + for (uint32_t i = 0; i < num_layer_properties; i++) { + layer_properties[i].type = XR_TYPE_API_LAYER_PROPERTIES; + layer_properties[i].next = nullptr; + } + + 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); + } +#endif + + return true; +} + +bool OpenXRAPI::load_supported_extensions() { + // This queries supported extensions that are available and can be initialized when we create our OpenXR instance + + if (supported_extensions != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &num_supported_extensions, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of extension properties"); + + supported_extensions = (XrExtensionProperties *)memalloc(sizeof(XrExtensionProperties) * num_supported_extensions); + ERR_FAIL_NULL_V(supported_extensions, false); + + // set our types + for (uint32_t i = 0; i < num_supported_extensions; i++) { + supported_extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES; + supported_extensions[i].next = nullptr; + } + 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); + } +#endif + + return true; +} + +bool OpenXRAPI::is_extension_supported(const char *p_extension) const { + for (uint32_t i = 0; i < num_supported_extensions; i++) { + if (strcmp(supported_extensions[i].extensionName, p_extension) == 0) { +#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; +} + +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(); + if (len < p_buffer_len - 1) { + // was having weird CI issues with strcpy so.... + memcpy(p_buffer, char_string.get_data(), len); + p_buffer[len] = '\0'; + } else { + memcpy(p_buffer, char_string.get_data(), p_buffer_len - 1); + p_buffer[p_buffer_len - 1] = '\0'; + } +} + +bool OpenXRAPI::create_instance() { + // Create our OpenXR instance, this will query any registered extension wrappers for extensions we need to enable. + + // Append the extensions requested by the registered extension wrappers. + Map<const char *, bool *> requested_extensions; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + Map<const char *, bool *> wrapper_request_extensions = wrapper->get_request_extensions(); + + // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end()); + for (auto &requested_extension : wrapper_request_extensions) { + requested_extensions[requested_extension.key] = requested_extension.value; + } + } + + // 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(); + for (auto &requested_extension : requested_extensions) { + 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!"); + } else { + // set this extension as not supported + *requested_extension.value = false; + } + } else if (requested_extension.value != nullptr) { + // set this extension as supported + *requested_extension.value = true; + + // and record that we want to enable it + enabled_extensions.push_back(requested_extension.key); + } else { + // record that we want to enable this + enabled_extensions.push_back(requested_extension.key); + } + } + + // Get our project name + String project_name = GLOBAL_GET("application/config/name"); + + // Create our OpenXR instance + XrApplicationInfo application_info{ + "", // applicationName, we'll set this down below + 1, // applicationVersion, we don't currently have this + "Godot Game Engine", // engineName + VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. + XR_CURRENT_API_VERSION // apiVersion + }; + + XrInstanceCreateInfo instance_create_info = { + XR_TYPE_INSTANCE_CREATE_INFO, // type + nullptr, // next + 0, // createFlags + application_info, // applicationInfo + 0, // enabledApiLayerCount, need to find out if we need support for this? + nullptr, // enabledApiLayerNames + uint32_t(enabled_extensions.size()), // enabledExtensionCount + enabled_extensions.ptr() // enabledExtensionNames + }; + + copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + + XrResult result = xrCreateInstance(&instance_create_info, &instance); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); + + // from this point on we can use get_error_string to get more info about our errors... + + XrInstanceProperties instanceProps = { + XR_TYPE_INSTANCE_PROPERTIES, // type; + nullptr, // next + 0, // runtimeVersion, from here will be set by our get call + "" // runtimeName + }; + result = xrGetInstanceProperties(instance, &instanceProps); + if (XR_FAILED(result)) { + // not fatal probably + print_line("OpenXR: Failed to get XR instance properties [", get_error_string(result), "]"); + } else { + print_line("OpenXR: Running on OpenXR runtime: ", instanceProps.runtimeName, " ", OpenXRUtil::make_xr_version_string(instanceProps.runtimeVersion)); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_created(instance); + } + + return true; +} + +bool OpenXRAPI::get_system_info() { + // Retrieve basic OpenXR system info based on the form factor we desire + + // Retrieve the system for our form factor, fails if form factor is not available + XrSystemGetInfo system_get_info = { + XR_TYPE_SYSTEM_GET_INFO, // type; + nullptr, // next + form_factor // formFactor + }; + + XrResult result = xrGetSystem(instance, &system_get_info, &system_id); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get system for our form factor [", get_error_string(result), "]"); + return false; + } + + // obtain info about our system, writing this out completely to make CI on Linux happy.. + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_system_properties_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSystemProperties system_properties = { + XR_TYPE_SYSTEM_PROPERTIES, // type + next_pointer, // next + 0, // systemId, from here will be set by our get call + 0, // vendorId + "", // systemName + { + 0, // maxSwapchainImageHeight + 0, // maxSwapchainImageWidth + 0, // maxLayerCount + }, // graphicsProperties + { + false, // orientationTracking + false // positionTracking + } // trackingProperties + }; + + result = xrGetSystemProperties(instance, system_id, &system_properties); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get System properties [", get_error_string(result), "]"); + return false; + } + + // remember this state, we'll use it later + system_name = String(system_properties.systemName); + vendor_id = system_properties.vendorId; + graphics_properties = system_properties.graphicsProperties; + tracking_properties = system_properties.trackingProperties; + + return true; +} + +bool OpenXRAPI::load_supported_view_configuration_types() { + // This queries the supported configuration types, likely there will only be one choosing between Mono (phone AR) and Stereo (HMDs) + + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (supported_view_configuration_types != nullptr) { + // free previous results + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + XrResult result = xrEnumerateViewConfigurations(instance, system_id, 0, &num_view_configuration_types, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + supported_view_configuration_types = (XrViewConfigurationType *)memalloc(sizeof(XrViewConfigurationType) * num_view_configuration_types); + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + 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])); + } +#endif + + return true; +} + +bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const { + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + for (uint32_t i = 0; i < num_view_configuration_types; i++) { + if (supported_view_configuration_types[i] == p_configuration_type) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type) { + // This loads our view configuration for each view so for a stereo HMD, we'll get two entries (that are likely identical) + // The returned data supplies us with the recommended render target size + + if (!is_view_configuration_supported(p_configuration_type)) { + print_line("OpenXR: View configuration ", OpenXRUtil::get_view_configuration_name(view_configuration), " is not supported."); + return false; + } + + if (view_configuration_views != nullptr) { + // free previous results + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + XrResult result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, 0, &view_count, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + view_configuration_views = (XrViewConfigurationView *)memalloc(sizeof(XrViewConfigurationView) * view_count); + ERR_FAIL_NULL_V(view_configuration_views, false); + + for (uint32_t i = 0; i < view_count; i++) { + view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; + view_configuration_views[i].next = nullptr; + } + + 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); + } +#endif + + return true; +} + +void OpenXRAPI::destroy_instance() { + if (view_configuration_views != nullptr) { + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + if (supported_view_configuration_types != nullptr) { + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + if (instance != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_destroyed(); + } + + xrDestroyInstance(instance); + instance = XR_NULL_HANDLE; + } + enabled_extensions.clear(); +} + +bool OpenXRAPI::create_session() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + ERR_FAIL_COND_V(session != XR_NULL_HANDLE, false); + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_session_create_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSessionCreateInfo session_create_info = { + XR_TYPE_SESSION_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + system_id // systemId + }; + + XrResult result = xrCreateSession(instance, &session_create_info, &session); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create session [", get_error_string(result), "]"); + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_created(session); + } + + return true; +} + +bool OpenXRAPI::load_supported_reference_spaces() { + // loads the supported reference spaces for our OpenXR session + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + XrResult result = xrEnumerateReferenceSpaces(session, 0, &num_reference_spaces, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get reference space count [", get_error_string(result), "]"); + return false; + } + + supported_reference_spaces = (XrReferenceSpaceType *)memalloc(sizeof(XrReferenceSpaceType) * num_reference_spaces); + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + 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])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + for (uint32_t i = 0; i < num_reference_spaces; i++) { + if (supported_reference_spaces[i] == p_reference_space) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::setup_spaces() { + XrResult result; + + XrPosef identityPose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, 0.0, 0.0 } + }; + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // create play space + { + if (!is_reference_space_supported(reference_space)) { + print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo play_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + reference_space, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create play space [", get_error_string(result), "]"); + return false; + } + } + + // create view space + { + if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) { + print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo view_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create view space [", get_error_string(result), "]"); + return false; + } + } + + return true; +} + +bool OpenXRAPI::load_supported_swapchain_formats() { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_swapchain_formats != nullptr) { + // free previous results + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + XrResult result = xrEnumerateSwapchainFormats(session, 0, &num_swapchain_formats, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain format count [", get_error_string(result), "]"); + return false; + } + + supported_swapchain_formats = (int64_t *)memalloc(sizeof(int64_t) * num_swapchain_formats); + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + 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])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + for (uint32_t i = 0; i < num_swapchain_formats; i++) { + if (supported_swapchain_formats[i] == p_swapchain_format) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::create_main_swapchain() { + ERR_FAIL_NULL_V(graphics_extension, false); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + /* + TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting + those for the ones Godot normally creates. + This however means we can only use swapchains for our main XR view. + + It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here. + We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier. + + Also Godot only creates a swapchain for the main output. + OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system + to optimise text rendering and background rendering as OpenXR may choose to re-use the results for reprojection while we're + 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. + */ + + // 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_swapchain_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 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)); + } + + Size2 recommended_size = get_recommended_target_size(); + + 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; + } + + views = (XrView *)memalloc(sizeof(XrView) * view_count); + ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); + + projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); + ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + + 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.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; + }; + + return true; +}; + +void OpenXRAPI::destroy_session() { + if (running && session != XR_NULL_HANDLE) { + xrEndSession(session); + } + + if (graphics_extension) { + graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + } + + if (views != nullptr) { + memfree(views); + views = nullptr; + } + + if (projection_views != nullptr) { + memfree(projection_views); + projection_views = nullptr; + } + + if (swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(swapchain); + swapchain = XR_NULL_HANDLE; + } + + if (supported_swapchain_formats != nullptr) { + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + // destroy our spaces + if (play_space != XR_NULL_HANDLE) { + xrDestroySpace(play_space); + play_space = XR_NULL_HANDLE; + } + if (view_space != XR_NULL_HANDLE) { + xrDestroySpace(view_space); + view_space = XR_NULL_HANDLE; + } + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + if (session != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_destroyed(); + } + + xrDestroySession(session); + session = XR_NULL_HANDLE; + } +} + +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) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + XrResult result; + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSwapchainCreateInfo swapchain_create_info = { + XR_TYPE_SWAPCHAIN_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags + p_swapchain_format, // format + p_sample_count, // sampleCount + p_width, // width + p_height, // height + 1, // faceCount + p_array_size, // arraySize + 1 // mipCount + }; + + XrSwapchain new_swapchain; + result = xrCreateSwapchain(session, &swapchain_create_info, &new_swapchain); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain [", get_error_string(result), "]"); + return false; + } + + if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, r_swapchain_graphics_data)) { + xrDestroySwapchain(new_swapchain); + return false; + } + + r_swapchain = new_swapchain; + + return true; +} + +bool OpenXRAPI::on_state_idle() { +#ifdef DEBUG + print_line("On state idle"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_idle(); + } + + return true; +} + +bool OpenXRAPI::on_state_ready() { +#ifdef DEBUG + print_line("On state ready"); +#endif + + // begin session + XrSessionBeginInfo session_begin_info = { + XR_TYPE_SESSION_BEGIN_INFO, // type + nullptr, // next + view_configuration // primaryViewConfigurationType + }; + + XrResult result = xrBeginSession(session, &session_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to begin session [", get_error_string(result), "]"); + return false; + } + + // This is when we create our swapchain, this can be a "long" time after Godot finishes, we can deal with this for now + // but once we want to provide Viewports for additional layers where OpenXR requires us to create further swapchains, + // we'll be creating those viewport WAY before we reach this point. + // We may need to implement a wait in our init in main.cpp polling our events until the session is 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()) { + return false; + } + + // we're running + running = true; + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_ready(); + } + + if (xr_interface) { + xr_interface->on_state_ready(); + } + + // TODO Tell android + + return true; +} + +bool OpenXRAPI::on_state_synchronized() { +#ifdef DEBUG + print_line("On state synchronized"); +#endif + + // Just in case, see if we already have active trackers... + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i]); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_synchronized(); + } + + return true; +} + +bool OpenXRAPI::on_state_visible() { +#ifdef DEBUG + print_line("On state visible"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_visible(); + } + + if (xr_interface) { + xr_interface->on_state_visible(); + } + + return true; +} + +bool OpenXRAPI::on_state_focused() { +#ifdef DEBUG + print_line("On state focused"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_focused(); + } + + if (xr_interface) { + xr_interface->on_state_focused(); + } + + return true; +} + +bool OpenXRAPI::on_state_stopping() { +#ifdef DEBUG + print_line("On state stopping"); +#endif + + if (xr_interface) { + xr_interface->on_state_stopping(); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_stopping(); + } + + if (running) { + XrResult result = xrEndSession(session); + if (XR_FAILED(result)) { + // we only report this.. + print_line("OpenXR: Failed to end session [", get_error_string(result), "]"); + } + + running = false; + } + + // TODO further cleanup + + return true; +} + +bool OpenXRAPI::on_state_loss_pending() { +#ifdef DEBUG + print_line("On state loss pending"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_loss_pending(); + } + + // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting) + + return true; +} + +bool OpenXRAPI::on_state_exiting() { +#ifdef DEBUG + print_line("On state existing"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_exiting(); + } + + // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting) + + return true; +} + +bool OpenXRAPI::is_initialized() { + return (instance != XR_NULL_HANDLE); +} + +bool OpenXRAPI::is_running() { + if (instance == XR_NULL_HANDLE) { + return false; + } + if (session == XR_NULL_HANDLE) { + return false; + } + + return running; +} + +bool OpenXRAPI::initialize(const String &p_rendering_driver) { + ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created"); + + if (p_rendering_driver == "vulkan") { +#ifdef VULKAN_ENABLED + graphics_extension = memnew(OpenXRVulkanExtension(this)); + register_extension_wrapper(graphics_extension); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else if (p_rendering_driver == "opengl3") { +#ifdef OPENGL3_ENABLED + // graphics_extension = memnew(OpenXROpenGLExtension(this)); + // register_extension_wrapper(graphics_extension); + ERR_FAIL_V_MSG(false, "OpenXR: OpenGL is not supported at this time."); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); + } + + // initialize + if (!load_layer_properties()) { + destroy_instance(); + return false; + } + + if (!load_supported_extensions()) { + destroy_instance(); + return false; + } + + if (!create_instance()) { + destroy_instance(); + return false; + } + + if (!get_system_info()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_types()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_views(view_configuration)) { + destroy_instance(); + return false; + } + + return true; +} + +bool OpenXRAPI::initialize_session() { + if (!create_session()) { + destroy_session(); + return false; + } + + if (!load_supported_reference_spaces()) { + destroy_session(); + return false; + } + + if (!setup_spaces()) { + destroy_session(); + return false; + } + + if (!load_supported_swapchain_formats()) { + destroy_session(); + return false; + } + + return true; +} + +void OpenXRAPI::finish() { + destroy_session(); + + destroy_instance(); +} + +void OpenXRAPI::set_xr_interface(OpenXRInterface *p_xr_interface) { + xr_interface = p_xr_interface; +} + +void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { + registered_extension_wrappers.push_back(p_extension_wrapper); +} + +Size2 OpenXRAPI::get_recommended_target_size() { + ERR_FAIL_NULL_V(view_configuration_views, Size2()); + + Size2 target_size; + + target_size.width = view_configuration_views[0].recommendedImageRectWidth; + target_size.height = view_configuration_views[0].recommendedImageRectHeight; + + return target_size; +} + +XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + XrResult result; + + ERR_FAIL_COND_V(!running, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + // Get timing for the next frame, as that is the current frame we're processing + XrTime display_time = get_next_frame_time(); + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + result = xrLocateSpace(view_space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to locate view space in play space [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + if (head_pose_confidence != confidence) { + // prevent error spam + 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"); + } else { + print_line("OpenVR Head pose now tracking with high confidence"); +#endif + } + } + + return confidence; +} + +bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { + ERR_FAIL_COND_V(!running, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == nullptr || !view_pose_valid) { + return false; + } + + // Note, the timing of this is set right before rendering, which is what we need here. + r_transform = transform_from_pose(views[p_view].pose); + + return true; +} + +bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix) { + ERR_FAIL_COND_V(!running, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == nullptr || !view_pose_valid) { + return false; + } + + return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); +} + +bool OpenXRAPI::poll_events() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + XrEventDataBuffer runtimeEvent; + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + runtimeEvent.next = nullptr; + // runtimeEvent.varying = ... + + XrResult pollResult = xrPollEvent(instance, &runtimeEvent); + while (pollResult == XR_SUCCESS) { + bool handled = false; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + handled |= wrapper->on_event_polled(runtimeEvent); + } + switch (runtimeEvent.type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + XrEventDataEventsLost *event = (XrEventDataEventsLost *)&runtimeEvent; + + // We probably didn't poll fast enough, just output warning + WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!"); + } break; + case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { + // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent; + + // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask, + // this will allow us to prevent rendering the part of our view which is never displayed giving us + // a decent performance improvement. + + print_verbose("OpenXR EVENT: STUB: visibility mask changed"); + } break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent; + + // TODO We get this event if we're about to loose our OpenXR instance. + // We should queue exiting Godot at this point. + + print_verbose("OpenXR EVENT: instance loss pending at " + itos(event->lossTime)); + return false; + } break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent; + + session_state = event->state; + if (session_state >= XR_SESSION_STATE_MAX_ENUM) { + print_verbose("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)); + + switch (session_state) { + case XR_SESSION_STATE_IDLE: + on_state_idle(); + break; + case XR_SESSION_STATE_READY: + on_state_ready(); + break; + case XR_SESSION_STATE_SYNCHRONIZED: + on_state_synchronized(); + break; + case XR_SESSION_STATE_VISIBLE: + on_state_visible(); + break; + case XR_SESSION_STATE_FOCUSED: + on_state_focused(); + break; + case XR_SESSION_STATE_STOPPING: + on_state_stopping(); + break; + case XR_SESSION_STATE_LOSS_PENDING: + on_state_loss_pending(); + break; + case XR_SESSION_STATE_EXITING: + on_state_exiting(); + break; + default: + break; + } + } + } break; + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; + + print_verbose("OpenXR EVENT: reference space type " + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + if (event->poseValid && xr_interface) { + xr_interface->on_pose_recentered(); + } + } break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + print_verbose("OpenXR EVENT: interaction profile changed!"); + + XrEventDataInteractionProfileChanged *event = (XrEventDataInteractionProfileChanged *)&runtimeEvent; + + List<RID> trackers; + tracker_owner.get_owned_list(&trackers); + for (int i = 0; i < trackers.size(); i++) { + tracker_check_profile(trackers[i], event->session); + } + + } break; + default: + if (!handled) { + print_verbose("OpenXR Unhandled event type " + OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + } + break; + } + + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + pollResult = xrPollEvent(instance, &runtimeEvent); + } + + if (pollResult == XR_EVENT_UNAVAILABLE) { + // processed all events in the queue + return true; + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Failed to poll events!"); + } +} + +bool OpenXRAPI::process() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (!poll_events()) { + return false; + } + + if (!running) { + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_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... + + 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); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); + return false; + } + + XrSwapchainImageWaitInfo swapchain_image_wait_info = { + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type + nullptr, // next + 17000000 // timeout in nanoseconds + }; + + result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::release_image(XrSwapchain p_swapchain) { + XrSwapchainImageReleaseInfo swapchain_image_release_info = { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type + nullptr // next + }; + XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +void OpenXRAPI::pre_render() { + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + // Waitframe does 2 important things in our process: + // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit + // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible + // This must thus be called as close to when we start rendering as possible + XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr }; + XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); + if (XR_FAILED(result)) { + print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); + + // reset just in case + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + frame_state.shouldRender = false; + + return; + } + + if (frame_state.predictedDisplayPeriod > 500000000) { + // display period more then 0.5 seconds? must be wrong data + print_verbose("OpenXR resetting invalid display period " + rtos(frame_state.predictedDisplayPeriod)); + frame_state.predictedDisplayPeriod = 0; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_pre_render(); + } + + // Get our view info for the frame we're about to render, note from the OpenXR manual: + // "Repeatedly calling xrLocateViews with the same time may not necessarily return the same result. Instead the prediction gets increasingly accurate as the function is called closer to the given time for which a prediction is made" + + // We're calling this "relatively" early, the positioning we're obtaining here will be used to do our frustum culling, + // occlusion culling, etc. There is however a technique that we can investigate in the future where after our entire + // Vulkan command buffer is build, but right before vkSubmitQueue is called, we call xrLocateViews one more time and + // update the view and projection matrix once more with a slightly more accurate predication and then submit the + // command queues. + + // That is not possible yet but worth investigating in the future. + + XrViewLocateInfo view_locate_info = { + XR_TYPE_VIEW_LOCATE_INFO, // type + nullptr, // next + view_configuration, // viewConfigurationType + frame_state.predictedDisplayTime, // displayTime + play_space // space + }; + XrViewState view_state = { + XR_TYPE_VIEW_STATE, // type + nullptr, // next + 0 // viewStateFlags + }; + uint32_t view_count_output; + result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views); + if (XR_FAILED(result)) { + print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]"); + return; + } + + bool pose_valid = true; + for (uint64_t i = 0; i < view_count_output; i++) { + if ((view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0 || + (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) { + pose_valid = false; + } + } + if (view_pose_valid != pose_valid) { + view_pose_valid = pose_valid; +#ifdef DEBUG + if (!view_pose_valid) { + print_line("OpenXR View pose became invalid"); + } else { + print_line("OpenXR View pose became valid"); + } +#endif + } + + // let's start our frame.. + XrFrameBeginInfo frame_begin_info = { + XR_TYPE_FRAME_BEGIN_INFO, // type + nullptr // next + }; + result = xrBeginFrame(session, &frame_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to being frame [", get_error_string(result), "]"); + return; + } +} + +bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + if (!can_render()) { + return false; + } + + // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + + return true; +} + +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); + } +}; + +void OpenXRAPI::end_frame() { + XrResult result; + + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + if (frame_state.shouldRender && view_pose_valid && !image_acquired) { + print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + } + + // must have: + // - 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) { + // submit 0 layers when we shouldn't render + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + 0, // layerCount + nullptr // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } + + // neither eye is rendered + return; + } + + // release our swapchain image if we acquired it + if (image_acquired) { + image_acquired = false; // whether we succeed or not, consider this released. + + release_image(swapchain); + } + + for (uint32_t eye = 0; eye < view_count; eye++) { + projection_views[eye].fov = views[eye].fov; + projection_views[eye].pose = views[eye].pose; + } + + Vector<const XrCompositionLayerBaseHeader *> layers_list; + + // Add composition layers from providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + XrCompositionLayerBaseHeader *layer = provider->get_composition_layer(); + if (layer) { + layers_list.push_back(layer); + } + } + + 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 + play_space, // space + view_count, // viewCount + projection_views, // views + }; + layers_list.push_back((const XrCompositionLayerBaseHeader *)&projection_layer); + + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + static_cast<uint32_t>(layers_list.size()), // layerCount + layers_list.ptr() // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } +} + +OpenXRAPI::OpenXRAPI() { + // OpenXRAPI is only constructed if OpenXR is enabled. + singleton = this; + + if (Engine::get_singleton()->is_editor_hint()) { + // Enabled OpenXR in the editor? Adjust our settings for the editor + + } else { + // Load settings from project settings + int ff = GLOBAL_GET("xr/openxr/form_factor"); + switch (ff) { + case 0: { + form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + } break; + case 1: { + form_factor = XR_FORM_FACTOR_HANDHELD_DISPLAY; + } break; + default: + break; + } + + int vc = GLOBAL_GET("xr/openxr/view_configuration"); + switch (vc) { + case 0: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO; + } break; + case 1: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + } break; + /* we don't support quad and observer configurations (yet) + case 2: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO; + } break; + case 3: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT; + } break; + */ + default: + break; + } + + int rs = GLOBAL_GET("xr/openxr/reference_space"); + switch (rs) { + case 0: { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + } break; + case 1: { + reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } break; + default: + break; + } + } + + // 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 +} + +OpenXRAPI::~OpenXRAPI() { + // cleanup our composition layer providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + memdelete(provider); + } + 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; + } + + if (layer_properties != nullptr) { + memfree(layer_properties); + layer_properties = nullptr; + } + + singleton = nullptr; +} + +Transform3D OpenXRAPI::transform_from_pose(const XrPosef &p_pose) { + Quaternion q(p_pose.orientation.x, p_pose.orientation.y, p_pose.orientation.z, p_pose.orientation.w); + Basis basis(q); + Vector3 origin(p_pose.position.x, p_pose.position.y, p_pose.position.z); + + return Transform3D(basis, origin); +} + +template <typename T> +XRPose::TrackingConfidence _transform_from_location(const T &p_location, Transform3D &r_transform) { + Basis basis; + Vector3 origin; + XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + const auto &pose = p_location.pose; + + // Check orientation + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + Quaternion q(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w); + r_transform.basis = Basis(q); + + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) { + // Fully valid orientation, so either 3DOF or 6DOF tracking with high confidence so default to HIGH_TRACKING + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } else { + // Orientation is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } + } else { + r_transform.basis = Basis(); + } + + // Check location + if (p_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + r_transform.origin = Vector3(pose.position.x, pose.position.y, pose.position.z); + + if (!(p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT)) { + // Location is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } else if (confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Position tracking without orientation tracking? + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } + } else { + // No tracking or 3DOF I guess.. + r_transform.origin = Vector3(); + } + + return confidence; +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity) { + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + XrVector3f linear_velocity = p_velocity.linearVelocity; + r_linear_velocity = Vector3(linear_velocity.x, linear_velocity.y, linear_velocity.z); + } else { + r_linear_velocity = Vector3(); + } + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + XrVector3f angular_velocity = p_velocity.angularVelocity; + r_angular_velocity = Vector3(angular_velocity.x, angular_velocity.y, angular_velocity.z); + } else { + r_angular_velocity = Vector3(); + } +} + +RID OpenXRAPI::get_tracker_rid(XrPath p_path) { + List<RID> current; + tracker_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(current[i]); + if (tracker && tracker->toplevel_path == p_path) { + return current[i]; + } + } + + return RID(); +} + +RID OpenXRAPI::tracker_create(const String p_name) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + Tracker new_tracker; + new_tracker.name = p_name; + new_tracker.toplevel_path = XR_NULL_PATH; + new_tracker.active_profile_rid = RID(); + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_tracker.toplevel_path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return tracker_owner.make_rid(new_tracker); +} + +String OpenXRAPI::tracker_get_name(RID p_tracker) { + if (p_tracker.is_null()) { + return String("None"); + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, String()); + + return tracker->name; +} + +void OpenXRAPI::tracker_check_profile(RID p_tracker, XrSession p_session) { + if (p_session == XR_NULL_HANDLE) { + p_session = session; + } + + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); + + if (tracker->toplevel_path == XR_NULL_PATH) { + // no path, how was this even created? + return; + } + + XrInteractionProfileState profile_state = { + XR_TYPE_INTERACTION_PROFILE_STATE, // type + nullptr, // next + XR_NULL_PATH // interactionProfile + }; + + XrResult result = xrGetCurrentInteractionProfile(p_session, tracker->toplevel_path, &profile_state); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get interaction profile for", itos(tracker->toplevel_path), "[", get_error_string(result), "]"); + return; + } + + XrPath new_profile = profile_state.interactionProfile; + XrPath was_profile = get_interaction_profile_path(tracker->active_profile_rid); + if (was_profile != new_profile) { + tracker->active_profile_rid = get_interaction_profile_rid(new_profile); + + if (xr_interface) { + xr_interface->tracker_profile_changed(p_tracker, tracker->active_profile_rid); + } + } +} + +void OpenXRAPI::tracker_free(RID p_tracker) { + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL(tracker); + + // there is nothing to free here + + tracker_owner.free(p_tracker); +} + +RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + ActionSet action_set; + + action_set.name = p_name; + action_set.is_attached = false; + + // create our action set... + XrActionSetCreateInfo action_set_info = { + XR_TYPE_ACTION_SET_CREATE_INFO, // type + nullptr, // next + "", // actionSetName + "", // localizedActionSetName + uint32_t(p_priority) // priority + }; + + 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), "]"); + return RID(); + } + + return action_set_owner.make_rid(action_set); +} + +String OpenXRAPI::action_set_get_name(RID p_action_set) { + if (p_action_set.is_null()) { + return String("None"); + } + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, String()); + + return action_set->name; +} + +bool OpenXRAPI::action_set_attach(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, false); + + if (action_set->is_attached) { + // already attached + return true; + } + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // So according to the docs, once we attach our action set to our session it becomes read only.. + // https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html + XrSessionActionSetsAttachInfo attach_info = { + XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type + nullptr, // next + 1, // countActionSets, + &action_set->handle // actionSets + }; + + XrResult result = xrAttachSessionActionSets(session, &attach_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to attach action set! [", get_error_string(result), "]"); + return false; + } + + action_set->is_attached = true; + + /* For debugging: + print_verbose("Attached set " + action_set->name); + List<RID> action_rids; + action_owner.get_owned_list(&action_rids); + for (int i = 0; i < action_rids.size(); i++) { + Action * action = action_owner.get_or_null(action_rids[i]); + if (action && action->action_set_rid == p_action_set) { + print_verbose(" - Action " + action->name + ": " + OpenXRUtil::get_action_type_name(action->action_type)); + for (int j = 0; j < action->trackers.size(); j++) { + Tracker * tracker = tracker_owner.get_or_null(action->trackers[j].tracker_rid); + if (tracker) { + print_verbose(" - " + tracker->name); + } + } + } + } + */ + + return true; +} + +void OpenXRAPI::action_set_free(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL(action_set); + + if (action_set->handle != XR_NULL_HANDLE) { + xrDestroyActionSet(action_set->handle); + } + + action_set_owner.free(p_action_set); +} + +RID OpenXRAPI::get_action_rid(XrAction p_action) { + List<RID> current; + action_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + Action *action = action_owner.get_or_null(current[i]); + if (action && action->handle == p_action) { + return current[i]; + } + } + + return RID(); +} + +RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + Action action; + action.name = p_name; + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, RID()); + ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID()); + action.action_set_rid = p_action_set; + + switch (p_action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: + action.action_type = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_FLOAT: + action.action_type = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: + action.action_type = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_POSE: + action.action_type = XR_ACTION_TYPE_POSE_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_HAPTIC: + action.action_type = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + default: + ERR_FAIL_V(RID()); + break; + } + + Vector<XrPath> toplevel_paths; + for (int i = 0; i < p_trackers.size(); i++) { + Tracker *tracker = tracker_owner.get_or_null(p_trackers[i]); + if (tracker != nullptr && tracker->toplevel_path != XR_NULL_PATH) { + ActionTracker action_tracker = { + p_trackers[i], // tracker + XR_NULL_HANDLE, // space + false // was_location_valid + }; + action.trackers.push_back(action_tracker); + + toplevel_paths.push_back(tracker->toplevel_path); + } + } + + XrActionCreateInfo action_info = { + XR_TYPE_ACTION_CREATE_INFO, // type + nullptr, // next + "", // actionName + action.action_type, // actionType + uint32_t(toplevel_paths.size()), // countSubactionPaths + toplevel_paths.ptr(), // subactionPaths + "" // localizedActionName + }; + + 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), "]"); + return RID(); + } + + return action_owner.make_rid(action); +} + +String OpenXRAPI::action_get_name(RID p_action) { + if (p_action.is_null()) { + return String("None"); + } + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, String()); + + return action->name; +} + +void OpenXRAPI::action_free(RID p_action) { + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL(action); + + if (action->handle != XR_NULL_HANDLE) { + xrDestroyAction(action->handle); + } + + action_owner.free(p_action); +} + +RID OpenXRAPI::get_interaction_profile_rid(XrPath p_path) { + List<RID> current; + interaction_profile_owner.get_owned_list(¤t); + for (int i = 0; i < current.size(); i++) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(current[i]); + if (ip && ip->path == p_path) { + return current[i]; + } + } + + return RID(); +} + +XrPath OpenXRAPI::get_interaction_profile_path(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return XR_NULL_PATH; + } + + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, XR_NULL_PATH); + + return ip->path; +} + +RID OpenXRAPI::interaction_profile_create(const String p_name) { + InteractionProfile new_interaction_profile; + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_interaction_profile.path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + RID existing_ip = get_interaction_profile_rid(new_interaction_profile.path); + if (existing_ip.is_valid()) { + return existing_ip; + } + + new_interaction_profile.name = p_name; + return interaction_profile_owner.make_rid(new_interaction_profile); +} + +String OpenXRAPI::interaction_profile_get_name(RID p_interaction_profile) { + if (p_interaction_profile.is_null()) { + return String("None"); + } + + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, String()); + + return ip->name; +} + +void OpenXRAPI::interaction_profile_clear_bindings(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); + + ip->bindings.clear(); +} + +bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + + XrActionSuggestedBinding binding; + + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_COND_V(action == nullptr || action->handle == XR_NULL_HANDLE, false); + + binding.action = action->handle; + + XrResult result = xrStringToPath(instance, p_path.utf8().get_data(), &binding.binding); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_path, "! [", get_error_string(result), "]"); + return false; + } + + ip->bindings.push_back(binding); + + return true; +} + +bool OpenXRAPI::interaction_profile_suggest_bindings(RID p_interaction_profile) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL_V(ip, false); + + const XrInteractionProfileSuggestedBinding suggested_bindings = { + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type + nullptr, // next + ip->path, // interactionProfile + uint32_t(ip->bindings.size()), // countSuggestedBindings + ip->bindings.ptr() // suggestedBindings + }; + + XrResult result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); + if (result == XR_ERROR_PATH_UNSUPPORTED) { + // this is fine, not all runtimes support all devices. + print_verbose("OpenXR Interaction profile " + ip->name + " is not supported on this runtime"); + } else if (XR_FAILED(result)) { + print_line("OpenXR: failed to suggest bindings for ", ip->name, "! [", get_error_string(result), "]"); + // reporting is enough... + } + + /* For debugging: + print_verbose("Suggested bindings for " + ip->name); + for (int i = 0; i < ip->bindings.size(); i++) { + uint32_t strlen; + char path[XR_MAX_PATH_LENGTH]; + + String action_name = action_get_name(get_action_rid(ip->bindings[i].action)); + + XrResult result = xrPathToString(instance, ip->bindings[i].binding, XR_MAX_PATH_LENGTH, &strlen, path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to retrieve bindings for ", action_name, "! [", get_error_string(result), "]"); + } + print_verbose(" - " + action_name + " => " + String(path)); + } + */ + + return true; +} + +void OpenXRAPI::interaction_profile_free(RID p_interaction_profile) { + InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile); + ERR_FAIL_NULL(ip); + + ip->bindings.clear(); + + interaction_profile_owner.free(p_interaction_profile); +} + +bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (!running) { + return false; + } + + Vector<XrActiveActionSet> active_sets; + for (int i = 0; i < p_active_sets.size(); i++) { + ActionSet *action_set = action_set_owner.get_or_null(p_active_sets[i]); + if (action_set && action_set->handle != XR_NULL_HANDLE) { + XrActiveActionSet aset; + aset.actionSet = action_set->handle; + aset.subactionPath = XR_NULL_PATH; + active_sets.push_back(aset); + } + } + + ERR_FAIL_COND_V(active_sets.size() == 0, false); + + XrActionsSyncInfo sync_info = { + XR_TYPE_ACTIONS_SYNC_INFO, // type + nullptr, // next + uint32_t(active_sets.size()), // countActiveActionSets + active_sets.ptr() // activeActionSets + }; + + XrResult result = xrSyncActions(session, &sync_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to sync active action sets! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::get_action_bool(RID p_action, RID p_tracker) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_BOOLEAN_INPUT, false); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + tracker->toplevel_path // subactionPath + }; + + XrActionStateBoolean result_state; + result_state.type = XR_TYPE_ACTION_STATE_BOOLEAN, + result_state.next = nullptr; + XrResult result = xrGetActionStateBoolean(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action boolean! [", get_error_string(result), "]"); + return false; + } + + return result_state.isActive && result_state.currentState; +} + +float OpenXRAPI::get_action_float(RID p_action, RID p_tracker) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, 0.0); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, 0.0); + + if (!running) { + return 0.0; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_FLOAT_INPUT, 0.0); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + tracker->toplevel_path // subactionPath + }; + + XrActionStateFloat result_state; + result_state.type = XR_TYPE_ACTION_STATE_FLOAT, + result_state.next = nullptr; + XrResult result = xrGetActionStateFloat(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action float! [", get_error_string(result), "]"); + return 0.0; + } + + return result_state.isActive ? result_state.currentState : 0.0; +} + +Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_tracker) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2()); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, Vector2()); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, Vector2()); + + if (!running) { + return Vector2(); + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VECTOR2F_INPUT, Vector2()); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + tracker->toplevel_path // subactionPath + }; + + XrActionStateVector2f result_state; + result_state.type = XR_TYPE_ACTION_STATE_VECTOR2F, + result_state.next = nullptr; + XrResult result = xrGetActionStateVector2f(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action vector2! [", get_error_string(result), "]"); + return Vector2(); + } + + return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2(); +} + +XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + if (!running) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + // print_verbose("Checking " + action->name + " => " + tracker->name + " (" + itos(tracker->toplevel_path) + ")"); + + uint64_t index = 0xFFFFFFFF; + uint64_t size = uint64_t(action->trackers.size()); + for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) { + if (action->trackers[i].tracker_rid == p_tracker) { + index = i; + } + } + + if (index == 0xFFFFFFFF) { + // couldn't find it? + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XrTime display_time = get_next_frame_time(); + if (display_time == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + if (action->trackers[index].space == XR_NULL_HANDLE) { + // if this is a pose we need to define spaces + + XrActionSpaceCreateInfo action_space_info = { + XR_TYPE_ACTION_SPACE_CREATE_INFO, // type + nullptr, // next + action->handle, // action + tracker->toplevel_path, // subactionPath + { + { 0.0, 0.0, 0.0, 1.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // poseInActionSpace + }; + + XrSpace space; + XrResult result = xrCreateActionSpace(session, &action_space_info, &space); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't create action space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + action->trackers.ptrw()[index].space = space; + } + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + XrResult result = xrLocateSpace(action->trackers[index].space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to locate space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + return confidence; +} + +bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Tracker *tracker = tracker_owner.get_or_null(p_tracker); + ERR_FAIL_NULL_V(tracker, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VIBRATION_OUTPUT, false); + + XrHapticActionInfo action_info = { + XR_TYPE_HAPTIC_ACTION_INFO, // type + nullptr, // next + action->handle, // action + tracker->toplevel_path // subactionPath + }; + + XrHapticVibration vibration = { + XR_TYPE_HAPTIC_VIBRATION, // type + nullptr, // next + p_duration_ns, // duration + p_frequency, // frequency + p_amplitude, // amplitude + }; + + XrResult result = xrApplyHapticFeedback(session, &action_info, (const XrHapticBaseHeader *)&vibration); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to apply haptic feedback! [", get_error_string(result), "]"); + return false; + } + + return true; +} diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h new file mode 100644 index 0000000000..7baf581a05 --- /dev/null +++ b/modules/openxr/openxr_api.h @@ -0,0 +1,295 @@ +/*************************************************************************/ +/* openxr_api.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_DRIVER_H +#define OPENXR_DRIVER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/math/transform_3d.h" +#include "core/math/vector2.h" +#include "core/os/memory.h" +#include "core/string/ustring.h" +#include "core/templates/map.h" +#include "core/templates/rid_owner.h" +#include "core/templates/vector.h" +#include "servers/xr/xr_pose.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +#include "action_map/openxr_action.h" + +#include "extensions/openxr_composition_layer_provider.h" +#include "extensions/openxr_extension_wrapper.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. + +// forward declarations, we don't want to include these fully +class OpenXRVulkanExtension; +class OpenXRInterface; + +class OpenXRAPI { +private: + // our singleton + static OpenXRAPI *singleton; + + // linked XR interface + OpenXRInterface *xr_interface = nullptr; + + // layers + uint32_t num_layer_properties = 0; + XrApiLayerProperties *layer_properties = nullptr; + + // extensions + uint32_t num_supported_extensions = 0; + XrExtensionProperties *supported_extensions = nullptr; + Vector<OpenXRExtensionWrapper *> registered_extension_wrappers; + Vector<const char *> enabled_extensions; + + bool ext_hp_mixed_reality_available = false; + bool ext_samsung_odyssey_available = false; + bool ext_vive_cosmos_available = false; + bool ext_vive_focus3_available = false; + bool ext_huawei_controller_available = false; + + // composition layer providers + Vector<OpenXRCompositionLayerProvider *> composition_layer_providers; + + // view configuration + uint32_t num_view_configuration_types = 0; + XrViewConfigurationType *supported_view_configuration_types = nullptr; + + // reference spaces + uint32_t num_reference_spaces = 0; + XrReferenceSpaceType *supported_reference_spaces = nullptr; + + // swapchains (note these are platform dependent) + uint32_t num_swapchain_formats = 0; + int64_t *supported_swapchain_formats = nullptr; + + // configuration + 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; + + // state + XrInstance instance = XR_NULL_HANDLE; + XrSystemId system_id; + String system_name; + uint32_t vendor_id; + XrSystemTrackingProperties tracking_properties; + XrSession session = XR_NULL_HANDLE; + XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; + bool running = false; + XrFrameState frame_state = { XR_TYPE_FRAME_STATE, NULL, 0, 0, false }; + + 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; + + XrSpace play_space = XR_NULL_HANDLE; + XrSpace view_space = XR_NULL_HANDLE; + bool view_pose_valid = false; + XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + + bool load_layer_properties(); + bool load_supported_extensions(); + bool is_extension_supported(const char *p_extension) const; + + // instance + bool create_instance(); + bool get_system_info(); + bool load_supported_view_configuration_types(); + bool is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const; + bool load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type); + void destroy_instance(); + + // session + bool create_session(); + bool load_supported_reference_spaces(); + bool is_reference_space_supported(XrReferenceSpaceType p_reference_space); + bool setup_spaces(); + bool load_supported_swapchain_formats(); + bool is_swapchain_format_supported(int64_t p_swapchain_format); + bool create_main_swapchain(); + 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); + + // action map + struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. + String name; // Name for this tracker (i.e. "/user/hand/left") + XrPath toplevel_path; // OpenXR XrPath for this tracker + RID active_profile_rid; // RID of the active profile for this tracker + }; + RID_Owner<Tracker, true> tracker_owner; + RID get_tracker_rid(XrPath p_path); + + struct ActionSet { // Action sets define a set of actions that can be enabled together + String name; // Name for this action set (i.e. "godot_action_set") + bool is_attached; // If true our action set has been attached to the session and can no longer be modified + XrActionSet handle; // OpenXR handle for this action set + }; + RID_Owner<ActionSet, true> action_set_owner; + + struct ActionTracker { // Links and action to a tracker + RID tracker_rid; // RID of the tracker + XrSpace space; // Optional space for pose actions + bool was_location_valid; // If true the last position we obtained was valid + }; + + struct Action { // Actions define the inputs and outputs in OpenXR + RID action_set_rid; // RID of the action set this action belongs to + String name; // Name for this action (i.e. "aim_pose") + XrActionType action_type; // Type of action (bool, float, etc.) + Vector<ActionTracker> trackers; // The trackers this action can be used with + XrAction handle; // OpenXR handle for this action + }; + RID_Owner<Action, true> action_owner; + RID get_action_rid(XrAction p_action); + + struct InteractionProfile { // Interaction profiles define suggested bindings between the physical inputs on controller types and our actions + String name; // Name of the interaction profile (i.e. "/interaction_profiles/valve/index_controller") + XrPath path; // OpenXR path for this profile + Vector<XrActionSuggestedBinding> bindings; // OpenXR action bindings + }; + RID_Owner<InteractionProfile, true> interaction_profile_owner; + RID get_interaction_profile_rid(XrPath p_path); + XrPath get_interaction_profile_path(RID p_interaction_profile); + + // state changes + bool poll_events(); + bool on_state_idle(); + bool on_state_ready(); + bool on_state_synchronized(); + bool on_state_visible(); + bool on_state_focused(); + bool on_state_stopping(); + bool on_state_loss_pending(); + bool on_state_exiting(); + + // convencience + void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); + +protected: + friend class OpenXRVulkanExtension; + + XrInstance get_instance() const { return instance; }; + XrSystemId get_system_id() const { return system_id; }; + XrSession get_session() const { return session; }; + + // helper method to convert an XrPosef to a Transform3D + Transform3D transform_from_pose(const XrPosef &p_pose); + + // helper method to get a valid Transform3D from an openxr space location + XRPose::TrackingConfidence transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform); + 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: + static bool openxr_is_enabled(bool p_check_run_in_editor = true); + static OpenXRAPI *get_singleton(); + + String get_error_string(XrResult result); + String get_swapchain_format_name(int64_t p_swapchain_format) const; + + void set_xr_interface(OpenXRInterface *p_xr_interface); + void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + + bool is_initialized(); + bool is_running(); + bool initialize(const String &p_rendering_driver); + 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; }; + + Size2 get_recommended_target_size(); + XRPose::TrackingConfidence get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool get_view_transform(uint32_t p_view, Transform3D &r_transform); + bool get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix); + bool process(); + + void pre_render(); + bool pre_draw_viewport(RID p_render_target); + void post_draw_viewport(RID p_render_target); + void end_frame(); + + // action map + String get_default_action_map_resource_name(); + + RID tracker_create(const String p_name); + String tracker_get_name(RID p_tracker); + void tracker_check_profile(RID p_tracker, XrSession p_session = XR_NULL_HANDLE); + void tracker_free(RID p_tracker); + + RID action_set_create(const String p_name, const String p_localized_name, const int p_priority); + String action_set_get_name(RID p_action_set); + bool action_set_attach(RID p_action_set); + void action_set_free(RID p_action_set); + + RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers); + String action_get_name(RID p_action); + void action_free(RID p_action); + + RID interaction_profile_create(const String p_name); + String interaction_profile_get_name(RID p_interaction_profile); + void interaction_profile_clear_bindings(RID p_interaction_profile); + bool interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path); + bool interaction_profile_suggest_bindings(RID p_interaction_profile); + void interaction_profile_free(RID p_interaction_profile); + + bool sync_action_sets(const Vector<RID> p_active_sets); + bool get_action_bool(RID p_action, RID p_tracker); + float get_action_float(RID p_action, RID p_tracker); + Vector2 get_action_vector2(RID p_action, RID p_tracker); + XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + + OpenXRAPI(); + ~OpenXRAPI(); +}; + +#endif // !OPENXR_DRIVER_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp new file mode 100644 index 0000000000..41ce8c019e --- /dev/null +++ b/modules/openxr/openxr_interface.cpp @@ -0,0 +1,771 @@ +/*************************************************************************/ +/* openxr_interface.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_interface.h" + +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "servers/rendering/rendering_server_globals.h" + +void OpenXRInterface::_bind_methods() { + // lifecycle signals + ADD_SIGNAL(MethodInfo("session_begun")); + ADD_SIGNAL(MethodInfo("session_stopping")); + ADD_SIGNAL(MethodInfo("session_focussed")); + ADD_SIGNAL(MethodInfo("session_visible")); + ADD_SIGNAL(MethodInfo("pose_recentered")); +} + +StringName OpenXRInterface::get_name() const { + return StringName("OpenXR"); +}; + +uint32_t OpenXRInterface::get_capabilities() const { + return XRInterface::XR_VR + XRInterface::XR_STEREO; +}; + +PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { + // These are hardcoded in OpenXR, note that they will only be available if added to our action map + + PackedStringArray arr = { + "left_hand", // /user/hand/left is mapped to our defaults + "right_hand", // /user/hand/right is mapped to our defaults + "/user/treadmill" + }; + + return arr; +} + +XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { + return tracking_state; +} + +void OpenXRInterface::_load_action_map() { + ERR_FAIL_NULL(openxr_api); + + // 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. + // 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. + + // Within our openxr device we maintain a number of classes that wrap the relevant OpenXR objects for this. + // Within OpenXRInterface we have a few internal classes that keep track of what we've created. + // This allow us to process the relevant actions each frame. + + // just in case clean up + free_trackers(); + free_interaction_profiles(); + free_action_sets(); + + Ref<OpenXRActionMap> action_map; + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + action_map.instantiate(); + action_map->create_editor_action_sets(); +#endif + } else { + String default_tres_name = openxr_api->get_default_action_map_resource_name(); + + // Check if we can load our default + if (ResourceLoader::exists(default_tres_name)) { + action_map = ResourceLoader::load(default_tres_name); + } + + // Check if we need to create default action set + if (action_map.is_null()) { + action_map.instantiate(); + action_map->create_default_action_sets(); +#ifdef TOOLS_ENABLED + // Save our action sets so our user can + action_map->set_path(default_tres_name, true); + ResourceSaver::save(default_tres_name, action_map); +#endif + } + } + + // process our action map + if (action_map.is_valid()) { + Map<Ref<OpenXRAction>, Action *> xr_actions; + + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + // Create our action set + Ref<OpenXRActionSet> xr_action_set = action_sets[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; + } + + // Now create our actions for these + Array actions = xr_action_set->get_actions(); + for (int j = 0; j < actions.size(); j++) { + Ref<OpenXRAction> xr_action = actions[j]; + + PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); + Vector<Tracker *> trackers; + + for (int k = 0; k < toplevel_paths.size(); k++) { + Tracker *tracker = find_tracker(toplevel_paths[k], true); + if (tracker) { + trackers.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); + } + + // 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]; + + // 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; + } + + 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); + + // And record it in our array so we can clean it up later on + if (interaction_profiles.has(ip)) { + interaction_profiles.push_back(ip); + } + } + } +} + +OpenXRInterface::ActionSet *OpenXRInterface::create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + // find if it already exists + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->action_set_name == p_action_set_name) { + // already exists in this set + return nullptr; + } + } + + ActionSet *action_set = memnew(ActionSet); + action_set->action_set_name = p_action_set_name; + action_set->is_active = true; + action_set->action_set_rid = openxr_api->action_set_create(p_action_set_name, p_localized_name, p_priority); + action_sets.push_back(action_set); + + return action_set; +} + +void OpenXRInterface::free_action_sets() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < action_sets.size(); i++) { + ActionSet *action_set = action_sets[i]; + + free_actions(action_set); + + openxr_api->action_set_free(action_set->action_set_rid); + + memfree(action_set); + } + action_sets.clear(); +} + +OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + if (p_action_set->actions[i]->action_name == p_action_name) { + // already exists in this set + return nullptr; + } + } + + Vector<RID> tracker_rids; + for (int i = 0; i < p_trackers.size(); i++) { + tracker_rids.push_back(p_trackers[i]->tracker_rid); + } + + Action *action = memnew(Action); + if (p_action_type == OpenXRAction::OPENXR_ACTION_POSE) { + // We can't have dual action names in OpenXR hence we added _pose, + // but default, aim and grip and default pose action names in Godot so rename them on the tracker. + // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side + if (p_action_name == "default_pose") { + action->action_name = "default"; + } else if (p_action_name == "aim_pose") { + action->action_name = "aim"; + } else if (p_action_name == "grip_pose") { + action->action_name = "grip"; + } else { + action->action_name = p_action_name; + } + } else { + action->action_name = p_action_name; + } + + action->action_type = p_action_type; + action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, tracker_rids); + p_action_set->actions.push_back(action); + + return action; +} + +OpenXRInterface::Action *OpenXRInterface::find_action(const String &p_action_name) { + // We just find the first action by this name + + for (int i = 0; i < action_sets.size(); i++) { + for (int j = 0; j < action_sets[i]->actions.size(); j++) { + if (action_sets[i]->actions[j]->action_name == p_action_name) { + return action_sets[i]->actions[j]; + } + } + } + + // not found + return nullptr; +} + +void OpenXRInterface::free_actions(ActionSet *p_action_set) { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + Action *action = p_action_set->actions[i]; + + openxr_api->action_free(action->action_rid); + + memdelete(action); + } + p_action_set->actions.clear(); +} + +OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_name, bool p_create) { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, nullptr); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size(); i++) { + tracker = trackers[i]; + if (tracker->tracker_name == p_tracker_name) { + return tracker; + } + } + + if (!p_create) { + return nullptr; + } + + // Create our RID + RID tracker_rid = openxr_api->tracker_create(p_tracker_name); + ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); + + // create our positional tracker + Ref<XRPositionalTracker> positional_tracker; + positional_tracker.instantiate(); + + // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. + if (p_tracker_name == "/user/hand/left") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("left_hand"); + positional_tracker->set_tracker_desc("Left hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); + } else if (p_tracker_name == "/user/hand/right") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("right_hand"); + positional_tracker->set_tracker_desc("Right hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); + } else { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name(p_tracker_name); + positional_tracker->set_tracker_desc(p_tracker_name); + } + positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + xr_server->add_tracker(positional_tracker); + + // create a new entry + tracker = memnew(Tracker); + tracker->tracker_name = p_tracker_name; + tracker->tracker_rid = tracker_rid; + tracker->positional_tracker = positional_tracker; + tracker->interaction_profile = RID(); + trackers.push_back(tracker); + + return tracker; +} + +void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_profile) { + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size() && tracker == nullptr; i++) { + if (trackers[i]->tracker_rid == p_tracker) { + tracker = trackers[i]; + } + } + ERR_FAIL_NULL(tracker); + + tracker->interaction_profile = p_interaction_profile; + + if (p_interaction_profile.is_null()) { + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); + tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + } else { + String name = openxr_api->interaction_profile_get_name(p_interaction_profile); + print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); + tracker->positional_tracker->set_tracker_profile(name); + } +} + +void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { + 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()); + + // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction + // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these + // and rebind them or even offer bindings to controllers that are not known to us. + + // We don't really have a consistent way to detect whether a controller is active however as long as it is + // unbound it seems to be unavailable, so far unknown controller seem to mimic one of the profiles we've + // supplied. + if (p_tracker->interaction_profile.is_null()) { + return; + } + + // We check all actions that are related to our tracker. + for (int i = 0; i < p_tracker->actions.size(); i++) { + Action *action = p_tracker->actions[i]; + switch (action->action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: { + bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); + } break; + case OpenXRAction::OPENXR_ACTION_FLOAT: { + real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: { + Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_POSE: { + Transform3D transform; + Vector3 linear, angular; + + XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); + + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); + } else { + p_tracker->positional_tracker->invalidate_pose(action->action_name); + } + } break; + default: { + // not yet supported + } break; + } + } +} + +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); + ERR_FAIL_NULL(tracker); + + // TODO OpenXR does not support delay, so we may need to add support for that somehow... + + XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds + + openxr_api->trigger_haptic_pulse(action->action_rid, tracker->tracker_rid, p_frequency, p_amplitude, duration); +} + +void OpenXRInterface::free_trackers() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < trackers.size(); i++) { + Tracker *tracker = trackers[i]; + + openxr_api->tracker_free(tracker->tracker_rid); + xr_server->remove_tracker(tracker->positional_tracker); + tracker->positional_tracker.unref(); + + memdelete(tracker); + } + trackers.clear(); +} + +void OpenXRInterface::free_interaction_profiles() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < interaction_profiles.size(); i++) { + openxr_api->interaction_profile_free(interaction_profiles[i]); + } + interaction_profiles.clear(); +} + +bool OpenXRInterface::initialize_on_startup() const { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + return true; + } +} + +bool OpenXRInterface::is_initialized() const { + return initialized; +}; + +bool OpenXRInterface::initialize() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, false); + + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else if (initialized) { + return true; + } + + // load up our action sets before setting up our session, note that our profiles are suggestions, OpenXR takes ownership of (re)binding + _load_action_map(); + + if (!openxr_api->initialize_session()) { + return false; + } + + // we must create a tracker for our head + head.instantiate(); + head->set_tracker_type(XRServer::TRACKER_HEAD); + head->set_tracker_name("head"); + head->set_tracker_desc("Players head"); + xr_server->add_tracker(head); + + // attach action sets + for (int i = 0; i < action_sets.size(); i++) { + openxr_api->action_set_attach(action_sets[i]->action_set_rid); + } + + // make this our primary interface + xr_server->set_primary_interface(this); + + initialized = true; + + return initialized; +} + +void OpenXRInterface::uninitialize() { + // Our OpenXR driver will clean itself up properly when Godot exits, so we just do some basic stuff here + + // end the session if we need to? + + // cleanup stuff + free_trackers(); + free_interaction_profiles(); + free_action_sets(); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (head.is_valid()) { + xr_server->remove_tracker(head); + head.unref(); + } + } + + initialized = false; +} + +bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { + return XRInterface::XR_PLAY_AREA_UNKNOWN; +} + +bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +Size2 OpenXRInterface::get_render_target_size() { + if (openxr_api == nullptr) { + return Size2(); + } else { + return openxr_api->get_recommended_target_size(); + } +} + +uint32_t OpenXRInterface::get_view_count() { + // TODO set this based on our configuration + return 2; +} + +void OpenXRInterface::_set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye) { + p_transform = Transform3D(); + + // if we're not tracking, don't put our head on the floor... + p_transform.origin.y = 1.5 * p_world_scale; + + // overkill but.. + if (p_eye == 1) { + p_transform.origin.x = 0.03 * p_world_scale; + } else if (p_eye == 2) { + p_transform.origin.x = -0.03 * p_world_scale; + } +} + +Transform3D OpenXRInterface::get_camera_transform() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + Transform3D hmd_transform; + double world_scale = xr_server->get_world_scale(); + + // head_transform should be updated in process + + hmd_transform.basis = head_transform.basis; + hmd_transform.origin = head_transform.origin * world_scale; + + return hmd_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()); + + Transform3D t; + if (openxr_api && openxr_api->get_view_transform(p_view, t)) { + // update our cached value if we have a valid transform + transform_for_view[p_view] = t; + } else { + // reuse cached value + t = transform_for_view[p_view]; + } + + // Apply our world scale + double world_scale = xr_server->get_world_scale(); + t.origin *= world_scale; + + return p_cam_transform * xr_server->get_reference_frame() * t; +} + +CameraMatrix OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { + CameraMatrix cm; + + if (openxr_api) { + if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { + return cm; + } + } + + // Failed to get from our OpenXR device? Default to some sort of sensible camera matrix.. + cm.set_for_hmd(p_view + 1, 1.0, 6.0, 14.5, 4.0, 1.5, p_z_near, p_z_far); + + return cm; +} + +void OpenXRInterface::process() { + if (openxr_api) { + // do our normal process + if (openxr_api->process()) { + Transform3D t; + Vector3 linear_velocity; + Vector3 angular_velocity; + XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Only update our transform if we have one to update it with + // note that poses are stored without world scale and reference frame applied! + head_transform = t; + head_linear_velocity = linear_velocity; + head_angular_velocity = angular_velocity; + } + } + + // handle our action sets.... + Vector<RID> active_sets; + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->is_active) { + active_sets.push_back(action_sets[i]->action_set_rid); + } + } + + if (openxr_api->sync_action_sets(active_sets)) { + for (int i = 0; i < trackers.size(); i++) { + handle_tracker(trackers[i]); + } + } + } + + if (head.is_valid()) { + // TODO figure out how to get our velocities + + head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity); + + // TODO set confidence on pose once we support tracking this.. + } +} + +void OpenXRInterface::pre_render() { + if (openxr_api) { + openxr_api->pre_render(); + } +} + +bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { + if (openxr_api) { + return openxr_api->pre_draw_viewport(p_render_target); + } else { + // don't render + return false; + } +} + +Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { + Vector<BlitToScreen> blit_to_screen; + + // If separate HMD we should output one eye to screen + if (p_screen_rect != Rect2()) { + BlitToScreen blit; + + blit.render_target = p_render_target; + blit.multi_view.use_layer = true; + blit.multi_view.layer = 0; + blit.lens_distortion.apply = false; + + Size2 render_size = get_render_target_size(); + Rect2 dst_rect = p_screen_rect; + float new_height = dst_rect.size.x * (render_size.y / render_size.x); + if (new_height > dst_rect.size.y) { + dst_rect.position.y = (0.5 * dst_rect.size.y) - (0.5 * new_height); + dst_rect.size.y = new_height; + } else { + float new_width = dst_rect.size.y * (render_size.x / render_size.y); + + dst_rect.position.x = (0.5 * dst_rect.size.x) - (0.5 * new_width); + dst_rect.size.x = new_width; + } + + blit.dst_rect = dst_rect; + blit_to_screen.push_back(blit); + } + + if (openxr_api) { + openxr_api->post_draw_viewport(p_render_target); + } + + return blit_to_screen; +} + +void OpenXRInterface::end_frame() { + if (openxr_api) { + openxr_api->end_frame(); + } +} + +void OpenXRInterface::on_state_ready() { + emit_signal(SNAME("session_begun")); +} + +void OpenXRInterface::on_state_visible() { + emit_signal(SNAME("session_visible")); +} + +void OpenXRInterface::on_state_focused() { + emit_signal(SNAME("session_focussed")); +} + +void OpenXRInterface::on_state_stopping() { + emit_signal(SNAME("session_stopping")); +} + +void OpenXRInterface::on_pose_recentered() { + emit_signal(SNAME("pose_recentered")); +} + +OpenXRInterface::OpenXRInterface() { + openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api) { + openxr_api->set_xr_interface(this); + } + + // while we don't have head tracking, don't put the headset on the floor... + _set_default_pos(head_transform, 1.0, 0); + _set_default_pos(transform_for_view[0], 1.0, 1); + _set_default_pos(transform_for_view[1], 1.0, 2); +} + +OpenXRInterface::~OpenXRInterface() { + if (is_initialized()) { + uninitialize(); + } + + if (openxr_api) { + openxr_api->set_xr_interface(nullptr); + openxr_api = nullptr; + } +} diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h new file mode 100644 index 0000000000..a223acfed0 --- /dev/null +++ b/modules/openxr/openxr_interface.h @@ -0,0 +1,143 @@ +/*************************************************************************/ +/* openxr_interface.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_INTERFACE_H +#define OPENXR_INTERFACE_H + +#include "servers/xr/xr_interface.h" +#include "servers/xr/xr_positional_tracker.h" + +#include "action_map/openxr_action_map.h" +#include "openxr_api.h" + +// declare some default strings +#define INTERACTION_PROFILE_NONE "/interaction_profiles/none" + +class OpenXRInterface : public XRInterface { + GDCLASS(OpenXRInterface, XRInterface); + +private: + OpenXRAPI *openxr_api = nullptr; + bool initialized = false; + XRInterface::TrackingStatus tracking_state; + + // At a minimum we need a tracker for our head + Ref<XRPositionalTracker> head; + Transform3D head_transform; + Vector3 head_linear_velocity; + Vector3 head_angular_velocity; + Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet + + void _load_action_map(); + + struct Action { // An action we've registered with OpenXR + String action_name; // Name of our action as presented to Godot (can be altered from the action map) + OpenXRAction::ActionType action_type; // The action type of this action + RID action_rid; // RID of the action registered with our OpenXR API + }; + struct ActionSet { // An action set we've registered with OpenXR + String action_set_name; // Name of our action set + bool is_active; // If true this action set is active and we will sync it + Vector<Action *> actions; // List of actions in this action set + RID action_set_rid; // RID of the action registered with our OpenXR API + }; + struct Tracker { // A tracker we've registered with OpenXR + String tracker_name; // Name of our tracker (can be altered from the action map) + Vector<Action *> actions; // Actions related to this tracker + Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state + RID tracker_rid; // RID of the tracker registered with our OpenXR API + RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null) + }; + + Vector<ActionSet *> action_sets; + Vector<RID> interaction_profiles; + Vector<Tracker *> trackers; + + ActionSet *create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority); + void free_action_sets(); + + Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<Tracker *> p_trackers); + Action *find_action(const String &p_action_name); + 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(); + + void free_interaction_profiles(); + + void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); + +protected: + static void _bind_methods(); + +public: + virtual StringName get_name() const override; + virtual uint32_t get_capabilities() const override; + + virtual PackedStringArray get_suggested_tracker_names() const override; + virtual TrackingStatus get_tracking_status() const override; + + bool initialize_on_startup() const; + virtual bool is_initialized() const override; + virtual bool initialize() override; + virtual void uninitialize() override; + + virtual void 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 = 0) override; + + virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + virtual XRInterface::PlayAreaMode get_play_area_mode() const override; + virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + + 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 CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) 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; + + void on_state_ready(); + void on_state_visible(); + void on_state_focused(); + void on_state_stopping(); + void on_pose_recentered(); + void tracker_profile_changed(RID p_tracker, RID p_interaction_profile); + + OpenXRInterface(); + ~OpenXRInterface(); +}; + +#endif // !OPENXR_INTERFACE_H diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp new file mode 100644 index 0000000000..230b10c5f1 --- /dev/null +++ b/modules/openxr/openxr_util.cpp @@ -0,0 +1,305 @@ +/*************************************************************************/ +/* openxr_util.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_util.h" + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +// TODO see if we can generate this code further using the xml file with meta data supplied by OpenXR + +String OpenXRUtil::get_view_configuration_name(XrViewConfigurationType p_view_configuration) { + switch (p_view_configuration) { + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_MAX_ENUM) + default: { + return String("View Configuration ") + String::num_int64(int64_t(p_view_configuration)); + } break; + } +} + +String OpenXRUtil::get_reference_space_name(XrReferenceSpaceType p_reference_space) { + switch (p_reference_space) { + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_LOCAL) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_STAGE) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_MAX_ENUM) + default: { + return String("Reference space ") + String::num_int64(int64_t(p_reference_space)); + } break; + } +} + +String OpenXRUtil::get_structure_type_name(XrStructureType p_structure_type) { + switch (p_structure_type) { + ENUM_TO_STRING_CASE(XR_TYPE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_TYPE_API_LAYER_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_EXTENSION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_END_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_VIBRATION) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_BUFFER) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_BOOLEAN) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_FLOAT) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_VECTOR2F) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_POSE) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SET_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_QUAD) + ENUM_TO_STRING_CASE(XR_TYPE_REFERENCE_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_LOCATION) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_VELOCITY) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_EVENTS_LOST) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_ACTION_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTIONS_SYNC_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CUBE_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_SWAPCHAIN_FORMAT_LIST_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_LABEL_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XCB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WAYLAND_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_EYE_GAZE_SAMPLE_TIME_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_VISIBILITY_MASK_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_EGL_MNDX) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_LOCATIONS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_VELOCITIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_MESH_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_UPDATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_POSE_TYPE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SWAPCHAIN_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC) + ENUM_TO_STRING_CASE(XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESHES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIVE_TRACKER_PATHS_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_MESH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_SCALE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_AIM_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_STYLE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_BINDING_MODIFICATIONS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_STRUCTURE_TYPE_MAX_ENUM) + default: { + return String("Structure type ") + String::num_int64(int64_t(p_structure_type)); + } break; + } +} + +String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) { + switch (p_session_state) { + ENUM_TO_STRING_CASE(XR_SESSION_STATE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_IDLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_READY) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_SYNCHRONIZED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_VISIBLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_FOCUSED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_STOPPING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_EXITING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_MAX_ENUM) + default: { + return String("Session state ") + String::num_int64(int64_t(p_session_state)); + } break; + } +} + +String OpenXRUtil::get_action_type_name(XrActionType p_action_type) { + switch (p_action_type) { + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_BOOLEAN_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_FLOAT_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VECTOR2F_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_POSE_INPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VIBRATION_OUTPUT) + ENUM_TO_STRING_CASE(XR_ACTION_TYPE_MAX_ENUM) + default: { + return String("Action type ") + String::num_int64(int64_t(p_action_type)); + } break; + } +} + +String OpenXRUtil::make_xr_version_string(XrVersion p_version) { + String version; + + version += String::num_int64(XR_VERSION_MAJOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_MINOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_PATCH(p_version)); + + return version; +} diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h new file mode 100644 index 0000000000..4371b74d2f --- /dev/null +++ b/modules/openxr/openxr_util.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* openxr_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 OPENXR_UTIL_H +#define OPENXR_UTIL_H + +#include "core/string/ustring.h" +#include <openxr/openxr.h> + +class OpenXRUtil { +public: + static String get_view_configuration_name(XrViewConfigurationType p_view_configuration); + static String get_reference_space_name(XrReferenceSpaceType p_reference_space); + static String get_structure_type_name(XrStructureType p_structure_type); + static String get_session_state_name(XrSessionState p_session_state); + static String get_action_type_name(XrActionType p_action_type); + static String make_xr_version_string(XrVersion p_version); +}; + +#endif // !OPENXR_UTIL_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp new file mode 100644 index 0000000000..0b48be5f2a --- /dev/null +++ b/modules/openxr/register_types.cpp @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* register_types.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 "register_types.h" +#include "main/main.h" + +#include "openxr_interface.h" + +#include "action_map/openxr_action.h" +#include "action_map/openxr_action_map.h" +#include "action_map/openxr_action_set.h" +#include "action_map/openxr_interaction_profile.h" + +#ifdef TOOLS_ENABLED + +#include "editor/editor_node.h" +#include "editor/openxr_editor_plugin.h" + +static void _editor_init() { + if (OpenXRAPI::openxr_is_enabled(false)) { + // Only add our OpenXR action map editor if OpenXR is enabled for our project + + OpenXREditorPlugin *openxr_plugin = memnew(OpenXREditorPlugin()); + EditorNode::get_singleton()->add_editor_plugin(openxr_plugin); + } +} + +#endif + +OpenXRAPI *openxr_api = nullptr; +Ref<OpenXRInterface> openxr_interface; + +void preregister_openxr_types() { + // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. + + if (OpenXRAPI::openxr_is_enabled()) { + openxr_api = memnew(OpenXRAPI); + ERR_FAIL_NULL(openxr_api); + + if (!openxr_api->initialize(Main::get_rendering_driver_name())) { + memdelete(openxr_api); + openxr_api = nullptr; + return; + } + } +} + +void register_openxr_types() { + GDREGISTER_CLASS(OpenXRInterface); + + GDREGISTER_CLASS(OpenXRAction); + GDREGISTER_CLASS(OpenXRActionSet); + GDREGISTER_CLASS(OpenXRActionMap); + GDREGISTER_CLASS(OpenXRIPBinding); + GDREGISTER_CLASS(OpenXRInteractionProfile); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + openxr_interface.instantiate(); + xr_server->add_interface(openxr_interface); + + if (openxr_interface->initialize_on_startup()) { + openxr_interface->initialize(); + } + } + +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(_editor_init); +#endif +} + +void unregister_openxr_types() { + if (openxr_interface.is_valid()) { + // uninitialize just in case + if (openxr_interface->is_initialized()) { + openxr_interface->uninitialize(); + } + + // unregister our interface from the XR server + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (xr_server->get_primary_interface() == openxr_interface) { + xr_server->set_primary_interface(Ref<XRInterface>()); + } + xr_server->remove_interface(openxr_interface); + } + + // and release + openxr_interface.unref(); + } + + if (openxr_api) { + openxr_api->finish(); + memdelete(openxr_api); + openxr_api = nullptr; + } +} diff --git a/modules/openxr/register_types.h b/modules/openxr/register_types.h new file mode 100644 index 0000000000..fb42770750 --- /dev/null +++ b/modules/openxr/register_types.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* register_types.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_REGISTER_TYPES_H +#define OPENXR_REGISTER_TYPES_H + +#define MODULE_OPENXR_HAS_PREREGISTER + +void preregister_openxr_types(); +void register_openxr_types(); +void unregister_openxr_types(); + +#endif // OPENXR_REGISTER_TYPES_H |