Merge pull request #96185 from dsnopek/openxr-composition-layer-android-surface

OpenXR: Support composition layers based on Android surfaces
This commit is contained in:
Rémi Verschelde 2024-09-12 09:25:23 +02:00
commit ea9ad8d6a1
No known key found for this signature in database
GPG key ID: C3336907360768E1
11 changed files with 462 additions and 154 deletions

View file

@ -10,6 +10,13 @@
<tutorials>
</tutorials>
<methods>
<method name="get_android_surface">
<return type="JavaObject" />
<description>
Returns a [JavaObject] representing an [code]android.view.Surface[/code] if [member use_android_surface] is enabled and OpenXR has created the surface. Otherwise, this will return [code]null[/code].
[b]Note:[/b] The surface can only be created during an active OpenXR session. So, if [member use_android_surface] is enabled outside of an OpenXR session, it won't be created until a new session fully starts.
</description>
</method>
<method name="intersects_ray" qualifiers="const">
<return type="Vector2" />
<param index="0" name="origin" type="Vector3" />
@ -32,6 +39,9 @@
Enables the blending the layer using its alpha channel.
Can be combined with [member Viewport.transparent_bg] to give the layer a transparent background.
</member>
<member name="android_surface_size" type="Vector2i" setter="set_android_surface_size" getter="get_android_surface_size" default="Vector2i(1024, 1024)">
The size of the Android surface to create if [member use_android_surface] is enabled.
</member>
<member name="enable_hole_punch" type="bool" setter="set_enable_hole_punch" getter="get_enable_hole_punch" default="false">
Enables a technique called "hole punching", which allows putting the composition layer behind the main projection layer (i.e. setting [member sort_order] to a negative value) while "punching a hole" through everything rendered by Godot so that the layer is still visible.
This can be used to create the illusion that the composition layer exists in the same 3D space as everything rendered by Godot, allowing objects to appear to pass both behind or in front of the composition layer.
@ -43,5 +53,10 @@
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.
</member>
<member name="use_android_surface" type="bool" setter="set_use_android_surface" getter="get_use_android_surface" default="false">
If enabled, an Android surface will be created (with the dimensions from [member android_surface_size]) which will provide the 2D content for the composition layer, rather than using [member layer_viewport].
See [method get_android_surface] for information about how to get the surface so that your application can draw to it.
[b]Note:[/b] This will only work in Android builds.
</member>
</members>
</class>

View file

