diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index c64b6de78ca0..3fd3daafb8c6 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -108,6 +108,7 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.c env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_htc_passthrough_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") diff --git a/modules/openxr/extensions/openxr_htc_passthrough_extension.cpp b/modules/openxr/extensions/openxr_htc_passthrough_extension.cpp new file mode 100644 index 000000000000..83d343232eb6 --- /dev/null +++ b/modules/openxr/extensions/openxr_htc_passthrough_extension.cpp @@ -0,0 +1,193 @@ +/**************************************************************************/ +/* openxr_htc_passthrough_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_htc_passthrough_extension.h" + +#include "core/os/os.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" + +using namespace godot; + +OpenXRHTCPassthroughExtension *OpenXRHTCPassthroughExtension::singleton = nullptr; + +OpenXRHTCPassthroughExtension *OpenXRHTCPassthroughExtension::get_singleton() { + return singleton; +} + +OpenXRHTCPassthroughExtension::OpenXRHTCPassthroughExtension() { + singleton = this; +} + +OpenXRHTCPassthroughExtension::~OpenXRHTCPassthroughExtension() { + cleanup(); +} + +HashMap<String, bool *> OpenXRHTCPassthroughExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_HTC_PASSTHROUGH_EXTENSION_NAME] = &htc_passthrough_ext; + + return request_extensions; +} + +void OpenXRHTCPassthroughExtension::cleanup() { + htc_passthrough_ext = false; +} + +Viewport *OpenXRHTCPassthroughExtension::get_main_viewport() { + MainLoop *main_loop = OS::get_singleton()->get_main_loop(); + if (!main_loop) { + print_error("Unable to retrieve main loop"); + return nullptr; + } + + SceneTree *scene_tree = Object::cast_to<SceneTree>(main_loop); + if (!scene_tree) { + print_error("Unable to retrieve scene tree"); + return nullptr; + } + + Viewport *viewport = scene_tree->get_root()->get_viewport(); + return viewport; +} + +void OpenXRHTCPassthroughExtension::on_instance_created(const XrInstance instance) { + if (htc_passthrough_ext) { + bool result = initialize_htc_passthrough_extension(instance); + if (!result) { + print_error("Failed to initialize htc_passthrough extension"); + htc_passthrough_ext = false; + } + } + + if (htc_passthrough_ext) { + OpenXRAPI::get_singleton()->register_composition_layer_provider(this); + } +} + +bool OpenXRHTCPassthroughExtension::is_passthrough_enabled() { + return htc_passthrough_ext && passthrough_handle != XR_NULL_HANDLE; +} + +bool OpenXRHTCPassthroughExtension::start_passthrough() { + if (passthrough_handle == XR_NULL_HANDLE) { + return false; + } + + if (is_passthrough_enabled()) { + return true; + } + + const XrPassthroughCreateInfoHTC create_info = { + XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC, + nullptr, + XR_PASSTHROUGH_FORM_PLANAR_HTC + }; + + XrResult result = xrCreatePassthroughHTC(OpenXRAPI::get_singleton()->get_session(), + &create_info, + &passthrough_handle); + + if (result != XR_SUCCESS) { + stop_passthrough(); + return false; + } + + // Check if the the viewport has transparent background + Viewport *viewport = get_main_viewport(); + if (viewport && !viewport->has_transparent_background()) { + print_error("Main viewport doesn't have transparent background! Passthrough may not properly render."); + } + + // HACK: to behave similar to Meta Passthrough variant + set_alpha(1.0f); + + return true; +} + +void OpenXRHTCPassthroughExtension::on_session_created(const XrSession session) { + if (session && htc_passthrough_ext) { + start_passthrough(); + } +} + +XrCompositionLayerBaseHeader *OpenXRHTCPassthroughExtension::get_composition_layer() { + if (is_passthrough_enabled()) { + composition_passthrough_layer.passthrough = passthrough_handle; + + return (XrCompositionLayerBaseHeader *)&composition_passthrough_layer; + } else { + return nullptr; + } +} + +void OpenXRHTCPassthroughExtension::stop_passthrough() { + if (!htc_passthrough_ext) { + return; + } + + if (passthrough_handle != XR_NULL_HANDLE) { + XrResult result = xrDestroyPassthroughHTC(passthrough_handle); + if (OpenXRAPI::get_singleton()->xr_result(result, "Unable to destroy passthrough feature")) { + passthrough_handle = XR_NULL_HANDLE; + } + } +} + +void OpenXRHTCPassthroughExtension::on_session_destroyed() { + if (htc_passthrough_ext) { + stop_passthrough(); + } +} + +void OpenXRHTCPassthroughExtension::on_instance_destroyed() { + if (htc_passthrough_ext) { + OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this); + } + cleanup(); +} + +void OpenXRHTCPassthroughExtension::set_alpha(float alpha) { + composition_passthrough_layer.color.alpha = std::clamp(alpha, 0.0f, 1.0f); +} + +float OpenXRHTCPassthroughExtension::get_alpha() const { + return composition_passthrough_layer.color.alpha; +} + +bool OpenXRHTCPassthroughExtension::initialize_htc_passthrough_extension(const XrInstance p_instance) { + ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false); + + EXT_INIT_XR_FUNC_V(xrCreatePassthroughHTC); + EXT_INIT_XR_FUNC_V(xrDestroyPassthroughHTC); + + return true; +} diff --git a/modules/openxr/extensions/openxr_htc_passthrough_extension.h b/modules/openxr/extensions/openxr_htc_passthrough_extension.h new file mode 100644 index 000000000000..9c100592c812 --- /dev/null +++ b/modules/openxr/extensions/openxr_htc_passthrough_extension.h @@ -0,0 +1,116 @@ +/**************************************************************************/ +/* openxr_htc_passthrough_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef OPENXR_HTC_PASSTHROUGH_EXTENSION_H +#define OPENXR_HTC_PASSTHROUGH_EXTENSION_H + +#include "../openxr_api.h" +#include "../util.h" +#include "openxr_composition_layer_provider.h" +#include "openxr_extension_wrapper.h" + +#include <map> + +class Viewport; + +// Wrapper for the HTC XR passthrough extensions (extension #318). +class OpenXRHTCPassthroughExtension : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { +public: + OpenXRHTCPassthroughExtension(); + ~OpenXRHTCPassthroughExtension(); + + virtual HashMap<String, bool *> get_requested_extensions() override; + + void on_instance_created(const XrInstance instance) override; + + void on_session_created(const XrSession session) override; + + void on_session_destroyed() override; + + void on_instance_destroyed() override; + + XrCompositionLayerBaseHeader *get_composition_layer() override; + + bool is_passthrough_supported() { + return htc_passthrough_ext; + } + + bool is_passthrough_enabled(); + + bool start_passthrough(); + + void stop_passthrough(); + + // set alpha of passthrough + void set_alpha(float alpha); + + // get alpha of passthrough + float get_alpha() const; + + static OpenXRHTCPassthroughExtension *get_singleton(); + +private: + // Create a passthrough feature + EXT_PROTO_XRRESULT_FUNC3(xrCreatePassthroughHTC, + (XrSession), session, + (const XrPassthroughCreateInfoHTC *), create_info, + (XrPassthroughHTC *), feature_out) + + // Destroy a previously created passthrough feature + EXT_PROTO_XRRESULT_FUNC1(xrDestroyPassthroughHTC, (XrPassthroughHTC), feature) + + bool initialize_htc_passthrough_extension(const XrInstance instance); + + void cleanup(); + + Viewport *get_main_viewport(); + + static OpenXRHTCPassthroughExtension *singleton; + + bool htc_passthrough_ext = false; // required for any passthrough functionality + + XrPassthroughHTC passthrough_handle = XR_NULL_HANDLE; // handle for passthrough + + XrCompositionLayerPassthroughHTC composition_passthrough_layer = { + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC, // XrStructureType + nullptr, // next + XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT, // XrCompositionLayerFlags + XR_NULL_HANDLE, // XrSpace + XR_NULL_HANDLE, // XrPassthroughHTC + { + // XrPassthroughColorHTC + XR_TYPE_PASSTHROUGH_COLOR_HTC, // XrStructureType + nullptr, // next + 1.0f // alpha (preset to opaque) + } + }; +}; + +#endif // OPENXR_HTC_PASSTHROUGH_EXTENSION_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 66f8192c9e00..0230a4d3c6dd 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -1112,20 +1112,40 @@ void OpenXRInterface::end_frame() { } bool OpenXRInterface::is_passthrough_supported() { - return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_supported(); + if ((fb_passthrough_wrapper != nullptr) && (fb_passthrough_wrapper->is_passthrough_supported())) { + return true; + } else if ((htc_passthrough_wrapper != nullptr) && (htc_passthrough_wrapper->is_passthrough_supported())) { + return true; + } else { + return false; + } } bool OpenXRInterface::is_passthrough_enabled() { - return passthrough_wrapper != nullptr && passthrough_wrapper->is_passthrough_enabled(); + if ((fb_passthrough_wrapper != nullptr) && (fb_passthrough_wrapper->is_passthrough_enabled())) { + return true; + } else if ((htc_passthrough_wrapper != nullptr) && (htc_passthrough_wrapper->is_passthrough_enabled())) { + return true; + } else { + return false; + } } bool OpenXRInterface::start_passthrough() { - return passthrough_wrapper != nullptr && passthrough_wrapper->start_passthrough(); + if ((fb_passthrough_wrapper != nullptr) && (fb_passthrough_wrapper->start_passthrough())) { + return true; + } else if ((htc_passthrough_wrapper != nullptr) && (htc_passthrough_wrapper->start_passthrough())) { + return true; + } else { + return false; + } } void OpenXRInterface::stop_passthrough() { - if (passthrough_wrapper) { - passthrough_wrapper->stop_passthrough(); + if (fb_passthrough_wrapper != nullptr) { + fb_passthrough_wrapper->stop_passthrough(); + } else if (htc_passthrough_wrapper != nullptr) { + htc_passthrough_wrapper->stop_passthrough(); } } @@ -1356,7 +1376,8 @@ OpenXRInterface::OpenXRInterface() { _set_default_pos(transform_for_view[0], 1.0, 1); _set_default_pos(transform_for_view[1], 1.0, 2); - passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton(); + fb_passthrough_wrapper = OpenXRFbPassthroughExtensionWrapper::get_singleton(); + htc_passthrough_wrapper = OpenXRHTCPassthroughExtension::get_singleton(); } OpenXRInterface::~OpenXRInterface() { diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 489d0845ba5a..5f4697d65437 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -34,6 +34,7 @@ #include "action_map/openxr_action_map.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" +#include "extensions/openxr_htc_passthrough_extension.h" #include "openxr_api.h" #include "servers/xr/xr_interface.h" @@ -49,7 +50,8 @@ class OpenXRInterface : public XRInterface { OpenXRAPI *openxr_api = nullptr; bool initialized = false; XRInterface::TrackingStatus tracking_state; - OpenXRFbPassthroughExtensionWrapper *passthrough_wrapper = nullptr; + OpenXRFbPassthroughExtensionWrapper *fb_passthrough_wrapper = nullptr; + OpenXRHTCPassthroughExtension *htc_passthrough_wrapper = nullptr; // At a minimum we need a tracker for our head Ref<XRPositionalTracker> head; diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 544932bdeb63..fb3e8e5bf619 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -47,6 +47,7 @@ #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" #include "extensions/openxr_htc_controller_extension.h" +#include "extensions/openxr_htc_passthrough_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" #include "extensions/openxr_huawei_controller_extension.h" #include "extensions/openxr_ml2_controller_extension.h" @@ -109,6 +110,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCPassthroughExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper));