Browse Source

Merge pull request #42282 from andriyDev/DeleteYSort

Remove YSort node, move its functionality into Node2D
Rémi Verschelde 4 years ago
parent
commit
79d679fd8e

+ 4 - 0
doc/classes/Node2D.xml

@@ -150,6 +150,10 @@
 		<member name="transform" type="Transform2D" setter="set_transform" getter="get_transform">
 			Local [Transform2D].
 		</member>
+		<member name="y_sort_enabled" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false">
+			If [code]true[/code], child nodes with the lowest Y position are drawn before those with a higher Y position. If [code]false[/code], Y-sorting is disabled. Y-sorting only affects children that inherit from [CanvasItem].
+			You can nest nodes with Y-sorting. Child Y-sorted nodes are sorted in the same space as the parent Y-sort. This feature allows you to organize a scene better or divide it into multiple ones without changing your scene tree.
+		</member>
 		<member name="z_as_relative" type="bool" setter="set_z_as_relative" getter="is_z_relative" default="true">
 			If [code]true[/code], the node's Z index is relative to its parent's Z index. If this node's Z index is 2 and its parent's effective Z index is 3, then this node's effective Z index will be 2 + 3 = 5.
 		</member>

+ 0 - 21
doc/classes/YSort.xml

@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="YSort" inherits="Node2D" version="4.0">
-	<brief_description>
-		Sort all child nodes based on their Y positions.
-	</brief_description>
-	<description>
-		Sort all child nodes based on their Y positions. The child node must inherit from [CanvasItem] for it to be sorted. Nodes that have a higher Y position will be drawn later, so they will appear on top of nodes that have a lower Y position.
-		Nesting of YSort nodes is possible. Children YSort nodes will be sorted in the same space as the parent YSort, allowing to better organize a scene or divide it in multiple ones, yet keep the unique sorting.
-	</description>
-	<tutorials>
-	</tutorials>
-	<methods>
-	</methods>
-	<members>
-		<member name="sort_enabled" type="bool" setter="set_sort_enabled" getter="is_sort_enabled" default="true">
-			If [code]true[/code], child nodes are sorted, otherwise sorting is disabled.
-		</member>
-	</members>
-	<constants>
-	</constants>
-</class>

+ 14 - 1
scene/2d/node_2d.cpp

@@ -391,6 +391,15 @@ Point2 Node2D::to_global(Point2 p_local) const {
 	return get_global_transform().xform(p_local);
 }
 
+void Node2D::set_y_sort_enabled(bool p_enabled) {
+	y_sort_enabled = p_enabled;
+	RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_enabled);
+}
+
+bool Node2D::is_y_sort_enabled() const {
+	return y_sort_enabled;
+}
+
 void Node2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_position", "position"), &Node2D::set_position);
 	ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Node2D::set_rotation);
@@ -437,6 +446,9 @@ void Node2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_z_as_relative", "enable"), &Node2D::set_z_as_relative);
 	ClassDB::bind_method(D_METHOD("is_z_relative"), &Node2D::is_z_relative);
 
+	ClassDB::bind_method(D_METHOD("set_y_sort_enabled", "enabled"), &Node2D::set_y_sort_enabled);
+	ClassDB::bind_method(D_METHOD("is_y_sort_enabled"), &Node2D::is_y_sort_enabled);
+
 	ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent);
 
 	ADD_GROUP("Transform", "");
@@ -454,7 +466,8 @@ void Node2D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", 0), "set_global_scale", "get_global_scale");
 	ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", 0), "set_global_transform", "get_global_transform");
 
-	ADD_GROUP("Z Index", "");
+	ADD_GROUP("Ordering", "");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_index", "get_z_index");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "z_as_relative"), "set_z_as_relative", "is_z_relative");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "y_sort_enabled"), "set_y_sort_enabled", "is_y_sort_enabled");
 }

+ 4 - 0
scene/2d/node_2d.h

