Browse Source

Merge pull request #49632 from reduz/refactor-visibility-notifier-2d

Refactor VisibilityNotifier2D
Rémi Verschelde 4 years ago
parent
commit
ad28a03b4b

+ 0 - 3
doc/classes/ProjectSettings.xml

@@ -1616,9 +1616,6 @@
 		<member name="rendering/xr/enabled" type="bool" setter="" getter="" default="false">
 			If [code]true[/code], XR support is enabled in Godot, this ensures required shaders are compiled.
 		</member>
-		<member name="world/2d/cell_size" type="int" setter="" getter="" default="100">
-			Cell size used for the 2D hash grid that [VisibilityNotifier2D] uses (in pixels).
-		</member>
 	</members>
 	<constants>
 	</constants>

+ 5 - 59
doc/classes/VisibilityEnabler2D.xml

@@ -1,79 +1,25 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="VisibilityEnabler2D" inherits="VisibilityNotifier2D" version="4.0">
 	<brief_description>
-		Enables certain nodes only when approximately visible.
 	</brief_description>
 	<description>
-		The VisibilityEnabler2D will disable [RigidBody2D], [AnimationPlayer], and other nodes when they are not visible. It will only affect nodes with the same root node as the VisibilityEnabler2D, and the root node itself.
-		If you just want to receive notifications, use [VisibilityNotifier2D] instead.
-		[b]Note:[/b] For performance reasons, VisibilityEnabler2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need precise visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
-		[b]Note:[/b] VisibilityEnabler2D will not affect nodes added after scene initialization.
 	</description>
 	<tutorials>
 	</tutorials>
 	<methods>
-		<method name="is_enabler_enabled" qualifiers="const">
-			<return type="bool">
-			</return>
-			<argument index="0" name="enabler" type="int" enum="VisibilityEnabler2D.Enabler">
-			</argument>
-			<description>
-				Returns whether the enabler identified by given [enum Enabler] constant is active.
-			</description>
-		</method>
-		<method name="set_enabler">
-			<return type="void">
-			</return>
-			<argument index="0" name="enabler" type="int" enum="VisibilityEnabler2D.Enabler">
-			</argument>
-			<argument index="1" name="enabled" type="bool">
-			</argument>
-			<description>
-				Sets active state of the enabler identified by given [enum Enabler] constant.
-			</description>
-		</method>
 	</methods>
 	<members>
-		<member name="freeze_bodies" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="true">
-			If [code]true[/code], [RigidBody2D] nodes will be paused.
+		<member name="enable_mode" type="int" setter="set_enable_mode" getter="get_enable_mode" enum="VisibilityEnabler2D.EnableMode" default="0">
 		</member>
-		<member name="pause_animated_sprites" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="true">
-			If [code]true[/code], [AnimatedSprite2D] nodes will be paused.
-		</member>
-		<member name="pause_animations" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="true">
-			If [code]true[/code], [AnimationPlayer] nodes will be paused.
-		</member>
-		<member name="pause_particles" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="true">
-			If [code]true[/code], [GPUParticles2D] nodes will be paused.
-		</member>
-		<member name="physics_process_parent" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="false">
-			If [code]true[/code], the parent's [method Node._physics_process] will be stopped.
-		</member>
-		<member name="process_parent" type="bool" setter="set_enabler" getter="is_enabler_enabled" default="false">
-			If [code]true[/code], the parent's [method Node._process] will be stopped.
+		<member name="enable_node_path" type="NodePath" setter="set_enable_node_path" getter="get_enable_node_path" default="NodePath(&quot;..&quot;)">
 		</member>
 	</members>
 	<constants>
-		<constant name="ENABLER_PAUSE_ANIMATIONS" value="0" enum="Enabler">
-			This enabler will pause [AnimationPlayer] nodes.
-		</constant>
-		<constant name="ENABLER_FREEZE_BODIES" value="1" enum="Enabler">
-			This enabler will freeze [RigidBody2D] nodes.
-		</constant>
-		<constant name="ENABLER_PAUSE_PARTICLES" value="2" enum="Enabler">
-			This enabler will stop [GPUParticles2D] nodes.
-		</constant>
-		<constant name="ENABLER_PARENT_PROCESS" value="3" enum="Enabler">
-			This enabler will stop the parent's _process function.
-		</constant>
-		<constant name="ENABLER_PARENT_PHYSICS_PROCESS" value="4" enum="Enabler">
-			This enabler will stop the parent's _physics_process function.
+		<constant name="ENABLE_MODE_INHERIT" value="0" enum="EnableMode">
 		</constant>
-		<constant name="ENABLER_PAUSE_ANIMATED_SPRITES" value="5" enum="Enabler">
-			This enabler will stop [AnimatedSprite2D] nodes animations.
+		<constant name="ENABLE_MODE_ALWAYS" value="1" enum="EnableMode">
 		</constant>
-		<constant name="ENABLER_MAX" value="6" enum="Enabler">
-			Represents the size of the [enum Enabler] enum.
+		<constant name="ENABLE_MODE_WHEN_PAUSED" value="2" enum="EnableMode">
 		</constant>
 	</constants>
 </class>

+ 1 - 16
doc/classes/VisibilityNotifier2D.xml

@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="VisibilityNotifier2D" inherits="Node2D" version="4.0">
 	<brief_description>
-		Detects approximately when the node is visible on screen.
+		Detects when the node extents are visible on screen.
 	</brief_description>
 	<description>
 		The VisibilityNotifier2D detects when it is visible on the screen. It also notifies when its bounding rectangle enters or exits the screen or a viewport.
 		If you want nodes to be disabled automatically when they exit the screen, use [VisibilityEnabler2D] instead.
-		[b]Note:[/b] For performance reasons, VisibilityNotifier2D uses an approximate heuristic with precision determined by [member ProjectSettings.world/2d/cell_size]. If you need precise visibility checking, use another method such as adding an [Area2D] node as a child of a [Camera2D] node.
 	</description>
 	<tutorials>
 		<link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link>
@@ -37,20 +36,6 @@
 				Emitted when the VisibilityNotifier2D exits the screen.
 			</description>
 		</signal>
