From 0f2b804059331f2454e060577ae0e1b44675c5aa Mon Sep 17 00:00:00 2001 From: David Snopek Date: Mon, 25 Mar 2024 08:07:28 -0500 Subject: [PATCH] Add support for OpenXR composition layers Co-authored-by: Bastiaan Olij --- doc/classes/RenderingServer.xml | 8 + modules/openxr/config.py | 4 + .../doc_classes/OpenXRCompositionLayer.xml | 34 ++ .../OpenXRCompositionLayerCylinder.xml | 25 ++ .../OpenXRCompositionLayerEquirect.xml | 28 ++ .../OpenXRCompositionLayerQuad.xml | 16 + .../openxr_composition_layer_extension.cpp | 296 +++++++++++++++++ .../openxr_composition_layer_extension.h | 112 +++++++ modules/openxr/openxr_api.cpp | 85 +++-- modules/openxr/openxr_api.h | 32 +- modules/openxr/register_types.cpp | 11 + .../openxr/scene/openxr_composition_layer.cpp | 303 ++++++++++++++++++ .../openxr/scene/openxr_composition_layer.h | 91 ++++++ .../openxr_composition_layer_cylinder.cpp | 190 +++++++++++ .../scene/openxr_composition_layer_cylinder.h | 73 +++++ .../openxr_composition_layer_equirect.cpp | 209 ++++++++++++ .../scene/openxr_composition_layer_equirect.h | 77 +++++ .../scene/openxr_composition_layer_quad.cpp | 98 ++++++ .../scene/openxr_composition_layer_quad.h | 61 ++++ scene/main/viewport.cpp | 4 + servers/rendering/renderer_viewport.cpp | 9 +- servers/rendering/renderer_viewport.h | 1 + servers/rendering/rendering_server_default.h | 1 + servers/rendering_server.cpp | 1 + servers/rendering_server.h | 1 + 25 files changed, 1721 insertions(+), 49 deletions(-) create mode 100644 modules/openxr/doc_classes/OpenXRCompositionLayer.xml create mode 100644 modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml create mode 100644 modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml create mode 100644 modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml create mode 100644 modules/openxr/extensions/openxr_composition_layer_extension.cpp create mode 100644 modules/openxr/extensions/openxr_composition_layer_extension.h create mode 100644 modules/openxr/scene/openxr_composition_layer.cpp create mode 100644 modules/openxr/scene/openxr_composition_layer.h create mode 100644 modules/openxr/scene/openxr_composition_layer_cylinder.cpp create mode 100644 modules/openxr/scene/openxr_composition_layer_cylinder.h create mode 100644 modules/openxr/scene/openxr_composition_layer_equirect.cpp create mode 100644 modules/openxr/scene/openxr_composition_layer_equirect.h create mode 100644 modules/openxr/scene/openxr_composition_layer_quad.cpp create mode 100644 modules/openxr/scene/openxr_composition_layer_quad.h diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 5efda5f83f4..c74d61ab882 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -3684,6 +3684,14 @@ Returns the viewport's last rendered frame. + + + + + Returns the viewport's update mode. See [enum ViewportUpdateMode] constants for options. + [b]Warning:[/b] Calling this from any thread other than the rendering thread will be detrimental to performance. + + diff --git a/modules/openxr/config.py b/modules/openxr/config.py index 92aaf06fcea..6c6b9fdb8b9 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -22,6 +22,10 @@ def get_doc_classes(): "OpenXRInteractionProfileMetadata", "OpenXRIPBinding", "OpenXRHand", + "OpenXRCompositionLayer", + "OpenXRCompositionLayerQuad", + "OpenXRCompositionLayerCylinder", + "OpenXRCompositionLayerEquirect", ] diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml new file mode 100644 index 00000000000..66baaf096d6 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -0,0 +1,34 @@ + + + + The parent class of all OpenXR composition layer nodes. + + + Composition layers allow 2D viewports to be displayed inside of the headset by the XR compositor through special projections that retain their quality. This allows for rendering clear text while keeping the layer at a native resolution. + [b]Note:[/b] If the OpenXR runtime doesn't support the given composition layer type, a fallback mesh can be generated with a [ViewportTexture], in order to emulate the composition layer. + + + + + + + + Returns true if the OpenXR runtime natively supports this composition layer type. + [b]Note:[/b] This will only return an accurate result after the OpenXR session has started. + + + + + + Enables the blending the layer using its alpha channel. + Can be combined with [member Viewport.transparent_bg] to give the layer a transparent background. + + + The [SubViewport] to render on the composition layer. + + + The sort order for this composition layer. Higher numbers will be shown in front of lower numbers. + [b]Note:[/b] This will have no effect if a fallback mesh is being used. + + + diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml new file mode 100644 index 00000000000..2de19776714 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -0,0 +1,25 @@ + + + + An OpenXR composition layer that is rendered as an internal slice of a cylinder. + + + An OpenXR composition layer that allows rendering a [SubViewport] on an internal slice of a cylinder. + + + + + + The aspect ratio of the slice. Used to set the height relative to the width. + + + The central angle of the cylinder. Used to set the width. + + + The number of segments to use in the fallback mesh. + + + The radius of the cylinder. + + + diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml new file mode 100644 index 00000000000..f6eba7e2280 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -0,0 +1,28 @@ + + + + An OpenXR composition layer that is rendered as an internal slice of a sphere. + + + An OpenXR composition layer that allows rendering a [SubViewport] on an internal slice of a sphere. + + + + + + The central horizontal angle of the sphere. Used to set the width. + + + The number of segments to use in the fallback mesh. + + + The lower vertical angle of the sphere. Used (together with [member upper_vertical_angle]) to set the height. + + + The radius of the sphere. + + + The upper vertical angle of the sphere. Used (together with [member lower_vertical_angle]) to set the height. + + + diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml new file mode 100644 index 00000000000..fff592bc4fe --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml @@ -0,0 +1,16 @@ + + + + An OpenXR composition layer that is rendered as a quad. + + + An OpenXR composition layer that allows rendering a [SubViewport] on a quad. + + + + + + The dimensions of the quad. + + + diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp new file mode 100644 index 00000000000..7c0c69d747d --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -0,0 +1,296 @@ +/**************************************************************************/ +/* openxr_composition_layer_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_composition_layer_extension.h" + +#include "servers/rendering/rendering_server_globals.h" + +//////////////////////////////////////////////////////////////////////////// +// OpenXRCompositionLayerExtension + +OpenXRCompositionLayerExtension *OpenXRCompositionLayerExtension::singleton = nullptr; + +OpenXRCompositionLayerExtension *OpenXRCompositionLayerExtension::get_singleton() { + return singleton; +} + +OpenXRCompositionLayerExtension::OpenXRCompositionLayerExtension() { + singleton = this; +} + +OpenXRCompositionLayerExtension::~OpenXRCompositionLayerExtension() { + singleton = nullptr; +} + +HashMap OpenXRCompositionLayerExtension::get_requested_extensions() { + HashMap request_extensions; + + request_extensions[XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME] = &cylinder_ext_available; + request_extensions[XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME] = &equirect_ext_available; + + return request_extensions; +} + +void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_instance) { + OpenXRAPI::get_singleton()->register_composition_layer_provider(this); +} + +void OpenXRCompositionLayerExtension::on_session_destroyed() { + OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this); +} + +void OpenXRCompositionLayerExtension::on_pre_render() { + for (OpenXRViewportCompositionLayerProvider *composition_layer : composition_layers) { + composition_layer->on_pre_render(); + } +} + +int OpenXRCompositionLayerExtension::get_composition_layer_count() { + return composition_layers.size(); +} + +XrCompositionLayerBaseHeader *OpenXRCompositionLayerExtension::get_composition_layer(int p_index) { + ERR_FAIL_INDEX_V(p_index, composition_layers.size(), nullptr); + return composition_layers[p_index]->get_composition_layer(); +} + +int OpenXRCompositionLayerExtension::get_composition_layer_order(int p_index) { + ERR_FAIL_INDEX_V(p_index, composition_layers.size(), 1); + return composition_layers[p_index]->get_sort_order(); +} + +void OpenXRCompositionLayerExtension::register_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer) { + composition_layers.push_back(p_composition_layer); +} + +void OpenXRCompositionLayerExtension::unregister_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer) { + composition_layers.erase(p_composition_layer); +} + +bool OpenXRCompositionLayerExtension::is_available(XrStructureType p_which) { + switch (p_which) { + case XR_TYPE_COMPOSITION_LAYER_QUAD: { + // Doesn't require an extension. + return true; + } break; + case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: { + return cylinder_ext_available; + } break; + case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: { + return equirect_ext_available; + } break; + default: { + ERR_PRINT(vformat("Unsupported composition layer type: %s", p_which)); + return false; + } + } +} + +//////////////////////////////////////////////////////////////////////////// +// OpenXRViewportCompositionLayerProvider + +OpenXRViewportCompositionLayerProvider::OpenXRViewportCompositionLayerProvider(XrCompositionLayerBaseHeader *p_composition_layer) { + composition_layer = p_composition_layer; + openxr_api = OpenXRAPI::get_singleton(); + composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton(); +} + +OpenXRViewportCompositionLayerProvider::~OpenXRViewportCompositionLayerProvider() { + // This will reset the viewport and free the swapchain too. + set_viewport(RID(), Size2i()); +} + +void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) { + if (alpha_blend != p_alpha_blend) { + alpha_blend = p_alpha_blend; + if (alpha_blend) { + composition_layer->layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; + } else { + composition_layer->layerFlags &= ~XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; + } + } +} + +void OpenXRViewportCompositionLayerProvider::set_viewport(RID p_viewport, Size2i p_size) { + RenderingServer *rs = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rs); + + if (viewport != p_viewport) { + if (viewport.is_valid()) { + RID rt = rs->viewport_get_render_target(viewport); + RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID()); + } + + viewport = p_viewport; + + if (viewport.is_valid()) { + viewport_size = p_size; + } else { + free_swapchain(); + viewport_size = Size2i(); + } + } +} + +void OpenXRViewportCompositionLayerProvider::on_pre_render() { + RenderingServer *rs = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rs); + + if (viewport.is_valid() && openxr_api && openxr_api->is_running()) { + RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(viewport); + if (update_mode == RS::VIEWPORT_UPDATE_ONCE || update_mode == RS::VIEWPORT_UPDATE_ALWAYS) { + // Update our XR swapchain + if (update_and_acquire_swapchain(update_mode == RS::VIEWPORT_UPDATE_ONCE)) { + // Render to our XR swapchain image. + RID rt = rs->viewport_get_render_target(viewport); + RSG::texture_storage->render_target_set_override(rt, get_current_swapchain_texture(), RID(), RID()); + } + } + } +} + +XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_composition_layer() { + if (openxr_api == nullptr || composition_layer_extension == nullptr) { + // OpenXR not initialised or we're in the editor? + return nullptr; + } + + if (!composition_layer_extension->is_available(composition_layer->type)) { + // Selected type is not supported, ignore our layer. + return nullptr; + } + + if (swapchain_info.swapchain == XR_NULL_HANDLE) { + // Don't have a swapchain to display? Ignore our layer. + return nullptr; + } + + if (swapchain_info.image_acquired) { + openxr_api->release_image(swapchain_info); + } + + // Update the layer struct for the swapchain. + switch (composition_layer->type) { + case XR_TYPE_COMPOSITION_LAYER_QUAD: { + XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer; + quad_layer->subImage.swapchain = swapchain_info.swapchain; + quad_layer->subImage.imageArrayIndex = 0; + quad_layer->subImage.imageRect.offset.x = 0; + quad_layer->subImage.imageRect.offset.y = 0; + quad_layer->subImage.imageRect.extent.width = swapchain_size.width; + quad_layer->subImage.imageRect.extent.height = swapchain_size.height; + } break; + + case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: { + XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer; + cylinder_layer->subImage.swapchain = swapchain_info.swapchain; + cylinder_layer->subImage.imageArrayIndex = 0; + cylinder_layer->subImage.imageRect.offset.x = 0; + cylinder_layer->subImage.imageRect.offset.y = 0; + cylinder_layer->subImage.imageRect.extent.width = swapchain_size.width; + cylinder_layer->subImage.imageRect.extent.height = swapchain_size.height; + } break; + + case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: { + XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer; + equirect_layer->subImage.swapchain = swapchain_info.swapchain; + equirect_layer->subImage.imageArrayIndex = 0; + equirect_layer->subImage.imageRect.offset.x = 0; + equirect_layer->subImage.imageRect.offset.y = 0; + equirect_layer->subImage.imageRect.extent.width = swapchain_size.width; + equirect_layer->subImage.imageRect.extent.height = swapchain_size.height; + } break; + + default: { + return nullptr; + } break; + } + + return composition_layer; +} + +bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p_static_image) { + if (openxr_api == nullptr || composition_layer_extension == nullptr) { + // OpenXR not initialised or we're in the editor? + return false; + } + if (!composition_layer_extension->is_available(composition_layer->type)) { + // Selected type is not supported? + return false; + } + + // See if our current swapchain is outdated. + if (swapchain_info.swapchain != XR_NULL_HANDLE) { + // If this swap chain, or the previous one, were static, then we can't reuse it. + if (swapchain_size == viewport_size && !p_static_image && !static_image) { + // We're all good! Just acquire it. + return openxr_api->acquire_image(swapchain_info); + } + + openxr_api->free_swapchain(swapchain_info); + } + + // Create our new swap chain + int64_t swapchain_format = openxr_api->get_color_swapchain_format(); + const uint32_t sample_count = 3; + const uint32_t array_size = 1; + XrSwapchainCreateFlags create_flags = 0; + if (p_static_image) { + create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT; + } + if (!openxr_api->create_swapchain(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size, swapchain_info.swapchain, &swapchain_info.swapchain_graphics_data)) { + swapchain_size = Size2i(); + return false; + } + + // Acquire our image so we can start rendering into it + bool ret = openxr_api->acquire_image(swapchain_info); + + swapchain_size = viewport_size; + static_image = p_static_image; + return ret; +} + +void OpenXRViewportCompositionLayerProvider::free_swapchain() { + if (swapchain_info.swapchain != XR_NULL_HANDLE) { + openxr_api->free_swapchain(swapchain_info); + } + + swapchain_size = Size2i(); + static_image = false; +} + +RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() { + if (openxr_api == nullptr) { + return RID(); + } + + return openxr_api->get_image(swapchain_info); +} diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h new file mode 100644 index 00000000000..7cc35005f84 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_extension.h @@ -0,0 +1,112 @@ +/**************************************************************************/ +/* openxr_composition_layer_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_COMPOSITION_LAYER_EXTENSION_H +#define OPENXR_COMPOSITION_LAYER_EXTENSION_H + +#include "openxr_composition_layer_provider.h" +#include "openxr_extension_wrapper.h" + +#include "../openxr_api.h" + +class OpenXRViewportCompositionLayerProvider; + +// This extension provides access to composition layers for displaying 2D content through the XR compositor. + +// OpenXRCompositionLayerExtension enables the extensions related to this functionality +class OpenXRCompositionLayerExtension : public OpenXRExtensionWrapper, public OpenXRCompositionLayerProvider { +public: + static OpenXRCompositionLayerExtension *get_singleton(); + + OpenXRCompositionLayerExtension(); + virtual ~OpenXRCompositionLayerExtension() override; + + virtual HashMap get_requested_extensions() override; + virtual void on_session_created(const XrSession p_instance) override; + virtual void on_session_destroyed() override; + virtual void on_pre_render() override; + + virtual int get_composition_layer_count() override; + virtual XrCompositionLayerBaseHeader *get_composition_layer(int p_index) override; + virtual int get_composition_layer_order(int p_index) override; + + void register_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer); + void unregister_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer); + + bool is_available(XrStructureType p_which); + +private: + static OpenXRCompositionLayerExtension *singleton; + + Vector composition_layers; + + bool cylinder_ext_available = false; + bool equirect_ext_available = false; +}; + +class OpenXRViewportCompositionLayerProvider { + XrCompositionLayerBaseHeader *composition_layer = nullptr; + int sort_order = 1; + bool alpha_blend = false; + + RID viewport; + Size2i viewport_size; + + OpenXRAPI::OpenXRSwapChainInfo swapchain_info; + Size2i swapchain_size; + bool static_image = false; + + OpenXRAPI *openxr_api = nullptr; + OpenXRCompositionLayerExtension *composition_layer_extension = nullptr; + + bool update_and_acquire_swapchain(bool p_static_image); + void free_swapchain(); + RID get_current_swapchain_texture(); + +public: + XrStructureType get_openxr_type() { return composition_layer->type; } + + void set_sort_order(int p_sort_order) { sort_order = p_sort_order; } + int get_sort_order() const { return sort_order; } + + void set_alpha_blend(bool p_alpha_blend); + bool get_alpha_blend() const { return alpha_blend; } + + void set_viewport(RID p_viewport, Size2i p_size); + RID get_viewport() const { return viewport; } + + void on_pre_render(); + XrCompositionLayerBaseHeader *get_composition_layer(); + + OpenXRViewportCompositionLayerProvider(XrCompositionLayerBaseHeader *p_composition_layer); + ~OpenXRViewportCompositionLayerProvider(); +}; + +#endif // OPENXR_COMPOSITION_LAYER_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 8dd017c2137..91a4839a066 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -906,25 +906,25 @@ bool OpenXRAPI::create_swapchains() { { // Build a vector with swapchain formats we want to use, from best fit to worst Vector usable_swapchain_formats; - int64_t swapchain_format_to_use = 0; + color_swapchain_format = 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++) { + for (int i = 0; i < usable_swapchain_formats.size() && color_swapchain_format == 0; i++) { if (is_swapchain_format_supported(usable_swapchain_formats[i])) { - swapchain_format_to_use = usable_swapchain_formats[i]; + color_swapchain_format = 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 color swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + if (color_swapchain_format == 0) { + color_swapchain_format = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable color swap chain format, using", get_swapchain_format_name(color_swapchain_format), "instead."); } else { - print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); + print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(color_swapchain_format)); } - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { + if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { return false; } } @@ -942,25 +942,25 @@ bool OpenXRAPI::create_swapchains() { if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { // Build a vector with swapchain formats we want to use, from best fit to worst Vector usable_swapchain_formats; - int64_t swapchain_format_to_use = 0; + depth_swapchain_format = 0; graphics_extension->get_usable_depth_formats(usable_swapchain_formats); // now find out which one is supported - for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + for (int i = 0; i < usable_swapchain_formats.size() && depth_swapchain_format == 0; i++) { if (is_swapchain_format_supported(usable_swapchain_formats[i])) { - swapchain_format_to_use = usable_swapchain_formats[i]; + depth_swapchain_format = usable_swapchain_formats[i]; } } - if (swapchain_format_to_use == 0) { + if (depth_swapchain_format == 0) { print_line("Couldn't find usable depth swap chain format, depth buffer will not be submitted."); } else { - print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); + print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(depth_swapchain_format)); // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning. - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { + if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { return false; } @@ -1014,10 +1014,6 @@ void OpenXRAPI::destroy_session() { xrEndSession(session); } - if (graphics_extension) { - graphics_extension->cleanup_swapchain_graphics_data(&swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data); - } - if (views != nullptr) { memfree(views); views = nullptr; @@ -1034,10 +1030,7 @@ void OpenXRAPI::destroy_session() { } for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (swapchains[i].swapchain != XR_NULL_HANDLE) { - xrDestroySwapchain(swapchains[i].swapchain); - swapchains[i].swapchain = XR_NULL_HANDLE; - } + free_swapchain(swapchains[i]); } if (supported_swapchain_formats != nullptr) { @@ -1071,7 +1064,7 @@ void OpenXRAPI::destroy_session() { } } -bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { +bool OpenXRAPI::create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); ERR_FAIL_NULL_V(graphics_extension, false); @@ -1088,7 +1081,7 @@ bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_ XrSwapchainCreateInfo swapchain_create_info = { XR_TYPE_SWAPCHAIN_CREATE_INFO, // type next_pointer, // next - 0, // createFlags + p_create_flags, // createFlags p_usage_flags, // usageFlags p_swapchain_format, // format p_sample_count, // sampleCount @@ -1805,6 +1798,21 @@ bool OpenXRAPI::process() { return true; } +void OpenXRAPI::free_swapchain(OpenXRSwapChainInfo &p_swapchain) { + if (p_swapchain.image_acquired) { + release_image(p_swapchain); + } + + if (graphics_extension && p_swapchain.swapchain_graphics_data != nullptr) { + graphics_extension->cleanup_swapchain_graphics_data(&p_swapchain.swapchain_graphics_data); + } + + if (p_swapchain.swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(p_swapchain.swapchain); + p_swapchain.swapchain = XR_NULL_HANDLE; + } +} + bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse... @@ -1856,10 +1864,26 @@ bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { p_swapchain.skip_acquire_swapchain = false; } + p_swapchain.image_acquired = true; return true; } +RID OpenXRAPI::get_image(OpenXRSwapChainInfo &p_swapchain) { + if (p_swapchain.image_acquired) { + return graphics_extension->get_texture(p_swapchain.swapchain_graphics_data, p_swapchain.image_index); + } else { + return RID(); + } +} + bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) { + if (!p_swapchain.image_acquired) { + // Already released or never acquired. + return true; + } + + p_swapchain.image_acquired = false; // Regardless if we succeed or not, consider this released. + XrSwapchainImageReleaseInfo swapchain_image_release_info = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type nullptr // next @@ -1987,7 +2011,6 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { if (!acquire_image(swapchains[i])) { return false; } - swapchains[i].image_acquired = true; } } @@ -2003,17 +2026,13 @@ XrSwapchain OpenXRAPI::get_color_swapchain() { } RID OpenXRAPI::get_color_texture() { - if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { - return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index); - } else { - return RID(); - } + return get_image(swapchains[OPENXR_SWAPCHAIN_COLOR]); } RID OpenXRAPI::get_depth_texture() { // Note, image will not be acquired if we didn't have a suitable swap chain format. - if (submit_depth_buffer && swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { - return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); + if (submit_depth_buffer) { + return get_image(swapchains[OPENXR_SWAPCHAIN_DEPTH]); } else { return RID(); } @@ -2069,8 +2088,6 @@ void OpenXRAPI::end_frame() { // release our swapchain image if we acquired it for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { if (swapchains[i].image_acquired) { - swapchains[i].image_acquired = false; // whether we succeed or not, consider this released. - release_image(swapchains[i]); } } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 7ec622364b0..615643591f2 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -57,6 +57,15 @@ class OpenXRInterface; class OpenXRAPI { +public: + struct OpenXRSwapChainInfo { + XrSwapchain swapchain = XR_NULL_HANDLE; + void *swapchain_graphics_data = nullptr; + uint32_t image_index = 0; + bool image_acquired = false; + bool skip_acquire_swapchain = false; + }; + private: // our singleton static OpenXRAPI *singleton; @@ -137,14 +146,8 @@ private: OPENXR_SWAPCHAIN_MAX }; - struct OpenXRSwapChainInfo { - XrSwapchain swapchain = XR_NULL_HANDLE; - void *swapchain_graphics_data = nullptr; - uint32_t image_index = 0; - bool image_acquired = false; - bool skip_acquire_swapchain = false; - }; - + int64_t color_swapchain_format = 0; + int64_t depth_swapchain_format = 0; OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; XrSpace play_space = XR_NULL_HANDLE; @@ -241,11 +244,6 @@ private: bool create_swapchains(); void destroy_session(); - // swapchains - bool create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); - bool acquire_image(OpenXRSwapChainInfo &p_swapchain); - bool release_image(OpenXRSwapChainInfo &p_swapchain); - // action map struct Tracker { // Trackers represent tracked physical objects such as controllers, pucks, etc. String name; // Name for this tracker (i.e. "/user/hand/left") @@ -405,6 +403,14 @@ public: // Play space. Size2 get_play_space_bounds() const; + // swapchains + int64_t get_color_swapchain_format() const { return color_swapchain_format; } + bool create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); + void free_swapchain(OpenXRSwapChainInfo &p_swapchain); + bool acquire_image(OpenXRSwapChainInfo &p_swapchain); + RID get_image(OpenXRSwapChainInfo &p_swapchain); + bool release_image(OpenXRSwapChainInfo &p_swapchain); + // action map String get_default_action_map_resource_name(); diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 3d34b27407c..68cdb6d9985 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -39,9 +39,14 @@ #include "extensions/openxr_extension_wrapper_extension.h" +#include "scene/openxr_composition_layer.h" +#include "scene/openxr_composition_layer_cylinder.h" +#include "scene/openxr_composition_layer_equirect.h" +#include "scene/openxr_composition_layer_quad.h" #include "scene/openxr_hand.h" #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_composition_layer_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_hand_tracking_extension.h" @@ -110,6 +115,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension)); @@ -162,6 +168,11 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OpenXRIPBinding); GDREGISTER_CLASS(OpenXRInteractionProfile); + GDREGISTER_ABSTRACT_CLASS(OpenXRCompositionLayer); + GDREGISTER_CLASS(OpenXRCompositionLayerEquirect); + GDREGISTER_CLASS(OpenXRCompositionLayerCylinder); + GDREGISTER_CLASS(OpenXRCompositionLayerQuad); + GDREGISTER_CLASS(OpenXRHand); XRServer *xr_server = XRServer::get_singleton(); diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp new file mode 100644 index 00000000000..120914485f2 --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -0,0 +1,303 @@ +/**************************************************************************/ +/* openxr_composition_layer.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_composition_layer.h" + +#include "../extensions/openxr_composition_layer_extension.h" +#include "../openxr_api.h" +#include "../openxr_interface.h" + +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/xr_nodes.h" +#include "scene/main/viewport.h" + +HashSet OpenXRCompositionLayer::viewports_in_use; + +OpenXRCompositionLayer::OpenXRCompositionLayer() { + openxr_api = OpenXRAPI::get_singleton(); + composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton(); + + Ref openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->connect("session_begun", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_begun)); + openxr_interface->connect("session_stopping", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_stopping)); + } + + set_process_internal(true); + set_notify_local_transform(true); + + if (Engine::get_singleton()->is_editor_hint()) { + // In the editor, create the fallback right away. + _create_fallback_node(); + } +} + +OpenXRCompositionLayer::~OpenXRCompositionLayer() { + Ref openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->disconnect("session_begun", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_begun)); + openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_stopping)); + } + + if (layer_viewport) { + viewports_in_use.erase(layer_viewport); + } + + if (openxr_layer_provider != nullptr) { + memdelete(openxr_layer_provider); + openxr_layer_provider = nullptr; + } +} + +void OpenXRCompositionLayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_layer_viewport", "viewport"), &OpenXRCompositionLayer::set_layer_viewport); + ClassDB::bind_method(D_METHOD("get_layer_viewport"), &OpenXRCompositionLayer::get_layer_viewport); + + ClassDB::bind_method(D_METHOD("set_sort_order", "order"), &OpenXRCompositionLayer::set_sort_order); + ClassDB::bind_method(D_METHOD("get_sort_order"), &OpenXRCompositionLayer::get_sort_order); + + ClassDB::bind_method(D_METHOD("set_alpha_blend", "enabled"), &OpenXRCompositionLayer::set_alpha_blend); + ClassDB::bind_method(D_METHOD("get_alpha_blend"), &OpenXRCompositionLayer::get_alpha_blend); + + ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sort_order", PROPERTY_HINT_NONE, ""), "set_sort_order", "get_sort_order"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_blend", PROPERTY_HINT_NONE, ""), "set_alpha_blend", "get_alpha_blend"); +} + +void OpenXRCompositionLayer::_create_fallback_node() { + ERR_FAIL_COND(fallback); + fallback = memnew(MeshInstance3D); + fallback->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + add_child(fallback, false, INTERNAL_MODE_FRONT); + should_update_fallback_mesh = true; +} + +void OpenXRCompositionLayer::_on_openxr_session_begun() { + if (!is_natively_supported()) { + if (!fallback) { + _create_fallback_node(); + } + } else if (layer_viewport && is_visible() && is_inside_tree()) { + openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + } +} + +void OpenXRCompositionLayer::_on_openxr_session_stopping() { + if (fallback && !Engine::get_singleton()->is_editor_hint()) { + fallback->queue_free(); + remove_child(fallback); + fallback = nullptr; + } else { + openxr_layer_provider->set_viewport(RID(), Size2i()); + } +} + +void OpenXRCompositionLayer::update_fallback_mesh() { + should_update_fallback_mesh = true; +} + +void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) { + if (layer_viewport == p_viewport) { + return; + } + + ERR_FAIL_COND_EDMSG(viewports_in_use.has(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); + + if (layer_viewport) { + viewports_in_use.erase(layer_viewport); + } + + layer_viewport = p_viewport; + + if (layer_viewport) { + viewports_in_use.insert(layer_viewport); + + SubViewport::UpdateMode update_mode = layer_viewport->get_update_mode(); + if (update_mode == SubViewport::UPDATE_WHEN_VISIBLE || update_mode == SubViewport::UPDATE_WHEN_PARENT_VISIBLE) { + WARN_PRINT_ONCE("OpenXR composition layers cannot use SubViewports with UPDATE_WHEN_VISIBLE or UPDATE_WHEN_PARENT_VISIBLE. Switching to UPDATE_ALWAYS."); + layer_viewport->set_update_mode(SubViewport::UPDATE_ALWAYS); + } + } + + if (fallback) { + _reset_fallback_material(); + } else if (openxr_api && openxr_api->is_running() && is_visible() && is_inside_tree()) { + if (layer_viewport) { + openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + } else { + openxr_layer_provider->set_viewport(RID(), Size2i()); + } + } +} + +SubViewport *OpenXRCompositionLayer::get_layer_viewport() const { + return layer_viewport; +} + +void OpenXRCompositionLayer::set_sort_order(int p_order) { + if (openxr_layer_provider) { + openxr_layer_provider->set_sort_order(p_order); + } +} + +int OpenXRCompositionLayer::get_sort_order() const { + if (openxr_layer_provider) { + return openxr_layer_provider->get_sort_order(); + } + return 1; +} + +void OpenXRCompositionLayer::set_alpha_blend(bool p_alpha_blend) { + if (openxr_layer_provider) { + openxr_layer_provider->set_alpha_blend(p_alpha_blend); + if (fallback) { + _reset_fallback_material(); + } + } +} + +bool OpenXRCompositionLayer::get_alpha_blend() const { + if (openxr_layer_provider) { + return openxr_layer_provider->get_alpha_blend(); + } + return false; +} + +bool OpenXRCompositionLayer::is_natively_supported() const { + if (composition_layer_extension) { + return composition_layer_extension->is_available(openxr_layer_provider->get_openxr_type()); + } + return false; +} + +void OpenXRCompositionLayer::_reset_fallback_material() { + ERR_FAIL_NULL(fallback); + + if (fallback->get_mesh().is_null()) { + return; + } + + if (layer_viewport) { + Ref material = fallback->get_surface_override_material(0); + if (material.is_null()) { + material.instantiate(); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + material->set_local_to_scene(true); + fallback->set_surface_override_material(0, material); + } + material->set_transparency(get_alpha_blend() ? StandardMaterial3D::TRANSPARENCY_ALPHA : StandardMaterial3D::TRANSPARENCY_DISABLED); + + Ref texture = material->get_texture(StandardMaterial3D::TEXTURE_ALBEDO); + if (texture.is_null()) { + texture.instantiate(); + // ViewportTexture can't be configured without a local scene, so use this hack to set it. + HashMap, Ref> remap_cache; + texture->configure_for_local_scene(this, remap_cache); + } + + Node *loc_scene = texture->get_local_scene(); + NodePath viewport_path = loc_scene->get_path_to(layer_viewport); + texture->set_viewport_path_in_scene(viewport_path); + material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, texture); + } else { + fallback->set_surface_override_material(0, Ref()); + } +} + +void OpenXRCompositionLayer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_INTERNAL_PROCESS: { + if (fallback) { + if (should_update_fallback_mesh) { + fallback->set_mesh(_create_fallback_mesh()); + _reset_fallback_material(); + should_update_fallback_mesh = false; + } + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!fallback && openxr_api && openxr_api->is_running() && is_inside_tree()) { + if (layer_viewport && is_visible()) { + openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + } else { + openxr_layer_provider->set_viewport(RID(), Size2i()); + } + } + update_configuration_warnings(); + } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + update_configuration_warnings(); + } break; + case NOTIFICATION_ENTER_TREE: { + if (composition_layer_extension) { + composition_layer_extension->register_composition_layer_provider(openxr_layer_provider); + } + + if (!fallback && layer_viewport && openxr_api && openxr_api->is_running() && is_visible()) { + openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); + } + } break; + case NOTIFICATION_EXIT_TREE: { + if (composition_layer_extension) { + composition_layer_extension->unregister_composition_layer_provider(openxr_layer_provider); + } + + // When a node is removed in the editor, we need to clear the layer viewport, because otherwise + // there will be issues with the tracking in viewports_in_use, since nodes deleted in the editor + // aren't really deleted in order to support undo. + if (Engine::get_singleton()->is_editor_hint() && layer_viewport) { + set_layer_viewport(nullptr); + } else if (!fallback) { + // This will clean up existing resources. + openxr_layer_provider->set_viewport(RID(), Size2i()); + } + } break; + } +} + +PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); + + if (is_visible() && is_inside_tree()) { + XROrigin3D *origin = Object::cast_to(get_parent()); + if (origin == nullptr) { + warnings.push_back(RTR("OpenXR composition layers must have an XROrigin3D node as their parent.")); + } + } + + if (!get_transform().basis.is_orthonormal()) { + warnings.push_back(RTR("OpenXR composition layers must have orthonormalized transforms (ie. no scale or shearing).")); + } + + return warnings; +} diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h new file mode 100644 index 00000000000..f683aea647d --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer.h @@ -0,0 +1,91 @@ +/**************************************************************************/ +/* openxr_composition_layer.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_COMPOSITION_LAYER_H +#define OPENXR_COMPOSITION_LAYER_H + +#include + +#include "scene/3d/node_3d.h" + +class MeshInstance3D; +class Mesh; +class OpenXRAPI; +class OpenXRCompositionLayerExtension; +class OpenXRViewportCompositionLayerProvider; +class SubViewport; + +class OpenXRCompositionLayer : public Node3D { + GDCLASS(OpenXRCompositionLayer, Node3D); + + SubViewport *layer_viewport = nullptr; + MeshInstance3D *fallback = nullptr; + bool should_update_fallback_mesh = false; + + void _create_fallback_node(); + void _reset_fallback_material(); + +protected: + OpenXRAPI *openxr_api = nullptr; + OpenXRCompositionLayerExtension *composition_layer_extension = nullptr; + OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr; + + static void _bind_methods(); + + void _notification(int p_what); + + virtual void _on_openxr_session_begun(); + virtual void _on_openxr_session_stopping(); + + virtual Ref _create_fallback_mesh() = 0; + + void update_fallback_mesh(); + + static HashSet viewports_in_use; + +public: + void set_layer_viewport(SubViewport *p_viewport); + SubViewport *get_layer_viewport() const; + + void set_sort_order(int p_order); + int get_sort_order() const; + + void set_alpha_blend(bool p_alpha_blend); + bool get_alpha_blend() const; + + bool is_natively_supported() const; + + virtual PackedStringArray get_configuration_warnings() const override; + + OpenXRCompositionLayer(); + ~OpenXRCompositionLayer(); +}; + +#endif // OPENXR_COMPOSITION_LAYER_H diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp new file mode 100644 index 00000000000..dcc9971ec96 --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp @@ -0,0 +1,190 @@ +/**************************************************************************/ +/* openxr_composition_layer_cylinder.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_composition_layer_cylinder.h" + +#include "../extensions/openxr_composition_layer_extension.h" +#include "../openxr_api.h" +#include "../openxr_interface.h" + +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/viewport.h" +#include "scene/resources/mesh.h" + +OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() { + composition_layer = { + XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + radius, // radius + central_angle, // centralAngle + aspect_ratio, // aspectRatio + }; + openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +} + +OpenXRCompositionLayerCylinder::~OpenXRCompositionLayerCylinder() { +} + +void OpenXRCompositionLayerCylinder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &OpenXRCompositionLayerCylinder::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &OpenXRCompositionLayerCylinder::get_radius); + + ClassDB::bind_method(D_METHOD("set_aspect_ratio", "aspect_ratio"), &OpenXRCompositionLayerCylinder::set_aspect_ratio); + ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &OpenXRCompositionLayerCylinder::get_aspect_ratio); + + ClassDB::bind_method(D_METHOD("set_central_angle", "angle"), &OpenXRCompositionLayerCylinder::set_central_angle); + ClassDB::bind_method(D_METHOD("get_central_angle"), &OpenXRCompositionLayerCylinder::get_central_angle); + + ClassDB::bind_method(D_METHOD("set_fallback_segments", "segments"), &OpenXRCompositionLayerCylinder::set_fallback_segments); + ClassDB::bind_method(D_METHOD("get_fallback_segments"), &OpenXRCompositionLayerCylinder::get_fallback_segments); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_NONE, ""), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "aspect_ratio", PROPERTY_HINT_RANGE, "0,100"), "set_aspect_ratio", "get_aspect_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "central_angle", PROPERTY_HINT_RANGE, "0,360,0.1,or_less,or_greater,radians_as_degrees"), "set_central_angle", "get_central_angle"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fallback_segments", PROPERTY_HINT_NONE, ""), "set_fallback_segments", "get_fallback_segments"); +} + +void OpenXRCompositionLayerCylinder::_on_openxr_session_begun() { + OpenXRCompositionLayer::_on_openxr_session_begun(); + if (openxr_api) { + composition_layer.space = openxr_api->get_play_space(); + } +} + +Ref OpenXRCompositionLayerCylinder::_create_fallback_mesh() { + Ref mesh; + mesh.instantiate(); + + float arc_length = radius * central_angle; + float half_height = ((1.0 / aspect_ratio) * arc_length) / 2.0; + + Array arrays; + arrays.resize(ArrayMesh::ARRAY_MAX); + + Vector vertices; + Vector normals; + Vector uvs; + Vector indices; + + float delta_angle = central_angle / fallback_segments; + float start_angle = (-Math_PI / 2.0) - (central_angle / 2.0); + + for (uint32_t i = 0; i < fallback_segments + 1; i++) { + float current_angle = start_angle + (delta_angle * i); + float x = radius * Math::cos(current_angle); + float z = radius * Math::sin(current_angle); + Vector3 normal(Math::cos(current_angle), 0, Math::sin(current_angle)); + + vertices.push_back(Vector3(x, -half_height, z)); + normals.push_back(normal); + uvs.push_back(Vector2((float)i / fallback_segments, 1)); + + vertices.push_back(Vector3(x, half_height, z)); + normals.push_back(normal); + uvs.push_back(Vector2((float)i / fallback_segments, 0)); + } + + for (uint32_t i = 0; i < fallback_segments; i++) { + uint32_t index = i * 2; + indices.push_back(index); + indices.push_back(index + 1); + indices.push_back(index + 3); + indices.push_back(index); + indices.push_back(index + 3); + indices.push_back(index + 2); + } + + arrays[ArrayMesh::ARRAY_VERTEX] = vertices; + arrays[ArrayMesh::ARRAY_NORMAL] = normals; + arrays[ArrayMesh::ARRAY_TEX_UV] = uvs; + arrays[ArrayMesh::ARRAY_INDEX] = indices; + + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); + return mesh; +} + +void OpenXRCompositionLayerCylinder::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + Transform3D transform = get_transform(); + Quaternion quat(transform.basis.orthonormalized()); + composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; + composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + } break; + } +} + +void OpenXRCompositionLayerCylinder::set_radius(float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + radius = p_radius; + composition_layer.radius = radius; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerCylinder::get_radius() const { + return radius; +} + +void OpenXRCompositionLayerCylinder::set_aspect_ratio(float p_aspect_ratio) { + ERR_FAIL_COND(p_aspect_ratio <= 0); + aspect_ratio = p_aspect_ratio; + composition_layer.aspectRatio = aspect_ratio; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerCylinder::get_aspect_ratio() const { + return aspect_ratio; +} + +void OpenXRCompositionLayerCylinder::set_central_angle(float p_central_angle) { + ERR_FAIL_COND(p_central_angle <= 0); + central_angle = p_central_angle; + composition_layer.centralAngle = central_angle; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerCylinder::get_central_angle() const { + return central_angle; +} + +void OpenXRCompositionLayerCylinder::set_fallback_segments(uint32_t p_fallback_segments) { + ERR_FAIL_COND(p_fallback_segments == 0); + fallback_segments = p_fallback_segments; + update_fallback_mesh(); +} + +uint32_t OpenXRCompositionLayerCylinder::get_fallback_segments() const { + return fallback_segments; +} diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h new file mode 100644 index 00000000000..dec7dac529c --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h @@ -0,0 +1,73 @@ +/**************************************************************************/ +/* openxr_composition_layer_cylinder.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_COMPOSITION_LAYER_CYLINDER_H +#define OPENXR_COMPOSITION_LAYER_CYLINDER_H + +#include + +#include "openxr_composition_layer.h" + +class OpenXRCompositionLayerCylinder : public OpenXRCompositionLayer { + GDCLASS(OpenXRCompositionLayerCylinder, OpenXRCompositionLayer); + + XrCompositionLayerCylinderKHR composition_layer; + + float radius = 1.0; + float aspect_ratio = 1.0; + float central_angle = Math_PI / 2.0; + uint32_t fallback_segments = 10; + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + virtual void _on_openxr_session_begun() override; + virtual Ref _create_fallback_mesh() override; + +public: + void set_radius(float p_radius); + float get_radius() const; + + void set_aspect_ratio(float p_aspect_ratio); + float get_aspect_ratio() const; + + void set_central_angle(float p_angle); + float get_central_angle() const; + + void set_fallback_segments(uint32_t p_fallback_segments); + uint32_t get_fallback_segments() const; + + OpenXRCompositionLayerCylinder(); + ~OpenXRCompositionLayerCylinder(); +}; + +#endif // OPENXR_COMPOSITION_LAYER_CYLINDER_H diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp new file mode 100644 index 00000000000..c911dbf6aee --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp @@ -0,0 +1,209 @@ +/**************************************************************************/ +/* openxr_composition_layer_equirect.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_composition_layer_equirect.h" + +#include "../extensions/openxr_composition_layer_extension.h" +#include "../openxr_api.h" +#include "../openxr_interface.h" + +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/viewport.h" +#include "scene/resources/mesh.h" + +OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() { + composition_layer = { + XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + radius, // radius + central_horizontal_angle, // centralHorizontalAngle + upper_vertical_angle, // upperVerticalAngle + -lower_vertical_angle, // lowerVerticalAngle + }; + openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +} + +OpenXRCompositionLayerEquirect::~OpenXRCompositionLayerEquirect() { +} + +void OpenXRCompositionLayerEquirect::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &OpenXRCompositionLayerEquirect::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &OpenXRCompositionLayerEquirect::get_radius); + + ClassDB::bind_method(D_METHOD("set_central_horizontal_angle", "angle"), &OpenXRCompositionLayerEquirect::set_central_horizontal_angle); + ClassDB::bind_method(D_METHOD("get_central_horizontal_angle"), &OpenXRCompositionLayerEquirect::get_central_horizontal_angle); + + ClassDB::bind_method(D_METHOD("set_upper_vertical_angle", "angle"), &OpenXRCompositionLayerEquirect::set_upper_vertical_angle); + ClassDB::bind_method(D_METHOD("get_upper_vertical_angle"), &OpenXRCompositionLayerEquirect::get_upper_vertical_angle); + + ClassDB::bind_method(D_METHOD("set_lower_vertical_angle", "angle"), &OpenXRCompositionLayerEquirect::set_lower_vertical_angle); + ClassDB::bind_method(D_METHOD("get_lower_vertical_angle"), &OpenXRCompositionLayerEquirect::get_lower_vertical_angle); + + ClassDB::bind_method(D_METHOD("set_fallback_segments", "segments"), &OpenXRCompositionLayerEquirect::set_fallback_segments); + ClassDB::bind_method(D_METHOD("get_fallback_segments"), &OpenXRCompositionLayerEquirect::get_fallback_segments); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_NONE, ""), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "central_horizontal_angle", PROPERTY_HINT_RANGE, "0,360,0.1,or_less,or_greater,radians_as_degrees"), "set_central_horizontal_angle", "get_central_horizontal_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "upper_vertical_angle", PROPERTY_HINT_RANGE, "0,90,0.1,or_less,or_greater,radians_as_degrees"), "set_upper_vertical_angle", "get_upper_vertical_angle"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lower_vertical_angle", PROPERTY_HINT_RANGE, "0,90,0.1,or_less,or_greater,radians_as_degrees"), "set_lower_vertical_angle", "get_lower_vertical_angle"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fallback_segments", PROPERTY_HINT_NONE, ""), "set_fallback_segments", "get_fallback_segments"); +} + +void OpenXRCompositionLayerEquirect::_on_openxr_session_begun() { + OpenXRCompositionLayer::_on_openxr_session_begun(); + if (openxr_api) { + composition_layer.space = openxr_api->get_play_space(); + } +} + +Ref OpenXRCompositionLayerEquirect::_create_fallback_mesh() { + Ref mesh; + mesh.instantiate(); + + Array arrays; + arrays.resize(ArrayMesh::ARRAY_MAX); + + Vector vertices; + Vector normals; + Vector uvs; + Vector indices; + + float step_horizontal = central_horizontal_angle / fallback_segments; + float step_vertical = (upper_vertical_angle + lower_vertical_angle) / fallback_segments; + + float start_horizontal_angle = Math_PI - (central_horizontal_angle / 2.0); + + for (uint32_t i = 0; i < fallback_segments + 1; i++) { + for (uint32_t j = 0; j < fallback_segments + 1; j++) { + float horizontal_angle = start_horizontal_angle + (step_horizontal * i); + float vertical_angle = -lower_vertical_angle + (step_vertical * j); + + Vector3 vertex( + radius * Math::cos(vertical_angle) * Math::sin(horizontal_angle), + radius * Math::sin(vertical_angle), + radius * Math::cos(vertical_angle) * Math::cos(horizontal_angle)); + + vertices.push_back(vertex); + normals.push_back(vertex.normalized()); + uvs.push_back(Vector2(1.0 - ((float)i / fallback_segments), 1.0 - (float(j) / fallback_segments))); + } + } + + for (uint32_t i = 0; i < fallback_segments; i++) { + for (uint32_t j = 0; j < fallback_segments; j++) { + uint32_t index = i * (fallback_segments + 1) + j; + indices.push_back(index); + indices.push_back(index + fallback_segments + 1); + indices.push_back(index + fallback_segments + 2); + + indices.push_back(index); + indices.push_back(index + fallback_segments + 2); + indices.push_back(index + 1); + } + } + + arrays[ArrayMesh::ARRAY_VERTEX] = vertices; + arrays[ArrayMesh::ARRAY_NORMAL] = normals; + arrays[ArrayMesh::ARRAY_TEX_UV] = uvs; + arrays[ArrayMesh::ARRAY_INDEX] = indices; + + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); + return mesh; +} + +void OpenXRCompositionLayerEquirect::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + Transform3D transform = get_transform(); + Quaternion quat(transform.basis.orthonormalized()); + composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; + composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + } break; + } +} + +void OpenXRCompositionLayerEquirect::set_radius(float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + radius = p_radius; + composition_layer.radius = radius; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerEquirect::get_radius() const { + return radius; +} + +void OpenXRCompositionLayerEquirect::set_central_horizontal_angle(float p_angle) { + ERR_FAIL_COND(p_angle <= 0); + central_horizontal_angle = p_angle; + composition_layer.centralHorizontalAngle = central_horizontal_angle; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerEquirect::get_central_horizontal_angle() const { + return central_horizontal_angle; +} + +void OpenXRCompositionLayerEquirect::set_upper_vertical_angle(float p_angle) { + ERR_FAIL_COND(p_angle <= 0 || p_angle > (Math_PI / 2.0)); + upper_vertical_angle = p_angle; + composition_layer.upperVerticalAngle = p_angle; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerEquirect::get_upper_vertical_angle() const { + return upper_vertical_angle; +} + +void OpenXRCompositionLayerEquirect::set_lower_vertical_angle(float p_angle) { + ERR_FAIL_COND(p_angle <= 0 || p_angle > (Math_PI / 2.0)); + lower_vertical_angle = p_angle; + composition_layer.lowerVerticalAngle = -p_angle; + update_fallback_mesh(); +} + +float OpenXRCompositionLayerEquirect::get_lower_vertical_angle() const { + return lower_vertical_angle; +} + +void OpenXRCompositionLayerEquirect::set_fallback_segments(uint32_t p_fallback_segments) { + ERR_FAIL_COND(p_fallback_segments == 0); + fallback_segments = p_fallback_segments; + update_fallback_mesh(); +} + +uint32_t OpenXRCompositionLayerEquirect::get_fallback_segments() const { + return fallback_segments; +} diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h new file mode 100644 index 00000000000..b74e131f35d --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_equirect.h @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* openxr_composition_layer_equirect.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_COMPOSITION_LAYER_EQUIRECT_H +#define OPENXR_COMPOSITION_LAYER_EQUIRECT_H + +#include + +#include "openxr_composition_layer.h" + +class OpenXRCompositionLayerEquirect : public OpenXRCompositionLayer { + GDCLASS(OpenXRCompositionLayerEquirect, OpenXRCompositionLayer); + + XrCompositionLayerEquirect2KHR composition_layer; + + float radius = 1.0; + float central_horizontal_angle = Math_PI / 2.0; + float upper_vertical_angle = Math_PI / 4.0; + float lower_vertical_angle = Math_PI / 4.0; + uint32_t fallback_segments = 10; + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + virtual void _on_openxr_session_begun() override; + virtual Ref _create_fallback_mesh() override; + +public: + void set_radius(float p_radius); + float get_radius() const; + + void set_central_horizontal_angle(float p_angle); + float get_central_horizontal_angle() const; + + void set_upper_vertical_angle(float p_angle); + float get_upper_vertical_angle() const; + + void set_lower_vertical_angle(float p_angle); + float get_lower_vertical_angle() const; + + void set_fallback_segments(uint32_t p_fallback_segments); + uint32_t get_fallback_segments() const; + + OpenXRCompositionLayerEquirect(); + ~OpenXRCompositionLayerEquirect(); +}; + +#endif // OPENXR_COMPOSITION_LAYER_EQUIRECT_H diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp new file mode 100644 index 00000000000..342df058082 --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* openxr_composition_layer_quad.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_composition_layer_quad.h" + +#include "../extensions/openxr_composition_layer_extension.h" +#include "../openxr_api.h" +#include "../openxr_interface.h" + +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/viewport.h" +#include "scene/resources/3d/primitive_meshes.h" + +OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() { + composition_layer = { + XR_TYPE_COMPOSITION_LAYER_QUAD, // type + nullptr, // next + 0, // layerFlags + XR_NULL_HANDLE, // space + XR_EYE_VISIBILITY_BOTH, // eyeVisibility + {}, // subImage + { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose + { (float)quad_size.x, (float)quad_size.y }, // size + }; + openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer)); +} + +OpenXRCompositionLayerQuad::~OpenXRCompositionLayerQuad() { +} + +void OpenXRCompositionLayerQuad::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_quad_size", "size"), &OpenXRCompositionLayerQuad::set_quad_size); + ClassDB::bind_method(D_METHOD("get_quad_size"), &OpenXRCompositionLayerQuad::get_quad_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "quad_size", PROPERTY_HINT_NONE, ""), "set_quad_size", "get_quad_size"); +} + +void OpenXRCompositionLayerQuad::_on_openxr_session_begun() { + OpenXRCompositionLayer::_on_openxr_session_begun(); + if (openxr_api) { + composition_layer.space = openxr_api->get_play_space(); + } +} + +Ref OpenXRCompositionLayerQuad::_create_fallback_mesh() { + Ref mesh; + mesh.instantiate(); + mesh->set_size(quad_size); + return mesh; +} + +void OpenXRCompositionLayerQuad::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + Transform3D transform = get_transform(); + Quaternion quat(transform.basis.orthonormalized()); + composition_layer.pose.orientation = { (float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w }; + composition_layer.pose.position = { (float)transform.origin.x, (float)transform.origin.y, (float)transform.origin.z }; + } break; + } +} + +void OpenXRCompositionLayerQuad::set_quad_size(const Size2 &p_size) { + quad_size = p_size; + composition_layer.size = { (float)quad_size.x, (float)quad_size.y }; + update_fallback_mesh(); +} + +Size2 OpenXRCompositionLayerQuad::get_quad_size() const { + return quad_size; +} diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h new file mode 100644 index 00000000000..e1141586c13 --- /dev/null +++ b/modules/openxr/scene/openxr_composition_layer_quad.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* openxr_composition_layer_quad.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_COMPOSITION_LAYER_QUAD_H +#define OPENXR_COMPOSITION_LAYER_QUAD_H + +#include + +#include "openxr_composition_layer.h" + +class OpenXRCompositionLayerQuad : public OpenXRCompositionLayer { + GDCLASS(OpenXRCompositionLayerQuad, OpenXRCompositionLayer); + + XrCompositionLayerQuad composition_layer; + + Size2 quad_size = Size2(1.0, 1.0); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + virtual void _on_openxr_session_begun() override; + virtual Ref _create_fallback_mesh() override; + +public: + void set_quad_size(const Size2 &p_size); + Size2 get_quad_size() const; + + OpenXRCompositionLayerQuad(); + ~OpenXRCompositionLayerQuad(); +}; + +#endif // OPENXR_COMPOSITION_LAYER_QUAD_H diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 4e1d2b39838..355b42ebbea 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -4486,6 +4486,10 @@ void Viewport::set_use_xr(bool p_use_xr) { } else { RS::get_singleton()->viewport_set_size(viewport, 0, 0); } + + // Reset render target override textures. + RID rt = RS::get_singleton()->viewport_get_render_target(viewport); + RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID()); } } } diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index bafabf16b85..2e57e0b8014 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -802,8 +802,6 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) { } else #endif // _3D_DISABLED { - RSG::texture_storage->render_target_set_override(vp->render_target, RID(), RID(), RID()); - RSG::scene->set_debug_draw_mode(vp->debug_draw); // render standard mono camera @@ -1062,6 +1060,13 @@ void RendererViewport::viewport_set_update_mode(RID p_viewport, RS::ViewportUpda viewport->update_mode = p_mode; } +RS::ViewportUpdateMode RendererViewport::viewport_get_update_mode(RID p_viewport) const { + Viewport *viewport = viewport_owner.get_or_null(p_viewport); + ERR_FAIL_NULL_V(viewport, RS::VIEWPORT_UPDATE_DISABLED); + + return viewport->update_mode; +} + RID RendererViewport::viewport_get_render_target(RID p_viewport) const { const Viewport *viewport = viewport_owner.get_or_null(p_viewport); ERR_FAIL_NULL_V(viewport, RID()); diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 90c29618b3b..5107398c54b 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -236,6 +236,7 @@ public: void viewport_set_texture_mipmap_bias(RID p_viewport, float p_mipmap_bias); void viewport_set_update_mode(RID p_viewport, RS::ViewportUpdateMode p_mode); + RS::ViewportUpdateMode viewport_get_update_mode(RID p_viewport) const; void viewport_set_vflip(RID p_viewport, bool p_enable); void viewport_set_clear_mode(RID p_viewport, RS::ViewportClearMode p_clear_mode); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 139624c777b..c50472c0cd7 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -625,6 +625,7 @@ public: FUNC2(viewport_set_texture_mipmap_bias, RID, float) FUNC2(viewport_set_update_mode, RID, ViewportUpdateMode) + FUNC1RC(ViewportUpdateMode, viewport_get_update_mode, RID) FUNC1RC(RID, viewport_get_render_target, RID) FUNC1RC(RID, viewport_get_texture, RID) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 994f6ad8b45..96d317ebd32 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2783,6 +2783,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("viewport_set_fsr_sharpness", "viewport", "sharpness"), &RenderingServer::viewport_set_fsr_sharpness); ClassDB::bind_method(D_METHOD("viewport_set_texture_mipmap_bias", "viewport", "mipmap_bias"), &RenderingServer::viewport_set_texture_mipmap_bias); ClassDB::bind_method(D_METHOD("viewport_set_update_mode", "viewport", "update_mode"), &RenderingServer::viewport_set_update_mode); + ClassDB::bind_method(D_METHOD("viewport_get_update_mode", "viewport"), &RenderingServer::viewport_get_update_mode); ClassDB::bind_method(D_METHOD("viewport_set_clear_mode", "viewport", "clear_mode"), &RenderingServer::viewport_set_clear_mode); ClassDB::bind_method(D_METHOD("viewport_get_render_target", "viewport"), &RenderingServer::viewport_get_render_target); ClassDB::bind_method(D_METHOD("viewport_get_texture", "viewport"), &RenderingServer::viewport_get_texture); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index a3a77bc57ba..8f0150f1804 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -873,6 +873,7 @@ public: }; virtual void viewport_set_update_mode(RID p_viewport, ViewportUpdateMode p_mode) = 0; + virtual ViewportUpdateMode viewport_get_update_mode(RID p_viewport) const = 0; enum ViewportClearMode { VIEWPORT_CLEAR_ALWAYS,