@@ -42,6 +42,7 @@ class Node2D : public CanvasItem {
 	real_t skew = 0.0;
 	int z_index = 0;
 	bool z_relative = true;
+	bool y_sort_enabled = false;
 
 	Transform2D _mat;
 
@@ -117,6 +118,9 @@ public:
 	void set_z_as_relative(bool p_enabled);
 	bool is_z_relative() const;
 
+	virtual void set_y_sort_enabled(bool p_enabled);
+	virtual bool is_y_sort_enabled() const;
+
 	Transform2D get_relative_transform_to_parent(const Node *p_parent) const;
 
 	Transform2D get_transform() const override;

+ 6 - 0
scene/2d/tile_map.cpp

@@ -334,6 +334,12 @@ TileMap::VisibilityMode TileMap::get_navigation_visibility_mode() {
 	return show_navigation;
 }
 
+void TileMap::set_y_sort_enabled(bool p_enable) {
+	Node2D::set_y_sort_enabled(p_enable);
+	_recreate_quadrants();
+	emit_signal("changed");
+}
+
 void TileMap::update_dirty_quadrants() {
 	if (!pending_update) {
 		return;

+ 1 - 0
scene/2d/tile_map.h

@@ -279,6 +279,7 @@ public:
 	int get_effective_quadrant_size() const;
 
 	void update_dirty_quadrants();
+	virtual void set_y_sort_enabled(bool p_enable) override;
 
 	Vector2 map_to_world(const Vector2i &p_pos) const;
 	Vector2i world_to_map(const Vector2 &p_pos) const;

+ 0 - 52
scene/2d/y_sort.cpp

@@ -1,52 +0,0 @@
-/*************************************************************************/
-/*  y_sort.cpp                                                           */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#include "y_sort.h"
-
-void YSort::set_sort_enabled(bool p_enabled) {
-	sort_enabled = p_enabled;
-	RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), sort_enabled);
-}
-
-bool YSort::is_sort_enabled() const {
-	return sort_enabled;
-}
-
-void YSort::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("set_sort_enabled", "enabled"), &YSort::set_sort_enabled);
-	ClassDB::bind_method(D_METHOD("is_sort_enabled"), &YSort::is_sort_enabled);
-
-	ADD_GROUP("Sort", "sort_");
-	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sort_enabled"), "set_sort_enabled", "is_sort_enabled");
-}
-
-YSort::YSort() {
-	RS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), true);
-}

+ 0 - 47
scene/2d/y_sort.h

@@ -1,47 +0,0 @@
-/*************************************************************************/
-/*  y_sort.h                                                             */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#ifndef Y_SORT_H
-#define Y_SORT_H
-
-#include "scene/2d/node_2d.h"
-
-class YSort : public Node2D {
-	GDCLASS(YSort, Node2D);
-	bool sort_enabled = true;
-	static void _bind_methods();
-
-public:
-	void set_sort_enabled(bool p_enabled);
-	bool is_sort_enabled() const;
-	YSort();
-};
-
-#endif // Y_SORT_H

+ 1 - 2
scene/register_scene_types.cpp

@@ -65,7 +65,6 @@
 #include "scene/2d/tile_map.h"
 #include "scene/2d/touch_screen_button.h"
 #include "scene/2d/visibility_notifier_2d.h"
-#include "scene/2d/y_sort.h"
 #include "scene/animation/animation_blend_space_1d.h"
 #include "scene/animation/animation_blend_space_2d.h"
 #include "scene/animation/animation_blend_tree.h"