-		<signal name="viewport_entered">
-			<argument index="0" name="viewport" type="Viewport">
-			</argument>
-			<description>
-				Emitted when the VisibilityNotifier2D enters a [Viewport]'s view.
-			</description>
-		</signal>
-		<signal name="viewport_exited">
-			<argument index="0" name="viewport" type="Viewport">
-			</argument>
-			<description>
-				Emitted when the VisibilityNotifier2D exits a [Viewport]'s view.
-			</description>
-		</signal>
 	</signals>
 	<constants>
 	</constants>

+ 3 - 3
scene/2d/audio_stream_player_2d.cpp

@@ -170,7 +170,6 @@ void AudioStreamPlayer2D::_notification(int p_what) {
 		//update anything related to position first, if possible of course
 
 		if (!output_ready.is_set()) {
-			List<Viewport *> viewports;
 			Ref<World2D> world_2d = get_world_2d();
 			ERR_FAIL_COND(world_2d.is_null());
 
@@ -203,8 +202,9 @@ void AudioStreamPlayer2D::_notification(int p_what) {
 				break;
 			}
 
-			world_2d->get_viewport_list(&viewports);
-			for (List<Viewport *>::Element *E = viewports.front(); E; E = E->next()) {
+			const Set<Viewport *> viewports = world_2d->get_viewports();
+
+			for (Set<Viewport *>::Element *E = viewports.front(); E; E = E->next()) {
 				Viewport *vp = E->get();
 				if (vp->is_audio_listener_2d()) {
 					//compute matrix to convert to screen

+ 76 - 224
scene/2d/visibility_notifier_2d.cpp

@@ -48,46 +48,29 @@ bool VisibilityNotifier2D::_edit_use_rect() const {
 }
 #endif
 
-void VisibilityNotifier2D::_enter_viewport(Viewport *p_viewport) {
-	ERR_FAIL_COND(viewports.has(p_viewport));
-	viewports.insert(p_viewport);
-
-	if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
+void VisibilityNotifier2D::_visibility_enter() {
+	if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) {
 		return;
 	}
 
-	if (viewports.size() == 1) {
-		emit_signal(SceneStringNames::get_singleton()->screen_entered);
-
-		_screen_enter();
-	}
-	emit_signal(SceneStringNames::get_singleton()->viewport_entered, p_viewport);
+	on_screen = true;
+	emit_signal(SceneStringNames::get_singleton()->screen_entered);
+	_screen_enter();
 }
-
-void VisibilityNotifier2D::_exit_viewport(Viewport *p_viewport) {
-	ERR_FAIL_COND(!viewports.has(p_viewport));
-	viewports.erase(p_viewport);
-
-	if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
+void VisibilityNotifier2D::_visibility_exit() {
+	if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) {
 		return;
 	}
 
-	emit_signal(SceneStringNames::get_singleton()->viewport_exited, p_viewport);
-	if (viewports.size() == 0) {
-		emit_signal(SceneStringNames::get_singleton()->screen_exited);
-
-		_screen_exit();
-	}
+	on_screen = false;
+	emit_signal(SceneStringNames::get_singleton()->screen_exited);
+	_screen_exit();
 }
 
 void VisibilityNotifier2D::set_rect(const Rect2 &p_rect) {
 	rect = p_rect;
 	if (is_inside_tree()) {
-		get_world_2d()->_update_notifier(this, get_global_transform().xform(rect));
-		if (Engine::get_singleton()->is_editor_hint()) {
-			update();
-			item_rect_changed();
-		}
+		RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), true, rect, callable_mp(this, &VisibilityNotifier2D::_visibility_enter), callable_mp(this, &VisibilityNotifier2D::_visibility_exit));
 	}
 }
 
@@ -99,11 +82,8 @@ void VisibilityNotifier2D::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			//get_world_2d()->
-			get_world_2d()->_register_notifier(this, get_global_transform().xform(rect));
-		} break;
-		case NOTIFICATION_TRANSFORM_CHANGED: {
-			//get_world_2d()->
-			get_world_2d()->_update_notifier(this, get_global_transform().xform(rect));
+			on_screen = false;
+			RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), true, rect, callable_mp(this, &VisibilityNotifier2D::_visibility_enter), callable_mp(this, &VisibilityNotifier2D::_visibility_exit));
 		} break;
 		case NOTIFICATION_DRAW: {
 			if (Engine::get_singleton()->is_editor_hint()) {
@@ -111,13 +91,14 @@ void VisibilityNotifier2D::_notification(int p_what) {
 			}
 		} break;
 		case NOTIFICATION_EXIT_TREE: {
-			get_world_2d()->_remove_notifier(this);
+			on_screen = false;
+			RS::get_singleton()->canvas_item_set_visibility_notifier(get_canvas_item(), false, Rect2(), Callable(), Callable());
 		} break;
 	}
 }
 
 bool VisibilityNotifier2D::is_on_screen() const {
-	return viewports.size() > 0;
+	return on_screen;
 }
 
 void VisibilityNotifier2D::_bind_methods() {
@@ -127,235 +108,106 @@ void VisibilityNotifier2D::_bind_methods() {
 
 	ADD_PROPERTY(PropertyInfo(Variant::RECT2, "rect"), "set_rect", "get_rect");
 
-	ADD_SIGNAL(MethodInfo("viewport_entered", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport")));
-	ADD_SIGNAL(MethodInfo("viewport_exited", PropertyInfo(Variant::OBJECT, "viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport")));
 	ADD_SIGNAL(MethodInfo("screen_entered"));
 	ADD_SIGNAL(MethodInfo("screen_exited"));
 }
 
 VisibilityNotifier2D::VisibilityNotifier2D() {
 	rect = Rect2(-10, -10, 20, 20);
-	set_notify_transform(true);
 }
 
 //////////////////////////////////////
 
 void VisibilityEnabler2D::_screen_enter() {
-	for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) {
-		_change_node_state(E->key(), true);
-	}
-
-	if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) {
-		get_parent()->set_physics_process(true);
-	}
-	if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) {
-		get_parent()->set_process(true);
-	}
-
-	visible = true;
+	_update_enable_mode(true);
 }
 
 void VisibilityEnabler2D::_screen_exit() {
-	for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) {
-		_change_node_state(E->key(), false);
-	}
-
-	if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) {
-		get_parent()->set_physics_process(false);
-	}
-	if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) {
-		get_parent()->set_process(false);
-	}
-
-	visible = false;
+	_update_enable_mode(false);
 }
 