@ -30,6 +30,12 @@
#include "openxr_composition_layer_extension.h"
#ifdef ANDROID_ENABLED
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#endif
#include "platform/android/api/java_class_wrapper.h"
#include "servers/rendering/rendering_server_globals.h"
////////////////////////////////////////////////////////////////////////////
@ -55,18 +61,37 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension
request_extensions[XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME] = &cylinder_ext_available;
request_extensions[XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME] = &equirect_ext_available;
#ifdef ANDROID_ENABLED
request_extensions[XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME] = &android_surface_ext_available;
#endif
return request_extensions;
}
void OpenXRCompositionLayerExtension::on_instance_created(const XrInstance p_instance) {
#ifdef ANDROID_ENABLED
EXT_INIT_XR_FUNC(xrDestroySwapchain);
EXT_INIT_XR_FUNC(xrCreateSwapchainAndroidSurfaceKHR);
#endif
}
void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) {
OpenXRAPI::get_singleton()->register_composition_layer_provider(this);
}
void OpenXRCompositionLayerExtension::on_session_destroyed() {
OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this);
#ifdef ANDROID_ENABLED
free_queued_android_surface_swapchains();
#endif
}
void OpenXRCompositionLayerExtension::on_pre_render() {
#ifdef ANDROID_ENABLED
free_queued_android_surface_swapchains();
#endif
for (OpenXRViewportCompositionLayerProvider *composition_layer : composition_layers) {
composition_layer->on_pre_render();
}
@ -113,6 +138,37 @@ bool OpenXRCompositionLayerExtension::is_available(XrStructureType p_which) {
}
}
#ifdef ANDROID_ENABLED
bool OpenXRCompositionLayerExtension::create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface) {
if (android_surface_ext_available) {
OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
ERR_FAIL_NULL_V(openxr_api, false);
// @todo We need a way to add to the next pointer chain.
XrResult result = xrCreateSwapchainAndroidSurfaceKHR(openxr_api->get_session(), p_info, r_swapchain, r_surface);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to create Android surface swapchain [", openxr_api->get_error_string(result), "]");
return false;
}
return true;
}
return false;
}
void OpenXRCompositionLayerExtension::free_android_surface_swapchain(XrSwapchain p_swapchain) {
android_surface_swapchain_free_queue.push_back(p_swapchain);
}
void OpenXRCompositionLayerExtension::free_queued_android_surface_swapchains() {
for (XrSwapchain swapchain : android_surface_swapchain_free_queue) {
xrDestroySwapchain(swapchain);
}
android_surface_swapchain_free_queue.clear();
}
#endif
////////////////////////////////////////////////////////////////////////////
// OpenXRViewportCompositionLayerProvider
@ -127,8 +183,12 @@ OpenXRViewportCompositionLayerProvider::~OpenXRViewportCompositionLayerProvider(
extension->on_viewport_composition_layer_destroyed(composition_layer);
}
// This will reset the viewport and free the swapchain too.
set_viewport(RID(), Size2i());
if (use_android_surface) {
free_swapchain();
} else {
// This will reset the viewport and free the swapchain too.
set_viewport(RID(), Size2i());
}
}
void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) {
@ -143,42 +203,119 @@ void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend)
}
void OpenXRViewportCompositionLayerProvider::set_viewport(RID p_viewport, Size2i p_size) {
ERR_FAIL_COND(use_android_surface);
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);
if (subviewport.viewport != p_viewport) {
if (subviewport.viewport.is_valid()) {
RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID());
}
viewport = p_viewport;
subviewport.viewport = p_viewport;
if (viewport.is_valid()) {
viewport_size = p_size;
if (subviewport.viewport.is_valid()) {
subviewport.viewport_size = p_size;
} else {
free_swapchain();
viewport_size = Size2i();
subviewport.viewport_size = Size2i();
}
}
}
void OpenXRViewportCompositionLayerProvider::set_use_android_surface(bool p_use_android_surface, Size2i p_size) {
#ifdef ANDROID_ENABLED
if (p_use_android_surface == use_android_surface) {
return;
}
use_android_surface = p_use_android_surface;
if (use_android_surface) {
if (!composition_layer_extension->is_android_surface_swapchain_available()) {
ERR_PRINT_ONCE("OpenXR: Cannot use Android surface for composition layer because the extension isn't available");
}
if (subviewport.viewport.is_valid()) {
set_viewport(RID(), Size2i());
}
swapchain_size = p_size;
} else {
free_swapchain();
}
#endif
}
#ifdef ANDROID_ENABLED
void OpenXRViewportCompositionLayerProvider::create_android_surface() {
ERR_FAIL_COND(android_surface.swapchain != XR_NULL_HANDLE || android_surface.surface.is_valid());
ERR_FAIL_COND(!openxr_api || !openxr_api->is_running());
// The XR_FB_android_surface_swapchain_create extension mandates that format, sampleCount,
// faceCount, arraySize, and mipCount must be zero.
XrSwapchainCreateInfo info = {
XR_TYPE_SWAPCHAIN_CREATE_INFO, // type
nullptr, // next
0, // createFlags
XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, // usageFlags
0, // format
0, // sampleCount
(uint32_t)swapchain_size.x, // width
(uint32_t)swapchain_size.y, // height
0, // faceCount
0, // arraySize
0, // mipCount
};
jobject surface;
composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface);
if (surface) {
android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface)));
}
}
#endif
Ref<JavaObject> OpenXRViewportCompositionLayerProvider::get_android_surface() {
#ifdef ANDROID_ENABLED
if (use_android_surface) {
if (android_surface.surface.is_null()) {
create_android_surface();
}
return android_surface.surface;
}
#endif
return Ref<JavaObject>();
}
void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const Dictionary &p_extension_property_values) {
extension_property_values = p_extension_property_values;
extension_property_values_changed = true;
}
void OpenXRViewportCompositionLayerProvider::on_pre_render() {
#ifdef ANDROID_ENABLED
if (use_android_surface) {
if (android_surface.surface.is_null()) {
create_android_surface();
}
return;
}
#endif
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 (subviewport.viewport.is_valid() && openxr_api && openxr_api->is_running()) {
RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(subviewport.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);
RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, get_current_swapchain_texture(), RID(), RID());
}
}
@ -196,48 +333,36 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return nullptr;
}
if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) {
XrSwapchainSubImage subimage = {
0, // swapchain
{ { 0, 0 }, { 0, 0 } }, // imageRect
0, // imageArrayIndex
};
update_swapchain_sub_image(subimage);
if (subimage.swapchain == XR_NULL_HANDLE) {
// Don't have a swapchain to display? Ignore our layer.
return nullptr;
}
if (swapchain_info.is_image_acquired()) {
swapchain_info.release();
}
// 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->space = openxr_api->get_play_space();
quad_layer->subImage.swapchain = swapchain_info.get_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;
quad_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: {
XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer;
cylinder_layer->space = openxr_api->get_play_space();
cylinder_layer->subImage.swapchain = swapchain_info.get_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;
cylinder_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: {
XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer;
equirect_layer->space = openxr_api->get_play_space();
equirect_layer->subImage.swapchain = swapchain_info.get_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;
equirect_layer->subImage = subimage;
} break;
default: {
@ -261,27 +386,49 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return composition_layer;
}
void OpenXRViewportCompositionLayerProvider::update_swapchain_sub_image(XrSwapchainSubImage &r_subimage) {
#ifdef ANDROID_ENABLED
if (use_android_surface) {
r_subimage.swapchain = android_surface.swapchain;
} else
#endif
{
XrSwapchain swapchain = subviewport.swapchain_info.get_swapchain();
if (swapchain && subviewport.swapchain_info.is_image_acquired()) {
subviewport.swapchain_info.release();
}
r_subimage.swapchain = swapchain;
}
r_subimage.imageRect.extent.width = swapchain_size.width;
r_subimage.imageRect.extent.height = swapchain_size.height;
}
bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p_static_image) {
ERR_FAIL_COND_V(use_android_surface, false);
if (openxr_api == nullptr || composition_layer_extension == nullptr) {
// OpenXR not initialized or we're in the editor?
return false;
}
if (!composition_layer_extension->is_available(composition_layer->type)) {
if (!composition_layer_extension->is_available(get_openxr_type())) {
// Selected type is not supported?
return false;
}
// See if our current swapchain is outdated.
if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
if (subviewport.swapchain_info.get_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) {
if (swapchain_size == subviewport.viewport_size && !p_static_image && !subviewport.static_image) {
// We're all good! Just acquire it.
// We can ignore should_render here, return will be false.
bool should_render = true;
return swapchain_info.acquire(should_render);
return subviewport.swapchain_info.acquire(should_render);
}
swapchain_info.queue_free();
subviewport.swapchain_info.queue_free();
}
// Create our new swap chain
@ -292,7 +439,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
if (p_static_image) {
create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT;
}
if (!swapchain_info.create(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)) {
if (!subviewport.swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, subviewport.viewport_size.width, subviewport.viewport_size.height, sample_count, array_size)) {
swapchain_size = Size2i();
return false;
}
@ -300,26 +447,40 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
// Acquire our image so we can start rendering into it,
// we can ignore should_render here, ret will be false.
bool should_render = true;
bool ret = swapchain_info.acquire(should_render);
bool ret = subviewport.swapchain_info.acquire(should_render);
swapchain_size = viewport_size;
static_image = p_static_image;
swapchain_size = subviewport.viewport_size;
subviewport.static_image = p_static_image;
return ret;
}
void OpenXRViewportCompositionLayerProvider::free_swapchain() {
if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
swapchain_info.queue_free();
#ifdef ANDROID_ENABLED
if (use_android_surface) {
if (android_surface.swapchain != XR_NULL_HANDLE) {
composition_layer_extension->free_android_surface_swapchain(android_surface.swapchain);
android_surface.swapchain = XR_NULL_HANDLE;
android_surface.surface.unref();
}
} else
#endif
{
if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
subviewport.swapchain_info.queue_free();
}
subviewport.static_image = false;
}
swapchain_size = Size2i();
static_image = false;
}
RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() {
ERR_FAIL_COND_V(use_android_surface, RID());
if (openxr_api == nullptr) {
return RID();
}
return swapchain_info.get_image();
return subviewport.swapchain_info.get_image();
}

View file

@ -36,6 +36,15 @@
#include "../openxr_api.h"
#ifdef ANDROID_ENABLED
#include <jni.h>
// Copied here from openxr_platform.h, in order to avoid including that whole header,
// which can cause compilation issues on some platforms.
typedef XrResult(XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo *info, XrSwapchain *swapchain, jobject *surface);
#endif
class JavaObject;
class OpenXRViewportCompositionLayerProvider;
// This extension provides access to composition layers for displaying 2D content through the XR compositor.
@ -49,6 +58,7 @@ public:
virtual ~OpenXRCompositionLayerExtension() override;
virtual HashMap<String, bool *> get_requested_extensions() override;
virtual void on_instance_created(const XrInstance p_instance) override;
virtual void on_session_created(const XrSession p_session) override;
virtual void on_session_destroyed() override;
virtual void on_pre_render() override;
@ -61,6 +71,12 @@ public:
void unregister_viewport_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer);
bool is_available(XrStructureType p_which);
bool is_android_surface_swapchain_available() { return android_surface_ext_available; }
#ifdef ANDROID_ENABLED
bool create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface);
void free_android_surface_swapchain(XrSwapchain p_swapchain);
#endif
private:
static OpenXRCompositionLayerExtension *singleton;
@ -69,6 +85,15 @@ private:
bool cylinder_ext_available = false;
bool equirect_ext_available = false;
bool android_surface_ext_available = false;
#ifdef ANDROID_ENABLED
Vector<XrSwapchain> android_surface_swapchain_free_queue;
void free_queued_android_surface_swapchains();
EXT_PROTO_XRRESULT_FUNC1(xrDestroySwapchain, (XrSwapchain), swapchain)
EXT_PROTO_XRRESULT_FUNC4(xrCreateSwapchainAndroidSurfaceKHR, (XrSession), session, (const XrSwapchainCreateInfo *), info, (XrSwapchain *), swapchain, (jobject *), surface)
#endif
};
class OpenXRViewportCompositionLayerProvider {
@ -78,20 +103,37 @@ class OpenXRViewportCompositionLayerProvider {
Dictionary extension_property_values;
bool extension_property_values_changed = true;
RID viewport;
Size2i viewport_size;
struct {
RID viewport;
Size2i viewport_size;
OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
bool static_image = false;
} subviewport;
OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
#ifdef ANDROID_ENABLED
struct {
XrSwapchain swapchain = XR_NULL_HANDLE;
Ref<JavaObject> surface;
} android_surface;
#endif
bool use_android_surface = false;
Size2i swapchain_size;
bool static_image = false;
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
// Only for SubViewports.
bool update_and_acquire_swapchain(bool p_static_image);
void free_swapchain();
RID get_current_swapchain_texture();
void update_swapchain_sub_image(XrSwapchainSubImage &r_swapchain_sub_image);
void free_swapchain();
#ifdef ANDROID_ENABLED
void create_android_surface();
#endif
public:
XrStructureType get_openxr_type() { return composition_layer->type; }
@ -102,7 +144,12 @@ public:
bool get_alpha_blend() const { return alpha_blend; }
void set_viewport(RID p_viewport, Size2i p_size);
RID get_viewport() const { return viewport; }
RID get_viewport() const { return subviewport.viewport; }
void set_use_android_surface(bool p_enable, Size2i p_size);
bool get_use_android_surface() const { return use_android_surface; }
Ref<JavaObject> get_android_surface();
void set_extension_property_values(const Dictionary &p_property_values);

View file

@ -38,6 +38,8 @@
#include "scene/3d/xr_nodes.h"
#include "scene/main/viewport.h"
#include "platform/android/api/java_class_wrapper.h"
Vector<OpenXRCompositionLayer *> OpenXRCompositionLayer::composition_layer_nodes;
static const char *HOLE_PUNCH_SHADER_CODE =
@ -47,7 +49,10 @@ static const char *HOLE_PUNCH_SHADER_CODE =
"\tALBEDO = vec3(0.0, 0.0, 0.0);\n"
"}\n";
OpenXRCompositionLayer::OpenXRCompositionLayer() {
OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer) {
composition_layer_base_header = p_composition_layer;
openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider(composition_layer_base_header));
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
@ -85,6 +90,12 @@ 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_use_android_surface", "enable"), &OpenXRCompositionLayer::set_use_android_surface);
ClassDB::bind_method(D_METHOD("get_use_android_surface"), &OpenXRCompositionLayer::get_use_android_surface);
ClassDB::bind_method(D_METHOD("set_android_surface_size", "size"), &OpenXRCompositionLayer::set_android_surface_size);
ClassDB::bind_method(D_METHOD("get_android_surface_size"), &OpenXRCompositionLayer::get_android_surface_size);
ClassDB::bind_method(D_METHOD("set_enable_hole_punch", "enable"), &OpenXRCompositionLayer::set_enable_hole_punch);
ClassDB::bind_method(D_METHOD("get_enable_hole_punch"), &OpenXRCompositionLayer::get_enable_hole_punch);
@ -94,11 +105,14 @@ void OpenXRCompositionLayer::_bind_methods() {
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("get_android_surface"), &OpenXRCompositionLayer::get_android_surface);
ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported);
ClassDB::bind_method(D_METHOD("intersects_ray", "origin", "direction"), &OpenXRCompositionLayer::intersects_ray);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_android_surface", PROPERTY_HINT_NONE, ""), "set_use_android_surface", "get_use_android_surface");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "android_surface_size", PROPERTY_HINT_NONE, ""), "set_android_surface_size", "get_android_surface_size");
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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_hole_punch", PROPERTY_HINT_NONE, ""), "set_enable_hole_punch", "get_enable_hole_punch");
@ -108,7 +122,7 @@ bool OpenXRCompositionLayer::_should_use_fallback_node() {
if (Engine::get_singleton()->is_editor_hint()) {
return true;
} else if (openxr_session_running) {
return enable_hole_punch || !is_natively_supported();
return enable_hole_punch || (!is_natively_supported() && !use_android_surface);
}
return false;
}
@ -128,10 +142,36 @@ void OpenXRCompositionLayer::_remove_fallback_node() {
fallback = nullptr;
}
void OpenXRCompositionLayer::_setup_composition_layer_provider() {
if (use_android_surface || layer_viewport) {
if (composition_layer_extension) {
composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
}
// NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
if (layer_viewport) {
// Set our properties on the layer provider, which will create all the necessary resources (ex swap chains).
openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
}
}
}
void OpenXRCompositionLayer::_clear_composition_layer_provider() {
if (composition_layer_extension) {
composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
}
// NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
if (!use_android_surface) {
// This will reset the viewport and free all the resources (ex swap chains) used by the layer.
openxr_layer_provider->set_viewport(RID(), Size2i());
}
}
void OpenXRCompositionLayer::_on_openxr_session_begun() {
openxr_session_running = true;
if (layer_viewport && is_natively_supported() && is_visible() && is_inside_tree()) {
openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
if (is_natively_supported() && is_visible() && is_inside_tree()) {
_setup_composition_layer_provider();
}
if (!fallback && _should_use_fallback_node()) {
_create_fallback_node();
@ -142,9 +182,8 @@ void OpenXRCompositionLayer::_on_openxr_session_stopping() {
openxr_session_running = false;
if (fallback && !_should_use_fallback_node()) {
_remove_fallback_node();
} else {
openxr_layer_provider->set_viewport(RID(), Size2i());
}
_clear_composition_layer_provider();
}
void OpenXRCompositionLayer::update_fallback_mesh() {
@ -162,6 +201,7 @@ XrPosef OpenXRCompositionLayer::get_openxr_pose() {
}
bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) {
ERR_FAIL_NULL_V(p_viewport, false);
for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) {
if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) {
return true;
@ -178,6 +218,9 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
if (p_viewport != nullptr) {
ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first."));
}
if (use_android_surface) {
ERR_FAIL_COND_MSG(p_viewport != nullptr, RTR("Cannot set SubViewport on an OpenXR composition layer when using an Android surface."));
}
layer_viewport = p_viewport;
@ -200,6 +243,41 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
}
}
void OpenXRCompositionLayer::set_use_android_surface(bool p_use_android_surface) {
if (use_android_surface == p_use_android_surface) {
return;
}
use_android_surface = p_use_android_surface;
if (use_android_surface) {
set_layer_viewport(nullptr);
openxr_layer_provider->set_use_android_surface(true, android_surface_size);
} else {
openxr_layer_provider->set_use_android_surface(false, Size2i());
}
notify_property_list_changed();
}
bool OpenXRCompositionLayer::get_use_android_surface() const {
return use_android_surface;
}
void OpenXRCompositionLayer::set_android_surface_size(Size2i p_size) {
if (android_surface_size == p_size) {
return;
}
android_surface_size = p_size;
if (use_android_surface) {
openxr_layer_provider->set_use_android_surface(true, android_surface_size);
}
}
Size2i OpenXRCompositionLayer::get_android_surface_size() const {
return android_surface_size;
}
SubViewport *OpenXRCompositionLayer::get_layer_viewport() const {
return layer_viewport;
}
@ -228,33 +306,23 @@ bool OpenXRCompositionLayer::get_enable_hole_punch() const {
}
void OpenXRCompositionLayer::set_sort_order(int p_order) {
if (openxr_layer_provider) {
openxr_layer_provider->set_sort_order(p_order);
update_configuration_warnings();
}
openxr_layer_provider->set_sort_order(p_order);
update_configuration_warnings();
}
int OpenXRCompositionLayer::get_sort_order() const {
if (openxr_layer_provider) {
return openxr_layer_provider->get_sort_order();
}
return 1;
return openxr_layer_provider->get_sort_order();
}
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();
}
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;
return openxr_layer_provider->get_alpha_blend();
}
bool OpenXRCompositionLayer::is_natively_supported() const {
@ -264,6 +332,10 @@ bool OpenXRCompositionLayer::is_natively_supported() const {
return false;
}
Ref<JavaObject> OpenXRCompositionLayer::get_android_surface() {
return openxr_layer_provider->get_android_surface();
}
Vector2 OpenXRCompositionLayer::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const {
return Vector2(-1.0, -1.0);
}
@ -301,10 +373,7 @@ void OpenXRCompositionLayer::_reset_fallback_material() {
Ref<ViewportTexture> 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<Resource>, Ref<Resource>> remap_cache;
texture->configure_for_local_scene(this, remap_cache);
texture = layer_viewport->get_texture();
}
Node *loc_scene = texture->get_local_scene();
@ -321,12 +390,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
case NOTIFICATION_POSTINITIALIZE: {
composition_layer_nodes.push_back(this);
if (openxr_layer_provider) {
for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
}
openxr_layer_provider->set_extension_property_values(extension_property_values);
for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
}
openxr_layer_provider->set_extension_property_values(extension_property_values);
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (fallback) {
@ -339,10 +406,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!fallback && openxr_session_running && is_inside_tree()) {
if (layer_viewport && is_visible()) {
openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
if (is_visible()) {
_setup_composition_layer_provider();
} else {
openxr_layer_provider->set_viewport(RID(), Size2i());
_clear_composition_layer_provider();
}
}
update_configuration_warnings();
@ -351,25 +418,15 @@ void OpenXRCompositionLayer::_notification(int p_what) {
update_configuration_warnings();
} break;
case NOTIFICATION_ENTER_TREE: {
if (composition_layer_extension) {
composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
}
if (is_viewport_in_use(layer_viewport)) {
set_layer_viewport(nullptr);
} else if (!fallback && layer_viewport && openxr_session_running && is_visible()) {
openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
if (layer_viewport && is_viewport_in_use(layer_viewport)) {
_clear_composition_layer_provider();
} else if (openxr_session_running && is_visible()) {
_setup_composition_layer_provider();
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (composition_layer_extension) {
composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
}
if (!fallback) {
// This will clean up existing resources.
openxr_layer_provider->set_viewport(RID(), Size2i());
}
// This will clean up existing resources.
_clear_composition_layer_provider();
} break;
}
}
@ -401,13 +458,27 @@ bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value
bool OpenXRCompositionLayer::_set(const StringName &p_property, const Variant &p_value) {
extension_property_values[p_property] = p_value;
if (openxr_layer_provider) {
openxr_layer_provider->set_extension_property_values(extension_property_values);
}
openxr_layer_provider->set_extension_property_values(extension_property_values);
return true;
}
void OpenXRCompositionLayer::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "layer_viewport") {
if (use_android_surface) {
p_property.usage &= ~PROPERTY_USAGE_EDITOR;
} else {
p_property.usage |= PROPERTY_USAGE_EDITOR;
}
} else if (p_property.name == "android_surface_size") {
if (use_android_surface) {
p_property.usage |= PROPERTY_USAGE_EDITOR;
} else {
p_property.usage &= ~PROPERTY_USAGE_EDITOR;
}
}
}
PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();

View file

@ -35,6 +35,7 @@
#include "scene/3d/node_3d.h"
class JavaObject;
class MeshInstance3D;
class Mesh;
class OpenXRAPI;
@ -45,7 +46,12 @@ class SubViewport;
class OpenXRCompositionLayer : public Node3D {
GDCLASS(OpenXRCompositionLayer, Node3D);
XrCompositionLayerBaseHeader *composition_layer_base_header = nullptr;
OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
SubViewport *layer_viewport = nullptr;
bool use_android_surface = false;
Size2i android_surface_size = Size2i(1024, 1024);
bool enable_hole_punch = false;
MeshInstance3D *fallback = nullptr;
bool should_update_fallback_mesh = false;
@ -58,10 +64,12 @@ class OpenXRCompositionLayer : public Node3D {
void _reset_fallback_material();
void _remove_fallback_node();
void _setup_composition_layer_provider();
void _clear_composition_layer_provider();
protected:
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
static void _bind_methods();
@ -69,6 +77,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_property_list) const;
bool _get(const StringName &p_property, Variant &r_value) const;
bool _set(const StringName &p_property, const Variant &p_value);
void _validate_property(PropertyInfo &p_property) const;
virtual void _on_openxr_session_begun();
virtual void _on_openxr_session_stopping();
@ -82,10 +91,18 @@ protected:
static Vector<OpenXRCompositionLayer *> composition_layer_nodes;
bool is_viewport_in_use(SubViewport *p_viewport);
OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer);
public:
void set_layer_viewport(SubViewport *p_viewport);
SubViewport *get_layer_viewport() const;
void set_use_android_surface(bool p_use_android_surface);
bool get_use_android_surface() const;
void set_android_surface_size(Size2i p_size);
Size2i get_android_surface_size() const;
void set_enable_hole_punch(bool p_enable);
bool get_enable_hole_punch() const;
@ -95,13 +112,13 @@ public:
void set_alpha_blend(bool p_alpha_blend);
bool get_alpha_blend() const;
Ref<JavaObject> get_android_surface();
bool is_natively_supported() const;
virtual PackedStringArray get_configuration_warnings() const override;
virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const;
OpenXRCompositionLayer();
~OpenXRCompositionLayer();
};