@@ -643,7 +642,6 @@ void register_scene_types() {
 	ClassDB::register_class<DirectionalLight2D>();
 	ClassDB::register_class<LightOccluder2D>();
 	ClassDB::register_class<OccluderPolygon2D>();
-	ClassDB::register_class<YSort>();
 	ClassDB::register_class<BackBufferCopy>();
 
 	OS::get_singleton()->yield(); //may take time to init
@@ -820,6 +818,7 @@ void register_scene_types() {
 	ClassDB::add_compatibility_class("ToolButton", "Button");
 	ClassDB::add_compatibility_class("Navigation3D", "Node3D");
 	ClassDB::add_compatibility_class("Navigation2D", "Node2D");
+	ClassDB::add_compatibility_class("YSort", "Node2D");
 
 	// Renamed in 4.0.
 	// Keep alphabetical ordering to easily locate classes and avoid duplicates.

+ 2 - 2
scene/resources/tile_set.cpp

@@ -3870,8 +3870,8 @@ void TileSetPluginAtlasRendering::tilemap_notification(TileMap *p_tile_map, int
 		} break;
 		case CanvasItem::NOTIFICATION_DRAW: {
 			Ref<TileSet> tile_set = p_tile_map->get_tileset();
-			if (tile_set.is_valid()) {
-				RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(p_tile_map->get_canvas_item(), tile_set->is_y_sorting());
+			if (tile_set.is_valid() || p_tile_map->is_y_sort_enabled()) {
+				RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(p_tile_map->get_canvas_item(), tile_set->is_y_sorting() || p_tile_map->is_y_sort_enabled());
 			}
 		} break;
 	}

+ 112 - 101
servers/rendering/renderer_canvas_cull.cpp

@@ -44,10 +44,10 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
 	memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
 
 	for (int i = 0; i < p_child_item_count; i++) {
-		_cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr);
+		_cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true);
 	}
 	if (p_canvas_item) {
-		_cull_canvas_item(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr);
+		_cull_canvas_item(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true);
 	}
 
 	RendererCanvasRender::Item *list = nullptr;
@@ -104,98 +104,7 @@ void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_PtrOwner<Rende
 	} while (ysort_owner && ysort_owner->sort_y);
 }
 