-void VisibilityEnabler2D::_find_nodes(Node *p_node) {
-	bool add = false;
-	Variant meta;
-
-	{
-		RigidBody2D *rb2d = Object::cast_to<RigidBody2D>(p_node);
-		if (rb2d && ((rb2d->get_mode() == RigidBody2D::MODE_DYNAMIC || rb2d->get_mode() == RigidBody2D::MODE_DYNAMIC_LOCKED))) {
-			add = true;
-			meta = rb2d->get_mode();
-		}
-	}
-
-	{
-		AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
-		if (ap) {
-			add = true;
-		}
-	}
-
-	{
-		AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node);
-		if (as) {
-			add = true;
-		}
-	}
-
-	{
-		GPUParticles2D *ps = Object::cast_to<GPUParticles2D>(p_node);
-		if (ps) {
-			add = true;
-		}
+void VisibilityEnabler2D::set_enable_mode(EnableMode p_mode) {
+	enable_mode = p_mode;
+	if (is_inside_tree()) {
+		_update_enable_mode(is_on_screen());
 	}
+}
+VisibilityEnabler2D::EnableMode VisibilityEnabler2D::get_enable_mode() {
+	return enable_mode;
+}
 
-	if (add) {
-		p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &VisibilityEnabler2D::_node_removed), varray(p_node), CONNECT_ONESHOT);
-		nodes[p_node] = meta;
-		_change_node_state(p_node, false);
+void VisibilityEnabler2D::set_enable_node_path(NodePath p_path) {
+	if (enable_node_path == p_path) {
+		return;
 	}
-
-	for (int i = 0; i < p_node->get_child_count(); i++) {
-		Node *c = p_node->get_child(i);
-		if (c->get_filename() != String()) {
-			continue; //skip, instance
+	enable_node_path = p_path;
+	if (is_inside_tree()) {
+		node_id = ObjectID();
+		Node *node = get_node(enable_node_path);
+		if (node) {
+			node_id = node->get_instance_id();
+			_update_enable_mode(is_on_screen());
+		}
+	}
+}
+NodePath VisibilityEnabler2D::get_enable_node_path() {
+	return enable_node_path;
+}
+
+void VisibilityEnabler2D::_update_enable_mode(bool p_enable) {
+	Node *node = static_cast<Node *>(ObjectDB::get_instance(node_id));
+	if (node) {
+		if (p_enable) {
+			switch (enable_mode) {
+				case ENABLE_MODE_INHERIT: {
+					node->set_process_mode(PROCESS_MODE_INHERIT);
+				} break;
+				case ENABLE_MODE_ALWAYS: {
+					node->set_process_mode(PROCESS_MODE_ALWAYS);
+				} break;
+				case ENABLE_MODE_WHEN_PAUSED: {
+					node->set_process_mode(PROCESS_MODE_WHEN_PAUSED);
+				} break;
+			}
+		} else {
+			node->set_process_mode(PROCESS_MODE_DISABLED);
 		}
-
-		_find_nodes(c);
 	}
 }
-
 void VisibilityEnabler2D::_notification(int p_what) {
 	if (p_what == NOTIFICATION_ENTER_TREE) {
 		if (Engine::get_singleton()->is_editor_hint()) {
 			return;
 		}
 
-		Node *from = this;
-		//find where current scene starts
-		while (from->get_parent() && from->get_filename() == String()) {
-			from = from->get_parent();
-		}
-
-		_find_nodes(from);
-
-		// We need to defer the call of set_process and set_physics_process,
-		// otherwise they are overwritten inside NOTIFICATION_READY.
-		// We can't use call_deferred, because it happens after a physics frame.
-		// The ready signal works as it's emitted immediately after NOTIFICATION_READY.
-
-		if (enabler[ENABLER_PARENT_PHYSICS_PROCESS] && get_parent()) {
-			get_parent()->connect(SceneStringNames::get_singleton()->ready,
-					callable_mp(get_parent(), &Node::set_physics_process), varray(false), CONNECT_ONESHOT);
-		}
-		if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) {
-			get_parent()->connect(SceneStringNames::get_singleton()->ready,
-					callable_mp(get_parent(), &Node::set_process), varray(false), CONNECT_ONESHOT);
+		node_id = ObjectID();
+		Node *node = get_node(enable_node_path);
+		if (node) {
+			node_id = node->get_instance_id();
+			node->set_process_mode(PROCESS_MODE_DISABLED);
 		}
 	}
 
 	if (p_what == NOTIFICATION_EXIT_TREE) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			return;
-		}
-
-		for (Map<Node *, Variant>::Element *E = nodes.front(); E; E = E->next()) {
-			if (!visible) {
-				_change_node_state(E->key(), true);
-			}
-			E->key()->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &VisibilityEnabler2D::_node_removed));
-		}
-
-		nodes.clear();
-	}
-}
-
-void VisibilityEnabler2D::_change_node_state(Node *p_node, bool p_enabled) {
-	ERR_FAIL_COND(!nodes.has(p_node));
-
-	if (enabler[ENABLER_FREEZE_BODIES]) {
-		RigidBody2D *rb = Object::cast_to<RigidBody2D>(p_node);
-		if (rb) {
-			rb->set_sleeping(!p_enabled);
-		}
-	}
-
-	if (enabler[ENABLER_PAUSE_ANIMATIONS]) {
-		AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
-
-		if (ap) {
-			ap->set_active(p_enabled);
-		}
-	}
-
-	if (enabler[ENABLER_PAUSE_ANIMATED_SPRITES]) {
-		AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node);
-
-		if (as) {
-			if (p_enabled) {
-				as->play();
-			} else {
-				as->stop();
-			}
-		}
-	}
-
-	if (enabler[ENABLER_PAUSE_PARTICLES]) {
-		GPUParticles2D *ps = Object::cast_to<GPUParticles2D>(p_node);
-
-		if (ps) {
-			ps->set_emitting(p_enabled);
-		}
-	}
-}
-
-void VisibilityEnabler2D::_node_removed(Node *p_node) {
-	if (!visible) {
-		_change_node_state(p_node, true);
+		node_id = ObjectID();
 	}
-	nodes.erase(p_node);
-}
-
-TypedArray<String> VisibilityEnabler2D::get_configuration_warnings() const {
-	TypedArray<String> warnings = Node::get_configuration_warnings();
-
-#ifdef TOOLS_ENABLED
-	if (is_inside_tree() && get_parent() && (get_parent()->get_filename() == String() && get_parent() != get_tree()->get_edited_scene_root())) {
-		warnings.push_back(TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."));
-	}
-#endif
-	return warnings;
 }
 
 void VisibilityEnabler2D::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("set_enabler", "enabler", "enabled"), &VisibilityEnabler2D::set_enabler);