View file

@ -38,20 +38,8 @@
#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() :
OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerCylinder::update_transform));
}

View file

@ -38,7 +38,18 @@
class OpenXRCompositionLayerCylinder : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerCylinder, OpenXRCompositionLayer);
XrCompositionLayerCylinderKHR composition_layer;
XrCompositionLayerCylinderKHR 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
1.0, // radius
Math_PI / 2.0, // centralAngle
1.0, // aspectRatio
};
float radius = 1.0;
float aspect_ratio = 1.0;

View file

@ -38,21 +38,8 @@
#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() :
OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerEquirect::update_transform));
}

View file

@ -38,7 +38,19 @@
class OpenXRCompositionLayerEquirect : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerEquirect, OpenXRCompositionLayer);
XrCompositionLayerEquirect2KHR composition_layer;
XrCompositionLayerEquirect2KHR 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
1.0, // radius
Math_PI / 2.0, // centralHorizontalAngle
Math_PI / 4.0, // upperVerticalAngle
-Math_PI / 4.0, // lowerVerticalAngle
};
float radius = 1.0;
float central_horizontal_angle = Math_PI / 2.0;

View file

@ -38,18 +38,8 @@
#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() :
OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerQuad::update_transform));
}

View file

@ -38,7 +38,16 @@
class OpenXRCompositionLayerQuad : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerQuad, OpenXRCompositionLayer);
XrCompositionLayerQuad composition_layer;
XrCompositionLayerQuad 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
{ 1.0, 1.0 }, // size
};
Size2 quad_size = Size2(1.0, 1.0);