-void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **z_list, RendererCanvasRender::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner) {
-	Item *ci = p_canvas_item;
-
-	if (!ci->visible) {
-		return;
-	}
-
-	if (ci->children_order_dirty) {
-		ci->child_items.sort_custom<ItemIndexSort>();
-		ci->children_order_dirty = false;
-	}
-
-	Rect2 rect = ci->get_rect();
-	Transform2D xform = ci->xform;
-	if (snapping_2d_transforms_to_pixel) {
-		xform.elements[2] = xform.elements[2].floor();
-	}
-	xform = p_transform * xform;
-
-	Rect2 global_rect = xform.xform(rect);
-	global_rect.position += p_clip_rect.position;
-
-	if (ci->use_parent_material && p_material_owner) {
-		ci->material_owner = p_material_owner;
-	} else {
-		p_material_owner = ci;
-		ci->material_owner = nullptr;
-	}
-
-	Color modulate(ci->modulate.r * p_modulate.r, ci->modulate.g * p_modulate.g, ci->modulate.b * p_modulate.b, ci->modulate.a * p_modulate.a);
-
-	if (modulate.a < 0.007) {
-		return;
-	}
-
-	int child_item_count = ci->child_items.size();
-	Item **child_items = ci->child_items.ptrw();
-
-	if (ci->clip) {
-		if (p_canvas_clip != nullptr) {
-			ci->final_clip_rect = p_canvas_clip->final_clip_rect.intersection(global_rect);
-		} else {
-			ci->final_clip_rect = global_rect;
-		}
-		ci->final_clip_rect.position = ci->final_clip_rect.position.round();
-		ci->final_clip_rect.size = ci->final_clip_rect.size.round();
-		ci->final_clip_owner = ci;
-
-	} else {
-		ci->final_clip_owner = p_canvas_clip;
-	}
-
-	if (ci->sort_y) {
-		if (ci->ysort_children_count == -1) {
-			ci->ysort_children_count = 0;
-			_collect_ysort_children(ci, Transform2D(), p_material_owner, nullptr, ci->ysort_children_count);
-		}
-
-		child_item_count = ci->ysort_children_count;
-		child_items = (Item **)alloca(child_item_count * sizeof(Item *));
-
-		int i = 0;
-		_collect_ysort_children(ci, Transform2D(), p_material_owner, child_items, i);
-
-		SortArray<Item *, ItemPtrSort> sorter;
-		sorter.sort(child_items, child_item_count);
-	}
-
-	if (ci->z_relative) {
-		p_z = CLAMP(p_z + ci->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
-	} else {
-		p_z = ci->z_index;
-	}
-
-	RendererCanvasRender::Item *canvas_group_from = nullptr;
-	bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->commands != nullptr);
-	if (use_canvas_group) {
-		int zidx = p_z - RS::CANVAS_ITEM_Z_MIN;
-		canvas_group_from = z_last_list[zidx];
-	}
-
-	for (int i = 0; i < child_item_count; i++) {
-		if ((!child_items[i]->behind && !use_canvas_group) || (ci->sort_y && child_items[i]->sort_y)) {
-			continue;
-		}
-		if (ci->sort_y) {
-			_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
-		} else {
-			_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
-		}
-	}
-
+void _attach_canvas_item_for_draw(RendererCanvasCull::Item *ci, RendererCanvasCull::Item *p_canvas_clip, RendererCanvasRender::Item **z_list, RendererCanvasRender::Item **z_last_list, const Transform2D &xform, const Rect2 &p_clip_rect, Rect2 global_rect, const Color &modulate, int p_z, RendererCanvasCull::Item *p_material_owner, bool use_canvas_group, RendererCanvasRender::Item *canvas_group_from) {
 	if (ci->copy_back_buffer) {
 		ci->copy_back_buffer->screen_rect = xform.xform(ci->copy_back_buffer->rect).intersection(p_clip_rect);
 	}
@@ -229,7 +138,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 			// We have two choices now, if user has drawn something, we must assume users wants to draw the "mask", so compute the size based on this.
 			// If nothing has been drawn, we just take it over and draw it ourselves.
 			if (ci->canvas_group->fit_empty && (ci->commands == nullptr ||
-													   (ci->commands->next == nullptr && ci->commands->type == Item::Command::TYPE_RECT && (static_cast<Item::CommandRect *>(ci->commands)->flags & RendererCanvasRender::CANVAS_RECT_IS_GROUP)))) {
+													   (ci->commands->next == nullptr && ci->commands->type == RendererCanvasCull::Item::Command::TYPE_RECT && (static_cast<RendererCanvasCull::Item::CommandRect *>(ci->commands)->flags & RendererCanvasRender::CANVAS_RECT_IS_GROUP)))) {
 				// No commands, or sole command is the one used to draw, so we (re)create the draw command.
 				ci->clear();
 
@@ -291,15 +200,117 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 
 		ci->next = nullptr;
 	}
+}
 