-	ClassDB::bind_method(D_METHOD("is_enabler_enabled", "enabler"), &VisibilityEnabler2D::is_enabler_enabled);
-	ClassDB::bind_method(D_METHOD("_node_removed"), &VisibilityEnabler2D::_node_removed);
+	ClassDB::bind_method(D_METHOD("set_enable_mode", "mode"), &VisibilityEnabler2D::set_enable_mode);
+	ClassDB::bind_method(D_METHOD("get_enable_mode"), &VisibilityEnabler2D::get_enable_mode);
 
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_animations"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_ANIMATIONS);
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "freeze_bodies"), "set_enabler", "is_enabler_enabled", ENABLER_FREEZE_BODIES);
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_particles"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_PARTICLES);
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_animated_sprites"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_ANIMATED_SPRITES);
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PROCESS);
-	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "physics_process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PHYSICS_PROCESS);
+	ClassDB::bind_method(D_METHOD("set_enable_node_path", "path"), &VisibilityEnabler2D::set_enable_node_path);
+	ClassDB::bind_method(D_METHOD("get_enable_node_path"), &VisibilityEnabler2D::get_enable_node_path);
 
-	BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATIONS);
-	BIND_ENUM_CONSTANT(ENABLER_FREEZE_BODIES);
-	BIND_ENUM_CONSTANT(ENABLER_PAUSE_PARTICLES);
-	BIND_ENUM_CONSTANT(ENABLER_PARENT_PROCESS);
-	BIND_ENUM_CONSTANT(ENABLER_PARENT_PHYSICS_PROCESS);
-	BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATED_SPRITES);
-	BIND_ENUM_CONSTANT(ENABLER_MAX);
-}
+	ADD_GROUP("Enabling", "enable_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,WhenPaused"), "set_enable_mode", "get_enable_mode");
+	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "enable_node_path"), "set_enable_node_path", "get_enable_node_path");
 
-void VisibilityEnabler2D::set_enabler(Enabler p_enabler, bool p_enable) {
-	ERR_FAIL_INDEX(p_enabler, ENABLER_MAX);
-	enabler[p_enabler] = p_enable;
-}
-
-bool VisibilityEnabler2D::is_enabler_enabled(Enabler p_enabler) const {
-	ERR_FAIL_INDEX_V(p_enabler, ENABLER_MAX, false);
-	return enabler[p_enabler];
+	BIND_ENUM_CONSTANT(ENABLE_MODE_INHERIT);
+	BIND_ENUM_CONSTANT(ENABLE_MODE_ALWAYS);
+	BIND_ENUM_CONSTANT(ENABLE_MODE_WHEN_PAUSED);
 }
 
 VisibilityEnabler2D::VisibilityEnabler2D() {
-	for (int i = 0; i < ENABLER_MAX; i++) {
-		enabler[i] = true;
-	}
-	enabler[ENABLER_PARENT_PROCESS] = false;
-	enabler[ENABLER_PARENT_PHYSICS_PROCESS] = false;
 }

+ 19 - 26
scene/2d/visibility_notifier_2d.h

