Support composition layers anywhere in the tree

This commit is contained in:
Fredia Huya-Kouadio 2025-12-24 13:07:39 -08:00 committed by Fredia Huya-Kouadio
parent 923c751af4
commit b389d580ac
4 changed files with 59 additions and 21 deletions

View file

@ -165,6 +165,11 @@
Emitted when an existing tracker has been updated. This can happen if the user switches controllers.
</description>
</signal>
<signal name="world_origin_changed">
<description>
Emitted when the world origin transform changes.
</description>
</signal>
</signals>
<constants>
<constant name="TRACKER_HEAD" value="1" enum="TrackerType">

View file

@ -64,9 +64,9 @@ OpenXRCompositionLayer::OpenXRCompositionLayer() {
}
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayer::update_transform));
XRServer::get_singleton()->connect("world_origin_changed", callable_mp(this, &OpenXRCompositionLayer::update_transform));
set_process_internal(true);
set_notify_local_transform(true);
if (Engine::get_singleton()->is_editor_hint()) {
// In the editor, create the fallback right away.
@ -204,7 +204,7 @@ bool OpenXRCompositionLayer::_should_use_fallback_node() {
if (Engine::get_singleton()->is_editor_hint() || openxr_api == nullptr) {
return true;
} else if (openxr_session_running) {
return enable_hole_punch || (!is_natively_supported() && !use_android_surface);
return enable_hole_punch || !is_natively_supported();
}
return false;
}
@ -218,7 +218,7 @@ void OpenXRCompositionLayer::_create_fallback_node() {
}
void OpenXRCompositionLayer::_remove_fallback_node() {
ERR_FAIL_COND(fallback != nullptr);
ERR_FAIL_COND(fallback == nullptr);
remove_child(fallback);
fallback->queue_free();
fallback = nullptr;
@ -253,7 +253,7 @@ void OpenXRCompositionLayer::_clear_composition_layer() {
}
void OpenXRCompositionLayer::_viewport_size_changed() {
if (layer_viewport && openxr_session_running && composition_layer_extension && is_natively_supported() && is_visible() && is_inside_tree()) {
if (layer_viewport && openxr_session_running && composition_layer_extension && is_natively_supported() && is_visible_in_tree() && is_inside_tree()) {
composition_layer_extension->composition_layer_set_viewport(composition_layer, layer_viewport->get_viewport_rid(), layer_viewport->get_size());
}
}
@ -278,19 +278,32 @@ void OpenXRCompositionLayer::_on_openxr_session_stopping() {
void OpenXRCompositionLayer::update_transform() {
if (composition_layer_extension) {
XRCamera3D *ancestor_camera = _get_xrcamera3d_ancestor();
bool parent_is_xr_camera = Object::cast_to<XRCamera3D>(get_parent()) != nullptr;
bool parent_is_xr_origin = Object::cast_to<XROrigin3D>(get_parent()) != nullptr;
OpenXRCompositionLayerExtension::PoseSpace new_pose_space;
// Automatically set the PoseSpace to POSE_HEAD_LOCKED if layer is a child of XRCamera3D.
if (parent_is_xr_camera) {
// Automatically set the PoseSpace to POSE_HEAD_LOCKED if layer has an XRCamera3D ancestor.
if (ancestor_camera) {
new_pose_space = OpenXRCompositionLayerExtension::PoseSpace::POSE_HEAD_LOCKED;
} else {
new_pose_space = OpenXRCompositionLayerExtension::PoseSpace::POSE_WORLD_LOCKED;
}
Transform3D xf;
if (parent_is_xr_origin || parent_is_xr_camera) {
xf = get_transform();
} else {
if (ancestor_camera) {
xf = ancestor_camera->get_global_transform().affine_inverse() * get_global_transform();
} else {
xf = XRServer::get_singleton()->get_world_origin().affine_inverse() * get_global_transform();
}
}
// Pose space must be set first, as composition_layer_set_transform() depends on it.
composition_layer_extension->composition_layer_set_pose_space(composition_layer, new_pose_space);
composition_layer_extension->composition_layer_set_transform(composition_layer, get_transform());
composition_layer_extension->composition_layer_set_transform(composition_layer, xf);
}
}
@ -299,7 +312,7 @@ void OpenXRCompositionLayer::update_fallback_mesh() {
}
bool OpenXRCompositionLayer::_should_register() {
return !registered && openxr_session_running && is_inside_tree() && is_visible() && is_natively_supported();
return !registered && openxr_session_running && is_inside_tree() && is_visible_in_tree() && is_natively_supported();
}
bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) {
@ -345,7 +358,8 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
if (fallback) {
_reset_fallback_material();
} else if (openxr_session_running && composition_layer_extension && is_visible() && is_inside_tree()) {
}
if (openxr_session_running && composition_layer_extension && is_visible_in_tree() && is_inside_tree() && is_natively_supported()) {
if (layer_viewport) {
composition_layer_extension->composition_layer_set_viewport(composition_layer, layer_viewport->get_viewport_rid(), layer_viewport->get_size());
} else {
@ -710,7 +724,7 @@ void OpenXRCompositionLayer::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_natively_supported() && openxr_session_running && is_inside_tree()) {
if (is_visible()) {
if (is_visible_in_tree()) {
_setup_composition_layer();
update_transform();
} else {
@ -719,19 +733,28 @@ void OpenXRCompositionLayer::_notification(int p_what) {
}
update_configuration_warnings();
} break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
update_transform();
update_configuration_warnings();
} break;
case NOTIFICATION_ENTER_TREE: {
if (layer_viewport && is_viewport_in_use(layer_viewport)) {
_clear_composition_layer();
} else if (openxr_session_running && is_visible()) {
} else if (openxr_session_running && is_visible_in_tree()) {
_setup_composition_layer();
}
update_transform();
} break;
case NOTIFICATION_PARENTED: {
// Enables NOTIFICATION_LOCAL_TRANSFORM_CHANGED when XROrigin3D or XRCamera3D are the parents
// since that notification happens less frequently than NOTIFICATION_TRANSFORM_CHANGED.
bool parent_is_xr_camera = Object::cast_to<XRCamera3D>(get_parent()) != nullptr;
bool parent_is_xr_origin = Object::cast_to<XROrigin3D>(get_parent()) != nullptr;
bool enable_local_transform_notification = parent_is_xr_camera || parent_is_xr_origin;
set_notify_local_transform(enable_local_transform_notification);
set_notify_transform(!enable_local_transform_notification);
update_transform();
} break;
case NOTIFICATION_EXIT_TREE: {
@ -795,18 +818,22 @@ void OpenXRCompositionLayer::_validate_property(PropertyInfo &p_property) const
}
}
XRCamera3D *OpenXRCompositionLayer::_get_xrcamera3d_ancestor() const {
Node *parent = get_parent();
while (parent != nullptr) {
XRCamera3D *camera = Object::cast_to<XRCamera3D>(parent);
if (camera != nullptr) {
return camera;
}
parent = parent->get_parent();
}
return nullptr;
}
PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
XROrigin3D *xr_origin = Object::cast_to<XROrigin3D>(get_parent());
XRCamera3D *xr_camera = Object::cast_to<XRCamera3D>(get_parent());
if (xr_origin == nullptr && xr_camera == nullptr) {
warnings.push_back(RTR("OpenXR composition layers must have have either an XROrigin3D or XRCamera3D 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)."));
}

View file

@ -40,6 +40,7 @@ class Mesh;
class OpenXRAPI;
class OpenXRCompositionLayerExtension;
class SubViewport;
class XRCamera3D;
class OpenXRCompositionLayer : public Node3D {
GDCLASS(OpenXRCompositionLayer, Node3D);
@ -125,6 +126,8 @@ private:
void _viewport_size_changed();
XRCamera3D *_get_xrcamera3d_ancestor() const;
protected:
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;

View file

@ -107,6 +107,8 @@ void XRServer::_bind_methods() {
ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("world_origin_changed"));
}
double XRServer::get_world_scale() const {
@ -157,6 +159,7 @@ Transform3D XRServer::get_world_origin() const {
void XRServer::set_world_origin(const Transform3D &p_world_origin) {
world_origin = p_world_origin;
emit_signal(SNAME("world_origin_changed"));
set_render_world_origin(world_origin);
}