-	for (int i = 0; i < child_item_count; i++) {
-		if (child_items[i]->behind || use_canvas_group || (ci->sort_y && child_items[i]->sort_y)) {
-			continue;
+void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **z_list, RendererCanvasRender::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort) {
+	Item *ci = p_canvas_item;
+
+	if (!ci->visible) {
+		return;
+	}
+
+	if (ci->children_order_dirty) {
+		ci->child_items.sort_custom<ItemIndexSort>();
+		ci->children_order_dirty = false;
+	}
+
+	Rect2 rect = ci->get_rect();
+	Transform2D xform = ci->xform;
+	if (snapping_2d_transforms_to_pixel) {
+		xform.elements[2] = xform.elements[2].floor();
+	}
+	xform = p_transform * xform;
+
+	Rect2 global_rect = xform.xform(rect);
+	global_rect.position += p_clip_rect.position;
+
+	if (ci->use_parent_material && p_material_owner) {
+		ci->material_owner = p_material_owner;
+	} else {
+		p_material_owner = ci;
+		ci->material_owner = nullptr;
+	}
+
+	Color modulate(ci->modulate.r * p_modulate.r, ci->modulate.g * p_modulate.g, ci->modulate.b * p_modulate.b, ci->modulate.a * p_modulate.a);
+
+	if (modulate.a < 0.007) {
+		return;
+	}
+
+	int child_item_count = ci->child_items.size();
+	Item **child_items = ci->child_items.ptrw();
+
+	if (ci->clip) {
+		if (p_canvas_clip != nullptr) {
+			ci->final_clip_rect = p_canvas_clip->final_clip_rect.intersection(global_rect);
+		} else {
+			ci->final_clip_rect = global_rect;
 		}
-		if (ci->sort_y) {
-			_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner);
+		ci->final_clip_rect.position = ci->final_clip_rect.position.round();
+		ci->final_clip_rect.size = ci->final_clip_rect.size.round();
+		ci->final_clip_owner = ci;
+
+	} else {
+		ci->final_clip_owner = p_canvas_clip;
+	}
+
+	if (ci->z_relative) {
+		p_z = CLAMP(p_z + ci->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
+	} else {
+		p_z = ci->z_index;
+	}
+
+	if (ci->sort_y) {
+		if (allow_y_sort) {
+			if (ci->ysort_children_count == -1) {
+				ci->ysort_children_count = 0;
+				_collect_ysort_children(ci, Transform2D(), p_material_owner, nullptr, ci->ysort_children_count);
+			}
+
+			child_item_count = ci->ysort_children_count + 1;
+			child_items = (Item **)alloca(child_item_count * sizeof(Item *));
+
+			child_items[0] = ci;
+			int i = 1;
+			_collect_ysort_children(ci, Transform2D(), p_material_owner, child_items, i);
+			ci->ysort_xform = ci->xform.affine_inverse();
+
+			SortArray<Item *, ItemPtrSort> sorter;
+			sorter.sort(child_items, child_item_count);
+
+			for (i = 0; i < child_item_count; i++) {
+				_cull_canvas_item(child_items[i], xform * child_items[i]->ysort_xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false);
+			}
 		} else {
-			_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner);
+			RendererCanvasRender::Item *canvas_group_from = nullptr;
+			bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->commands != nullptr);
+			if (use_canvas_group) {
+				int zidx = p_z - RS::CANVAS_ITEM_Z_MIN;
+				canvas_group_from = z_last_list[zidx];
+			}
+
+			_attach_canvas_item_for_draw(ci, p_canvas_clip, z_list, z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+		}
+	} else {
+		RendererCanvasRender::Item *canvas_group_from = nullptr;
+		bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->commands != nullptr);
+		if (use_canvas_group) {
+			int zidx = p_z - RS::CANVAS_ITEM_Z_MIN;
+			canvas_group_from = z_last_list[zidx];
+		}
+
+		for (int i = 0; i < child_item_count; i++) {
+			if (!child_items[i]->behind && !use_canvas_group) {
+				continue;
+			}
+			_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true);
+		}
+		_attach_canvas_item_for_draw(ci, p_canvas_clip, z_list, z_last_list, xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
+		for (int i = 0; i < child_item_count; i++) {
+			if (child_items[i]->behind || use_canvas_group) {
+				continue;
+			}
+			_cull_canvas_item(child_items[i], xform, p_clip_rect, modulate, p_z, z_list, z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true);
 		}
 	}
 }

+ 1 - 1
servers/rendering/renderer_canvas_cull.h

@@ -158,7 +158,7 @@ public:
 
 private:
 	void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel);
-	void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **z_list, RendererCanvasRender::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner);
+	void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **z_list, RendererCanvasRender::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort);
 
 	RendererCanvasRender::Item **z_list;
 	RendererCanvasRender::Item **z_last_list;