@@ -41,12 +41,12 @@ class VisibilityNotifier2D : public Node2D {
 
 	Rect2 rect;
 
-protected:
-	friend struct SpatialIndexer2D;
-
-	void _enter_viewport(Viewport *p_viewport);
-	void _exit_viewport(Viewport *p_viewport);
+private:
+	bool on_screen = false;
+	void _visibility_enter();
+	void _visibility_exit();
 
+protected:
 	virtual void _screen_enter() {}
 	virtual void _screen_exit() {}
 
@@ -71,42 +71,35 @@ class VisibilityEnabler2D : public VisibilityNotifier2D {
 	GDCLASS(VisibilityEnabler2D, VisibilityNotifier2D);
 
 public:
-	enum Enabler {
-		ENABLER_PAUSE_ANIMATIONS,
-		ENABLER_FREEZE_BODIES,
-		ENABLER_PAUSE_PARTICLES,
-		ENABLER_PARENT_PROCESS,
-		ENABLER_PARENT_PHYSICS_PROCESS,
-		ENABLER_PAUSE_ANIMATED_SPRITES,
-		ENABLER_MAX
+	enum EnableMode {
+		ENABLE_MODE_INHERIT,
+		ENABLE_MODE_ALWAYS,
+		ENABLE_MODE_WHEN_PAUSED,
 	};
 
 protected:
+	ObjectID node_id;
 	virtual void _screen_enter() override;
 	virtual void _screen_exit() override;
 
-	bool visible = false;
-
-	void _find_nodes(Node *p_node);
-
-	Map<Node *, Variant> nodes;
-	void _node_removed(Node *p_node);
-	bool enabler[ENABLER_MAX];
-
-	void _change_node_state(Node *p_node, bool p_enabled);
+	EnableMode enable_mode = ENABLE_MODE_INHERIT;
+	NodePath enable_node_path = NodePath("..");
 
 	void _notification(int p_what);
 	static void _bind_methods();
 
+	void _update_enable_mode(bool p_enable);
+
 public:
-	void set_enabler(Enabler p_enabler, bool p_enable);
-	bool is_enabler_enabled(Enabler p_enabler) const;
+	void set_enable_mode(EnableMode p_mode);
+	EnableMode get_enable_mode();
 
-	TypedArray<String> get_configuration_warnings() const override;
+	void set_enable_node_path(NodePath p_path);
+	NodePath get_enable_node_path();
 
 	VisibilityEnabler2D();
 };
 
-VARIANT_ENUM_CAST(VisibilityEnabler2D::Enabler);
+VARIANT_ENUM_CAST(VisibilityEnabler2D::EnableMode);
 
 #endif // VISIBILITY_NOTIFIER_2D_H

+ 0 - 12
scene/main/viewport.cpp

@@ -188,11 +188,6 @@ void Viewport::update_worlds() {
 		return;
 	}
 
-	Rect2 abstracted_rect = Rect2(Vector2(), get_visible_rect().size);
-	Rect2 xformed_rect = (global_canvas_transform * canvas_transform).affine_inverse().xform(abstracted_rect);
-	find_world_2d()->_update_viewport(this, xformed_rect);
-	find_world_2d()->_update();
-
 	find_world_3d()->_update(get_tree()->get_frame());
 }
 
@@ -441,8 +436,6 @@ void Viewport::_notification(int p_what) {
 			_update_listener();
 			_update_listener_2d();
 
-			find_world_2d()->_register_viewport(this, Rect2());
-
 			add_to_group("_viewports");
 			if (get_tree()->is_debugging_collisions_hint()) {
 				//2D
@@ -499,9 +492,6 @@ void Viewport::_notification(int p_what) {
 		} break;
 		case NOTIFICATION_EXIT_TREE: {
 			_gui_cancel_tooltip();
-			if (world_2d.is_valid()) {
-				world_2d->_remove_viewport(this);
-			}
 
 			RenderingServer::get_singleton()->viewport_set_scenario(viewport, RID());
 			RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
@@ -1156,7 +1146,6 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
 	}
 
 	if (is_inside_tree()) {
-		find_world_2d()->_remove_viewport(this);
 		RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
 	}
 
@@ -1172,7 +1161,6 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
 	if (is_inside_tree()) {
 		current_canvas = find_world_2d()->get_canvas();
 		RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas);
-		find_world_2d()->_register_viewport(this, Rect2());
 	}
 }
 

+ 0 - 287
scene/resources/world_2d.cpp

@@ -38,284 +38,6 @@
 #include "servers/physics_server_2d.h"
 #include "servers/rendering_server.h"
 
-struct SpatialIndexer2D {
-	struct CellRef {
-		int ref = 0;
-
-		_FORCE_INLINE_ int inc() {
-			ref++;
-			return ref;
-		}
-		_FORCE_INLINE_ int dec() {
-			ref--;
-			return ref;
-		}
-	};
-
-	struct CellKey {
-		union {
-			struct {
-				int32_t x;
-				int32_t y;
-			};
-			uint64_t key = 0;
-		};
-
-		bool operator==(const CellKey &p_key) const { return key == p_key.key; }
-		_FORCE_INLINE_ bool operator<(const CellKey &p_key) const {
-			return key < p_key.key;
-		}
-	};
-
-	struct CellData {
-		Map<VisibilityNotifier2D *, CellRef> notifiers;
-	};
-
-	Map<CellKey, CellData> cells;
-	int cell_size;
-
-	Map<VisibilityNotifier2D *, Rect2> notifiers;
-
-	struct ViewportData {
-		Map<VisibilityNotifier2D *, uint64_t> notifiers;
-		Rect2 rect;
-	};
-
-	Map<Viewport *, ViewportData> viewports;
-
-	bool changed = false;
-
-	uint64_t pass = 0;
-
-	void _notifier_update_cells(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect, bool p_add) {
-		Point2i begin = p_rect.position;
-		begin /= cell_size;
-		Point2i end = p_rect.position + p_rect.size;
-		end /= cell_size;
-		for (int i = begin.x; i <= end.x; i++) {
-			for (int j = begin.y; j <= end.y; j++) {
-				CellKey ck;
-				ck.x = i;
-				ck.y = j;
-				Map<CellKey, CellData>::Element *E = cells.find(ck);
-
-				if (p_add) {
-					if (!E) {
-						E = cells.insert(ck, CellData());
-					}
-					E->get().notifiers[p_notifier].inc();
-				} else {
-					ERR_CONTINUE(!E);
-					if (E->get().notifiers[p_notifier].dec() == 0) {
-						E->get().notifiers.erase(p_notifier);
-						if (E->get().notifiers.is_empty()) {
-							cells.erase(E);
-						}
-					}
-				}
-			}
-		}
-	}
-
-	void _notifier_add(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) {
-		ERR_FAIL_COND(notifiers.has(p_notifier));
-		notifiers[p_notifier] = p_rect;
-		_notifier_update_cells(p_notifier, p_rect, true);
-		changed = true;
-	}
-
-	void _notifier_update(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) {
-		Map<VisibilityNotifier2D *, Rect2>::Element *E = notifiers.find(p_notifier);
-		ERR_FAIL_COND(!E);
-		if (E->get() == p_rect) {
-			return;
-		}
-
-		_notifier_update_cells(p_notifier, p_rect, true);
-		_notifier_update_cells(p_notifier, E->get(), false);
-		E->get() = p_rect;
-		changed = true;
-	}
-
-	void _notifier_remove(VisibilityNotifier2D *p_notifier) {
-		Map<VisibilityNotifier2D *, Rect2>::Element *E = notifiers.find(p_notifier);
-		ERR_FAIL_COND(!E);
-		_notifier_update_cells(p_notifier, E->get(), false);
-		notifiers.erase(p_notifier);
-
-		List<Viewport *> removed;
-		for (Map<Viewport *, ViewportData>::Element *F = viewports.front(); F; F = F->next()) {
-			Map<VisibilityNotifier2D *, uint64_t>::Element *G = F->get().notifiers.find(p_notifier);
-
-			if (G) {
-				F->get().notifiers.erase(G);
-				removed.push_back(F->key());
-			}
-		}
-
-		while (!removed.is_empty()) {
-			p_notifier->_exit_viewport(removed.front()->get());
-			removed.pop_front();
-		}
-
-		changed = true;
-	}
-
-	void _add_viewport(Viewport *p_viewport, const Rect2 &p_rect) {
-		ERR_FAIL_COND(viewports.has(p_viewport));
-		ViewportData vd;
-		vd.rect = p_rect;
-		viewports[p_viewport] = vd;
-		changed = true;
-	}
-
-	void _update_viewport(Viewport *p_viewport, const Rect2 &p_rect) {
-		Map<Viewport *, ViewportData>::Element *E = viewports.find(p_viewport);
-		ERR_FAIL_COND(!E);
-		if (E->get().rect == p_rect) {
-			return;
-		}
-		E->get().rect = p_rect;
-		changed = true;
-	}
-
-	void _remove_viewport(Viewport *p_viewport) {
-		ERR_FAIL_COND(!viewports.has(p_viewport));
-		List<VisibilityNotifier2D *> removed;
-		for (Map<VisibilityNotifier2D *, uint64_t>::Element *E = viewports[p_viewport].notifiers.front(); E; E = E->next()) {
-			removed.push_back(E->key());
-		}
-
-		while (!removed.is_empty()) {
-			removed.front()->get()->_exit_viewport(p_viewport);
-			removed.pop_front();
-		}
-
-		viewports.erase(p_viewport);
-	}
-
-	void _update() {
-		if (!changed) {
-			return;
-		}
-
-		for (Map<Viewport *, ViewportData>::Element *E = viewports.front(); E; E = E->next()) {
-			Point2i begin = E->get().rect.position;
-			begin /= cell_size;
-			Point2i end = E->get().rect.position + E->get().rect.size;
-			end /= cell_size;
-			pass++;
-			List<VisibilityNotifier2D *> added;
-			List<VisibilityNotifier2D *> removed;
-
-			uint64_t visible_cells = (uint64_t)(end.x - begin.x) * (uint64_t)(end.y - begin.y);
-
-			if (visible_cells > 10000) {
-				//well you zoomed out a lot, it's your problem. To avoid freezing in the for loops below, we'll manually check cell by cell
-
-				for (Map<CellKey, CellData>::Element *F = cells.front(); F; F = F->next()) {
-					const CellKey &ck = F->key();
-
-					if (ck.x < begin.x || ck.x > end.x) {
-						continue;
-					}
-					if (ck.y < begin.y || ck.y > end.y) {
-						continue;
-					}
-
-					//notifiers in cell
-					for (Map<VisibilityNotifier2D *, CellRef>::Element *G = F->get().notifiers.front(); G; G = G->next()) {
-						Map<VisibilityNotifier2D *, uint64_t>::Element *H = E->get().notifiers.find(G->key());
-						if (!H) {
-							H = E->get().notifiers.insert(G->key(), pass);
-							added.push_back(G->key());
-						} else {
-							H->get() = pass;
-						}
-					}
-				}
-
-			} else {
-				//check cells in grid fashion
-				for (int i = begin.x; i <= end.x; i++) {
-					for (int j = begin.y; j <= end.y; j++) {
-						CellKey ck;
-						ck.x = i;
-						ck.y = j;
-
-						Map<CellKey, CellData>::Element *F = cells.find(ck);
-						if (!F) {
-							continue;
-						}
-
-						//notifiers in cell
-						for (Map<VisibilityNotifier2D *, CellRef>::Element *G = F->get().notifiers.front(); G; G = G->next()) {
-							Map<VisibilityNotifier2D *, uint64_t>::Element *H = E->get().notifiers.find(G->key());
-							if (!H) {
-								H = E->get().notifiers.insert(G->key(), pass);
-								added.push_back(G->key());
-							} else {
-								H->get() = pass;
-							}
-						}
-					}
-				}
-			}
-
-			for (Map<VisibilityNotifier2D *, uint64_t>::Element *F = E->get().notifiers.front(); F; F = F->next()) {
-				if (F->get() != pass) {
-					removed.push_back(F->key());
-				}
-			}
-
-			while (!added.is_empty()) {
-				added.front()->get()->_enter_viewport(E->key());
-				added.pop_front();
-			}
-
-			while (!removed.is_empty()) {
-				E->get().notifiers.erase(removed.front()->get());
-				removed.front()->get()->_exit_viewport(E->key());
-				removed.pop_front();
-			}
-		}
-
-		changed = false;
-	}
-
-	SpatialIndexer2D() {
-		cell_size = GLOBAL_DEF("world/2d/cell_size", 100);
-	}
-};
-
-void World2D::_register_viewport(Viewport *p_viewport, const Rect2 &p_rect) {
-	indexer->_add_viewport(p_viewport, p_rect);
-}
-
-void World2D::_update_viewport(Viewport *p_viewport, const Rect2 &p_rect) {
-	indexer->_update_viewport(p_viewport, p_rect);
-}
-
-void World2D::_remove_viewport(Viewport *p_viewport) {
-	indexer->_remove_viewport(p_viewport);
-}
-
-void World2D::_register_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) {
-	indexer->_notifier_add(p_notifier, p_rect);
-}
-
-void World2D::_update_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect) {
-	indexer->_notifier_update(p_notifier, p_rect);
-}
-
-void World2D::_remove_notifier(VisibilityNotifier2D *p_notifier) {
-	indexer->_notifier_remove(p_notifier);
-}
-
-void World2D::_update() {
-	indexer->_update();
-}
-
 RID World2D::get_canvas() const {
 	return canvas;
 }
@@ -328,12 +50,6 @@ RID World2D::get_navigation_map() const {
 	return navigation_map;
 }
 
-void World2D::get_viewport_list(List<Viewport *> *r_viewports) {
-	for (Map<Viewport *, SpatialIndexer2D::ViewportData>::Element *E = indexer->viewports.front(); E; E = E->next()) {
-		r_viewports->push_back(E->key());
-	}
-}
-
 void World2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_canvas"), &World2D::get_canvas);
 	ClassDB::bind_method(D_METHOD("get_space"), &World2D::get_space);
@@ -369,13 +85,10 @@ World2D::World2D() {
 	NavigationServer2D::get_singleton()->map_set_active(navigation_map, true);
 	NavigationServer2D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 10));
 	NavigationServer2D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 5));
-
-	indexer = memnew(SpatialIndexer2D);
 }
 
 World2D::~World2D() {
 	RenderingServer::get_singleton()->free(canvas);
 	PhysicsServer2D::get_singleton()->free(space);
 	NavigationServer2D::get_singleton()->free(navigation_map);
-	memdelete(indexer);
 }

+ 3 - 11
scene/resources/world_2d.h

@@ -46,23 +46,15 @@ class World2D : public Resource {
 	RID space;
 	RID navigation_map;
 
-	SpatialIndexer2D *indexer;
+	Set<Viewport *> viewports;
 
 protected:
 	static void _bind_methods();
 	friend class Viewport;
-	friend class VisibilityNotifier2D;
 
-	void _register_viewport(Viewport *p_viewport, const Rect2 &p_rect);
-	void _update_viewport(Viewport *p_viewport, const Rect2 &p_rect);
+	void _register_viewport(Viewport *p_viewport);
 	void _remove_viewport(Viewport *p_viewport);
 
-	void _register_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect);
-	void _update_notifier(VisibilityNotifier2D *p_notifier, const Rect2 &p_rect);
-	void _remove_notifier(VisibilityNotifier2D *p_notifier);
-
-	void _update();
-
 public:
 	RID get_canvas() const;
 	RID get_space() const;
@@ -70,7 +62,7 @@ public:
 
 	PhysicsDirectSpaceState2D *get_direct_space_state();
 
-	void get_viewport_list(List<Viewport *> *r_viewports);
+	_FORCE_INLINE_ const Set<Viewport *> &get_viewports() { return viewports; }
 
 	World2D();
 	~World2D();

+ 103 - 22
servers/rendering/renderer_canvas_cull.cpp

@@ -104,7 +104,7 @@ void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_PtrOwner<Rende
 	} while (ysort_owner && ysort_owner->sort_y);
 }
 
-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) {
+void RendererCanvasCull::_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, const Transform2D &p_xform) {
 	if (ci->copy_back_buffer) {
 		ci->copy_back_buffer->screen_rect = xform.xform(ci->copy_back_buffer->rect).intersection(p_clip_rect);
 	}
@@ -173,32 +173,44 @@ void _attach_canvas_item_for_draw(RendererCanvasCull::Item *ci, RendererCanvasCu
 		}
 	}
 
-	if (ci->update_when_visible) {
-		RenderingServerDefault::redraw_request();
-	}
-
-	if ((ci->commands != nullptr && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
+	if (((ci->commands != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
 		//something to draw?
-		ci->final_transform = xform;
-		ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a);
-		ci->global_rect_cache = global_rect;
-		ci->global_rect_cache.position -= p_clip_rect.position;
-		ci->light_masked = false;
 
-		int zidx = p_z - RS::CANVAS_ITEM_Z_MIN;
+		if (ci->update_when_visible) {
+			RenderingServerDefault::redraw_request();
+		}
 
-		if (z_last_list[zidx]) {
-			z_last_list[zidx]->next = ci;
-			z_last_list[zidx] = ci;
+		if (ci->commands != nullptr) {
+			ci->final_transform = xform;
+			ci->final_modulate = Color(modulate.r * ci->self_modulate.r, modulate.g * ci->self_modulate.g, modulate.b * ci->self_modulate.b, modulate.a * ci->self_modulate.a);
+			ci->global_rect_cache = global_rect;
+			ci->global_rect_cache.position -= p_clip_rect.position;
+			ci->light_masked = false;
 
-		} else {
-			z_list[zidx] = ci;
-			z_last_list[zidx] = ci;
+			int zidx = p_z - RS::CANVAS_ITEM_Z_MIN;
+
+			if (z_last_list[zidx]) {
+				z_last_list[zidx]->next = ci;
+				z_last_list[zidx] = ci;
+
+			} else {
+				z_list[zidx] = ci;
+				z_last_list[zidx] = ci;
+			}
+
+			ci->z_final = p_z;
+
+			ci->next = nullptr;
 		}
 
-		ci->z_final = p_z;
+		if (ci->visibility_notifier) {
+			if (!ci->visibility_notifier->visible_element.in_list()) {
+				visibility_notifier_list.add(&ci->visibility_notifier->visible_element);
+				ci->visibility_notifier->just_visible = true;
+			}
 
-		ci->next = nullptr;
+			ci->visibility_notifier->visible_in_frame = RSG::rasterizer->get_frame_number();
+		}
 	}
 }
 
@@ -215,6 +227,13 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 	}
 
 	Rect2 rect = ci->get_rect();
+
+	if (ci->visibility_notifier) {
+		if (ci->visibility_notifier->area.size != Vector2()) {
+			rect = rect.merge(ci->visibility_notifier->area);
+		}
+	}
+
 	Transform2D xform = ci->xform;
 	if (snapping_2d_transforms_to_pixel) {
 		xform.elements[2] = xform.elements[2].floor();
@@ -289,7 +308,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 				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);
+			_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, xform);
 		}
 	} else {
 		RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -305,7 +324,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 			}
 			_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);
+		_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, xform);
 		for (int i = 0; i < child_item_count; i++) {
 			if (child_items[i]->behind || use_canvas_group) {
 				continue;
@@ -1095,6 +1114,26 @@ void RendererCanvasCull::canvas_item_set_use_parent_material(RID p_item, bool p_
 	canvas_item->use_parent_material = p_enable;
 }
 
+void RendererCanvasCull::canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callable, const Callable &p_exit_callable) {
+	Item *canvas_item = canvas_item_owner.getornull(p_item);
+	ERR_FAIL_COND(!canvas_item);
+
+	if (p_enable) {
+		if (!canvas_item->visibility_notifier) {
+			canvas_item->visibility_notifier = visibility_notifier_allocator.alloc();
+		}
+		canvas_item->visibility_notifier->area = p_area;
+		canvas_item->visibility_notifier->enter_callable = p_enter_callable;
+		canvas_item->visibility_notifier->exit_callable = p_exit_callable;
+
+	} else {
+		if (canvas_item->visibility_notifier) {
+			visibility_notifier_allocator.free(canvas_item->visibility_notifier);
+			canvas_item->visibility_notifier = nullptr;
+		}
+	}
+}
+
 void RendererCanvasCull::canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin, bool p_fit_empty, float p_fit_margin, bool p_blur_mipmaps) {
 	Item *canvas_item = canvas_item_owner.getornull(p_item);
 	ERR_FAIL_COND(!canvas_item);
@@ -1477,6 +1516,44 @@ void RendererCanvasCull::canvas_item_set_default_texture_repeat(RID p_item, RS::
 	ci->texture_repeat = p_repeat;
 }
 
+void RendererCanvasCull::update_visibility_notifiers() {
+	SelfList<Item::VisibilityNotifierData> *E = visibility_notifier_list.first();
+	while (E) {
+		SelfList<Item::VisibilityNotifierData> *N = E->next();
+
+		Item::VisibilityNotifierData *visibility_notifier = E->self();
+		if (visibility_notifier->just_visible) {
+			visibility_notifier->just_visible = false;
+
+			if (!visibility_notifier->enter_callable.is_null()) {
+				if (RSG::threaded) {
+					visibility_notifier->enter_callable.call_deferred(nullptr, 0);
+				} else {
+					Callable::CallError ce;
+					Variant ret;
+					visibility_notifier->enter_callable.call(nullptr, 0, ret, ce);
+				}
+			}
+		} else {
+			if (visibility_notifier->visible_in_frame != RSG::rasterizer->get_frame_number()) {
+				visibility_notifier_list.remove(E);
+
+				if (!visibility_notifier->exit_callable.is_null()) {
+					if (RSG::threaded) {
+						visibility_notifier->exit_callable.call_deferred(nullptr, 0);
+					} else {
+						Callable::CallError ce;
+						Variant ret;
+						visibility_notifier->exit_callable.call(nullptr, 0, ret, ce);
+					}
+				}
+			}
+		}
+
+		E = N;
+	}
+}
+
 bool RendererCanvasCull::free(RID p_rid) {
 	if (canvas_owner.owns(p_rid)) {
 		Canvas *canvas = canvas_owner.getornull(p_rid);
@@ -1531,6 +1608,10 @@ bool RendererCanvasCull::free(RID p_rid) {
 			canvas_item->child_items[i]->parent = RID();
 		}
 
+		if (canvas_item->visibility_notifier != nullptr) {
+			visibility_notifier_allocator.free(canvas_item->visibility_notifier);
+		}
+
 		/*
 		if (canvas_item->material) {
 			canvas_item->material->owners.erase(canvas_item);

+ 24 - 0
servers/rendering/renderer_canvas_cull.h

@@ -31,6 +31,7 @@
 #ifndef RENDERING_SERVER_CANVAS_CULL_H
 #define RENDERING_SERVER_CANVAS_CULL_H
 
+#include "core/templates/paged_allocator.h"
 #include "renderer_compositor.h"
 #include "renderer_viewport.h"
 
@@ -55,6 +56,20 @@ public:
 
 		Vector<Item *> child_items;
 
+		struct VisibilityNotifierData {
+			Rect2 area;
+			Callable enter_callable;
+			Callable exit_callable;
+			bool just_visible = false;
+			uint64_t visible_in_frame = 0;
+			SelfList<VisibilityNotifierData> visible_element;
+			VisibilityNotifierData() :
+					visible_element(this) {
+			}
+		};
+
+		VisibilityNotifierData *visibility_notifier = nullptr;
+
 		Item() {
 			children_order_dirty = true;
 			E = nullptr;
@@ -156,6 +171,11 @@ public:
 	bool sdf_used = false;
 	bool snapping_2d_transforms_to_pixel = false;
 
+	PagedAllocator<Item::VisibilityNotifierData> visibility_notifier_allocator;
+	SelfList<Item::VisibilityNotifierData>::List visibility_notifier_list;
+
+	_FORCE_INLINE_ void _attach_canvas_item_for_draw(Item *ci, 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, const Transform2D &p_xform);
+
 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, bool allow_y_sort);
@@ -224,6 +244,8 @@ public:
 
 	void canvas_item_set_use_parent_material(RID p_item, bool p_enable);
 
+	void canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callable, const Callable &p_exit_callable);
+
 	void canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin = 5.0, bool p_fit_empty = false, float p_fit_margin = 0.0, bool p_blur_mipmaps = false);
 
 	RID canvas_light_allocate();
@@ -283,6 +305,8 @@ public:
 	void canvas_item_set_default_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter);
 	void canvas_item_set_default_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat);
 
+	void update_visibility_notifiers();
+
 	bool free(RID p_rid);
 	RendererCanvasCull();
 	~RendererCanvasCull();

+ 3 - 0
servers/rendering/rendering_server_default.cpp

@@ -117,6 +117,8 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
 	_draw_margins();
 	RSG::rasterizer->end_frame(p_swap_buffers);
 
+	RSG::canvas->update_visibility_notifiers();
+
 	while (frame_drawn_callbacks.front()) {
 		Object *obj = ObjectDB::get_instance(frame_drawn_callbacks.front()->get().object);
 		if (obj) {
@@ -396,6 +398,7 @@ RenderingServerDefault::RenderingServerDefault(bool p_create_thread) :
 		server_thread = 0;
 	}
 
+	RSG::threaded = p_create_thread;
 	RSG::canvas = memnew(RendererCanvasCull);
 	RSG::viewport = memnew(RendererViewport);
 	RendererSceneCull *sr = memnew(RendererSceneCull);

+ 2 - 0
servers/rendering/rendering_server_default.h

@@ -820,6 +820,8 @@ public:
 
 	FUNC2(canvas_item_set_use_parent_material, RID, bool)
 
+	FUNC5(canvas_item_set_visibility_notifier, RID, bool, const Rect2 &, const Callable &, const Callable &)
+
 	FUNC6(canvas_item_set_canvas_group_mode, RID, CanvasGroupMode, float, bool, float, bool)
 
 	FUNCRIDSPLIT(canvas_light)

+ 2 - 0
servers/rendering/rendering_server_globals.cpp

@@ -30,6 +30,8 @@
 
 #include "rendering_server_globals.h"
 
+bool RenderingServerGlobals::threaded = false;
+
 RendererStorage *RenderingServerGlobals::storage = nullptr;
 RendererCanvasRender *RenderingServerGlobals::canvas_render = nullptr;
 RendererCompositor *RenderingServerGlobals::rasterizer = nullptr;

+ 2 - 0
servers/rendering/rendering_server_globals.h

@@ -41,6 +41,8 @@ class RendererScene;
 
 class RenderingServerGlobals {
 public:
+	static bool threaded;
+
 	static RendererStorage *storage;
 	static RendererCanvasRender *canvas_render;
 	static RendererCompositor *rasterizer;

+ 2 - 0
servers/rendering_server.h

@@ -1301,6 +1301,8 @@ public:
 
 	virtual void canvas_item_set_use_parent_material(RID p_item, bool p_enable) = 0;
 
+	virtual void canvas_item_set_visibility_notifier(RID p_item, bool p_enable, const Rect2 &p_area, const Callable &p_enter_callbable, const Callable &p_exit_callable) = 0;
+
 	enum CanvasGroupMode {
 		CANVAS_GROUP_MODE_DISABLED,
 		CANVAS_GROUP_MODE_OPAQUE,