Browse Source

Merge pull request #53463 from lawnjelly/vital_redraws

Add editor vital redraws only option
Rémi Verschelde 3 years ago
parent
commit
689f59dca0

+ 6 - 0
core/os/os.cpp

@@ -146,6 +146,10 @@ bool OS::is_in_low_processor_usage_mode() const {
 	return low_processor_usage_mode;
 }
 
+void OS::set_update_vital_only(bool p_enabled) {
+	_update_vital_only = p_enabled;
+}
+
 void OS::set_low_processor_usage_mode_sleep_usec(int p_usec) {
 	low_processor_usage_mode_sleep_usec = p_usec;
 }
@@ -834,6 +838,8 @@ OS::OS() {
 	_keep_screen_on = true; // set default value to true, because this had been true before godot 2.0.
 	low_processor_usage_mode = false;
 	low_processor_usage_mode_sleep_usec = 10000;
+	_update_vital_only = false;
+	_update_pending = false;
 	_verbose_stdout = false;
 	_debug_stdout = false;
 	_no_window = false;

+ 23 - 0
core/os/os.h

@@ -50,6 +50,8 @@ class OS {
 	bool _keep_screen_on;
 	bool low_processor_usage_mode;
 	int low_processor_usage_mode_sleep_usec;
+	bool _update_vital_only;
+	bool _update_pending;
 	bool _verbose_stdout;
 	bool _debug_stdout;
 	String _local_clipboard;
@@ -287,6 +289,27 @@ public:
 	virtual bool is_in_low_processor_usage_mode() const;
 	virtual void set_low_processor_usage_mode_sleep_usec(int p_usec);
 	virtual int get_low_processor_usage_mode_sleep_usec() const;
+	virtual void set_update_vital_only(bool p_enabled);
+	virtual void set_update_pending(bool p_pending) { _update_pending = p_pending; }
+
+	// Convenience easy switch for turning this off outside tools builds, without littering calling code
+	// with #ifdefs. It will also hopefully be compiled out in release.
+#ifdef TOOLS_ENABLED
+	// This function is used to throttle back updates of animations and particle systems when using UPDATE_VITAL_ONLY mode.
+
+	// CASE 1) We are not in UPDATE_VITAL_ONLY mode - always return true and update.
+	// CASE 2) We are in UPDATE_VITAL_ONLY mode -
+
+	// In most cases this will return false and prevent animations etc updating.
+	// The exception is that we can also choose to receive a true
+	// each time a frame is redrawn as a result of moving the mouse, clicking etc.
+	// This enables us to keep e.g. particle systems processing, but ONLY when other
+	// events have caused a redraw.
+	virtual bool is_update_pending(bool p_include_redraws = false) const { return !_update_vital_only || (_update_pending && p_include_redraws); }
+#else
+	// Always update when outside the editor, UPDATE_VITAL_ONLY has no effect outside the editor.
+	virtual bool is_update_pending(bool p_include_redraws = false) const { return true; }
+#endif
 
 	virtual String get_executable_path() const;
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;

+ 11 - 0
doc/classes/VisualServer.xml

@@ -1178,8 +1178,10 @@
 		</method>
 		<method name="has_changed" qualifiers="const">
 			<return type="bool" />
+			<argument index="0" name="queried_priority" type="int" enum="VisualServer.ChangedPriority" default="0" />
 			<description>
 				Returns [code]true[/code] if changes have been made to the VisualServer's data. [method draw] is usually called if this happens.
+				As changes are registered as either high or low priority (e.g. dynamic shaders), this function takes an optional argument to query either low or high priority changes, or any changes.
 			</description>
 		</method>
 		<method name="has_feature" qualifiers="const">
@@ -3841,5 +3843,14 @@
 		<constant name="ENV_SSAO_BLUR_3x3" value="3" enum="EnvironmentSSAOBlur">
 			Performs a 3x3 blur on the SSAO output. Use this for smoothest SSAO.
 		</constant>
+		<constant name="CHANGED_PRIORITY_ANY" value="0" enum="ChangedPriority">
+			Used to query for any changes that request a redraw, whatever the priority.
+		</constant>
+		<constant name="CHANGED_PRIORITY_LOW" value="1" enum="ChangedPriority">
+			Registered changes which have low priority can be optionally prevented from causing editor redraws. Examples might include dynamic shaders (typically using the [code]TIME[/code] built-in).
+		</constant>
+		<constant name="CHANGED_PRIORITY_HIGH" value="2" enum="ChangedPriority">
+			Registered changes which can cause a redraw default to high priority.
+		</constant>
 	</constants>
 </class>

+ 2 - 2
drivers/gles2/rasterizer_canvas_gles2.cpp

@@ -1659,7 +1659,7 @@ void RasterizerCanvasGLES2::_legacy_canvas_render_item(Item *p_ci, RenderItemSta
 
 			if (shader_ptr != r_ris.shader_cache) {
 				if (shader_ptr->canvas_item.uses_time) {
-					VisualServerRaster::redraw_request();
+					VisualServerRaster::redraw_request(false);
 				}
 
 				state.canvas_shader.set_custom_shader(shader_ptr->custom_code_id);
@@ -2021,7 +2021,7 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI
 
 			if (shader_ptr != r_ris.shader_cache) {
 				if (shader_ptr->canvas_item.uses_time) {
-					VisualServerRaster::redraw_request();
+					VisualServerRaster::redraw_request(false);
 				}
 
 				state.canvas_shader.set_custom_shader(shader_ptr->custom_code_id);

+ 1 - 1
drivers/gles2/rasterizer_scene_gles2.cpp

@@ -1242,7 +1242,7 @@ void RasterizerSceneGLES2::_add_geometry_with_material(RasterizerStorageGLES2::G
 	// do not add anything here, as lights are duplicated elements..
 
 	if (p_material->shader->spatial.uses_time) {
-		VisualServerRaster::redraw_request();
+		VisualServerRaster::redraw_request(false);
 	}
 }
 

+ 3 - 3
drivers/gles3/rasterizer_canvas_gles3.cpp

@@ -170,7 +170,7 @@ void RasterizerCanvasGLES3::_legacy_canvas_render_item(Item *p_ci, RenderItemSta
 
 			if (shader_ptr != r_ris.shader_cache || r_ris.rebind_shader) {
 				if (shader_ptr->canvas_item.uses_time) {
-					VisualServerRaster::redraw_request();
+					VisualServerRaster::redraw_request(false);
 				}
 
 				state.canvas_shader.set_custom_shader(shader_ptr->custom_code_id);
@@ -968,7 +968,7 @@ void RasterizerCanvasGLES3::render_batches(Item *p_current_clip, bool &r_reclip,
 
 							glVertexAttrib4f(VS::ARRAY_COLOR, 1, 1, 1, 1); //not used, so keep white
 
-							VisualServerRaster::redraw_request();
+							VisualServerRaster::redraw_request(false);
 
 							storage->particles_request_process(particles_cmd->particles);
 							//enable instancing
@@ -1250,7 +1250,7 @@ void RasterizerCanvasGLES3::render_joined_item(const BItemJoined &p_bij, RenderI
 
 			if (shader_ptr != r_ris.shader_cache || r_ris.rebind_shader) {
 				if (shader_ptr->canvas_item.uses_time) {
-					VisualServerRaster::redraw_request();
+					VisualServerRaster::redraw_request(false);
 				}
 
 				state.canvas_shader.set_custom_shader(shader_ptr->custom_code_id);

+ 1 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -2406,7 +2406,7 @@ void RasterizerSceneGLES3::_add_geometry_with_material(RasterizerStorageGLES3::G
 	}
 
 	if (p_material->shader->spatial.uses_time) {
-		VisualServerRaster::redraw_request();
+		VisualServerRaster::redraw_request(false);
 	}
 }
 

+ 1 - 1
editor/code_editor.cpp

@@ -936,7 +936,7 @@ void CodeTextEditor::update_editor_settings() {
 	text_editor->set_line_length_guideline_hard_column(EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_hard_column"));
 	text_editor->set_scroll_pass_end_of_file(EditorSettings::get_singleton()->get("text_editor/cursor/scroll_past_end_of_file"));
 	text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret"));
-	text_editor->cursor_set_blink_enabled(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink"));
+	text_editor->cursor_set_blink_enabled(EditorSettings::get_singleton()->is_caret_blink_active());
 	text_editor->cursor_set_blink_speed(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink_speed"));
 	text_editor->set_auto_brace_completion(EditorSettings::get_singleton()->get("text_editor/completion/auto_brace_complete"));
 }

+ 23 - 2
editor/editor_node.cpp

@@ -636,9 +636,17 @@ void EditorNode::_update_update_spinner() {
 	update_spinner->set_visible(EditorSettings::get_singleton()->get("interface/editor/show_update_spinner"));
 
 	const bool update_continuously = EditorSettings::get_singleton()->get("interface/editor/update_continuously");
+	const bool vital_only = EditorSettings::get_singleton()->get("interface/editor/update_vital_only");
 	PopupMenu *update_popup = update_spinner->get_popup();
 	update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_CONTINUOUSLY), update_continuously);
-	update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !update_continuously);
+
+	if (update_continuously) {
+		update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), false);
+		update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_VITAL_ONLY), false);
+	} else {
+		update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !vital_only);
+		update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_VITAL_ONLY), vital_only);
+	}
 
 	if (update_continuously) {
 		update_spinner->set_tooltip(TTR("Spins when the editor window redraws.\nUpdate Continuously is enabled, which can increase power usage. Click to disable it."));
@@ -656,6 +664,11 @@ void EditorNode::_update_update_spinner() {
 	}
 
 	OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
+
+	// Only set low priority redraws to false in the editor.
+	// When we run the project in the editor, we don't want it to prevent
+	// rendering any frames.
+	OS::get_singleton()->set_update_vital_only(vital_only && !update_continuously);
 }
 
 void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) {
@@ -2791,6 +2804,12 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 		} break;
 		case SETTINGS_UPDATE_WHEN_CHANGED: {
 			EditorSettings::get_singleton()->set("interface/editor/update_continuously", false);
+			EditorSettings::get_singleton()->set("interface/editor/update_vital_only", false);
+			_update_update_spinner();
+		} break;
+		case SETTINGS_UPDATE_VITAL_ONLY: {
+			EditorSettings::get_singleton()->set("interface/editor/update_continuously", false);
+			EditorSettings::get_singleton()->set("interface/editor/update_vital_only", true);
 			_update_update_spinner();
 		} break;
 		case SETTINGS_UPDATE_SPINNER_HIDE: {
@@ -5951,6 +5970,7 @@ EditorNode::EditorNode() {
 	EDITOR_DEF("interface/editor/quit_confirmation", true);
 	EDITOR_DEF("interface/editor/show_update_spinner", false);
 	EDITOR_DEF("interface/editor/update_continuously", false);
+	EDITOR_DEF("interface/editor/update_vital_only", false);
 	EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", false);
 	EDITOR_DEF_RST("interface/scene_tabs/show_thumbnail_on_hover", true);
 	EDITOR_DEF_RST("interface/inspector/capitalize_properties", true);
@@ -6622,7 +6642,8 @@ EditorNode::EditorNode() {
 	update_spinner->get_popup()->connect("id_pressed", this, "_menu_option");
 	p = update_spinner->get_popup();
 	p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY);
-	p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED);
+	p->add_radio_check_item(TTR("Update All Changes"), SETTINGS_UPDATE_WHEN_CHANGED);
+	p->add_radio_check_item(TTR("Update Vital Changes"), SETTINGS_UPDATE_VITAL_ONLY);
 	p->add_separator();
 	p->add_item(TTR("Hide Update Spinner"), SETTINGS_UPDATE_SPINNER_HIDE);
 	_update_update_spinner();

+ 1 - 0
editor/editor_node.h

@@ -176,6 +176,7 @@ private:
 		RUN_VCS_SETTINGS,
 		RUN_VCS_SHUT_DOWN,
 		SETTINGS_UPDATE_CONTINUOUSLY,
+		SETTINGS_UPDATE_VITAL_ONLY,
 		SETTINGS_UPDATE_WHEN_CHANGED,
 		SETTINGS_UPDATE_ALWAYS,
 		SETTINGS_UPDATE_CHANGES,

+ 13 - 0
editor/editor_settings.cpp

@@ -1292,6 +1292,19 @@ void EditorSettings::load_favorites() {
 	}
 }
 
+// The logic for this is rather convoluted as it takes into account whether
+// vital updates only is selected.
+bool EditorSettings::is_caret_blink_active() const {
+	bool blink = get("text_editor/cursor/caret_blink");
+	bool vital_only = get("interface/editor/update_vital_only");
+	bool continuous = get("interface/editor/update_continuously");
+
+	if (vital_only && !continuous) {
+		blink = false;
+	}
+	return blink;
+}
+
 bool EditorSettings::is_dark_theme() {
 	int AUTO_COLOR = 0;
 	int LIGHT_COLOR = 2;

+ 1 - 0
editor/editor_settings.h

@@ -179,6 +179,7 @@ public:
 	void load_favorites();
 
 	bool is_dark_theme();
+	bool is_caret_blink_active() const;
 
 	void list_text_editor_themes();
 	void load_text_editor_theme();

+ 1 - 1
editor/plugins/shader_editor_plugin.cpp

@@ -370,7 +370,7 @@ void ShaderEditor::_editor_settings_changed() {
 	shader_editor->get_text_edit()->set_syntax_coloring(EditorSettings::get_singleton()->get("text_editor/highlighting/syntax_highlighting"));
 	shader_editor->get_text_edit()->set_highlight_all_occurrences(EditorSettings::get_singleton()->get("text_editor/highlighting/highlight_all_occurrences"));
 	shader_editor->get_text_edit()->set_highlight_current_line(EditorSettings::get_singleton()->get("text_editor/highlighting/highlight_current_line"));
-	shader_editor->get_text_edit()->cursor_set_blink_enabled(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink"));
+	shader_editor->get_text_edit()->cursor_set_blink_enabled(EditorSettings::get_singleton()->is_caret_blink_active());
 	shader_editor->get_text_edit()->cursor_set_blink_speed(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink_speed"));
 	shader_editor->get_text_edit()->add_constant_override("line_spacing", EditorSettings::get_singleton()->get("text_editor/theme/line_spacing"));
 	shader_editor->get_text_edit()->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret"));

+ 10 - 1
main/main.cpp

@@ -2277,7 +2277,16 @@ bool Main::iteration() {
 
 	if (OS::get_singleton()->can_draw() && VisualServer::get_singleton()->is_render_loop_enabled()) {
 		if ((!force_redraw_requested) && OS::get_singleton()->is_in_low_processor_usage_mode()) {
-			if (VisualServer::get_singleton()->has_changed()) {
+			// We can choose whether to redraw as a result of any redraw request, or redraw only for vital requests.
+			VisualServer::ChangedPriority priority = (OS::get_singleton()->is_update_pending() ? VisualServer::CHANGED_PRIORITY_ANY : VisualServer::CHANGED_PRIORITY_HIGH);
+
+			// Determine whether the scene has changed, to know whether to draw.
+			// If it has changed, inform the update pending system so it can keep
+			// particle systems etc updating when running in vital updates only mode.
+			bool has_changed = VisualServer::get_singleton()->has_changed(priority);
+			OS::get_singleton()->set_update_pending(has_changed);
+
+			if (has_changed) {
 				VisualServer::get_singleton()->draw(true, scaled_step); // flush visual commands
 				Engine::get_singleton()->frames_drawn++;
 			}

+ 6 - 3
scene/2d/cpu_particles_2d.cpp

@@ -30,6 +30,7 @@
 
 #include "cpu_particles_2d.h"
 #include "core/core_string_names.h"
+#include "core/os/os.h"
 #include "scene/2d/canvas_item.h"
 #include "scene/2d/particles_2d.h"
 #include "scene/resources/particles_material.h"
@@ -1028,9 +1029,11 @@ void CPUParticles2D::_set_redraw(bool p_redraw) {
 }
 
 void CPUParticles2D::_update_render_thread() {
-	update_mutex.lock();
-	VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
-	update_mutex.unlock();
+	if (OS::get_singleton()->is_update_pending(true)) {
+		update_mutex.lock();
+		VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
+		update_mutex.unlock();
+	}
 }
 
 void CPUParticles2D::_notification(int p_what) {

+ 9 - 6
scene/3d/cpu_particles.cpp

@@ -30,6 +30,7 @@
 
 #include "cpu_particles.h"
 
+#include "core/os/os.h"
 #include "scene/3d/camera.h"
 #include "scene/3d/particles.h"
 #include "scene/resources/particles_material.h"
@@ -1152,14 +1153,16 @@ void CPUParticles::_set_redraw(bool p_redraw) {
 }
 
 void CPUParticles::_update_render_thread() {
-	update_mutex.lock();
+	if (OS::get_singleton()->is_update_pending(true)) {
+		update_mutex.lock();
 
-	if (can_update.is_set()) {
-		VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
-		can_update.clear(); //wait for next time
-	}
+		if (can_update.is_set()) {
+			VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
+			can_update.clear(); //wait for next time
+		}
 
-	update_mutex.unlock();
+		update_mutex.unlock();
+	}
 }
 
 void CPUParticles::_notification(int p_what) {

+ 7 - 5
scene/animation/animation_tree.cpp

@@ -1236,12 +1236,14 @@ void AnimationTree::advance(float p_time) {
 }
 
 void AnimationTree::_notification(int p_what) {
-	if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) {
-		_process_graph(get_physics_process_delta_time());
-	}
+	if (active && OS::get_singleton()->is_update_pending()) {
+		if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) {
+			_process_graph(get_physics_process_delta_time());
+		}
 
-	if (active && p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) {
-		_process_graph(get_process_delta_time());
+		if (p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) {
+			_process_graph(get_process_delta_time());
+		}
 	}
 
 	if (p_what == NOTIFICATION_EXIT_TREE) {

+ 3 - 2
scene/animation/animation_tree_player.cpp

@@ -31,6 +31,7 @@
 #include "animation_tree_player.h"
 #include "animation_player.h"
 
+#include "core/os/os.h"
 #include "scene/scene_string_names.h"
 
 void AnimationTreePlayer::set_animation_process_mode(AnimationProcessMode p_mode) {
@@ -433,7 +434,7 @@ void AnimationTreePlayer::_notification(int p_what) {
 				break;
 			}
 
-			if (processing) {
+			if (processing && OS::get_singleton()->is_update_pending()) {
 				_process_animation(get_process_delta_time());
 			}
 		} break;
@@ -442,7 +443,7 @@ void AnimationTreePlayer::_notification(int p_what) {
 				break;
 			}
 
-			if (processing) {
+			if (processing && OS::get_singleton()->is_update_pending()) {
 				_process_animation(get_physics_process_delta_time());
 			}
 		} break;

+ 4 - 2
scene/gui/line_edit.cpp

@@ -687,7 +687,8 @@ void LineEdit::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 #ifdef TOOLS_ENABLED
 			if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) {
-				cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
+				EDITOR_DEF("text_editor/cursor/caret_blink", false);
+				cursor_set_blink_enabled(EditorSettings::get_singleton()->is_caret_blink_active());
 				cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
 
 				if (!EditorSettings::get_singleton()->is_connected("settings_changed", this, "_editor_settings_changed")) {
@@ -1669,7 +1670,8 @@ PopupMenu *LineEdit::get_menu() const {
 
 void LineEdit::_editor_settings_changed() {
 #ifdef TOOLS_ENABLED
-	cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
+	EDITOR_DEF("text_editor/cursor/caret_blink", false);
+	cursor_set_blink_enabled(EditorSettings::get_singleton()->is_caret_blink_active());
 	cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
 #endif
 }

+ 1 - 1
servers/visual/visual_server_canvas.cpp

@@ -172,7 +172,7 @@ void VisualServerCanvas::_render_canvas_item(Item *p_canvas_item, const Transfor
 	}
 
 	if (ci->update_when_visible) {
-		VisualServerRaster::redraw_request();
+		VisualServerRaster::redraw_request(false);
 	}
 
 	if ((!ci->commands.empty() && p_clip_rect.intersects(global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {

+ 16 - 4
servers/visual/visual_server_raster.cpp

@@ -40,7 +40,7 @@
 
 // careful, these may run in different threads than the visual server
 
-int VisualServerRaster::changes = 0;
+int VisualServerRaster::changes[2] = { 0 };
 
 /* BLACK BARS */
 
@@ -98,7 +98,8 @@ void VisualServerRaster::draw(bool p_swap_buffers, double frame_step) {
 	//needs to be done before changes is reset to 0, to not force the editor to redraw
 	VS::get_singleton()->emit_signal("frame_pre_draw");
 
-	changes = 0;
+	changes[0] = 0;
+	changes[1] = 0;
 
 	VSG::rasterizer->begin_frame(frame_step);
 
@@ -127,8 +128,19 @@ void VisualServerRaster::draw(bool p_swap_buffers, double frame_step) {
 }
 void VisualServerRaster::sync() {
 }
-bool VisualServerRaster::has_changed() const {
-	return changes > 0;
+
+bool VisualServerRaster::has_changed(ChangedPriority p_priority) const {
+	switch (p_priority) {
+		default: {
+			return (changes[0] > 0) || (changes[1] > 0);
+		} break;
+		case CHANGED_PRIORITY_LOW: {
+			return changes[0] > 0;
+		} break;
+		case CHANGED_PRIORITY_HIGH: {
+			return changes[1] > 0;
+		} break;
+	}
 }
 void VisualServerRaster::init() {
 	VSG::rasterizer->initialize();

+ 26 - 16
servers/visual/visual_server_raster.h

@@ -53,7 +53,8 @@ class VisualServerRaster : public VisualServer {
 
 	};
 
-	static int changes;
+	// low and high priority
+	static int changes[2];
 	RID test_cube;
 
 	int black_margin[4];
@@ -68,27 +69,36 @@ class VisualServerRaster : public VisualServer {
 	List<FrameDrawnCallbacks> frame_drawn_callbacks;
 
 	void _draw_margins();
-	static void _changes_changed() {}
 
-public:
-	//if editor is redrawing when it shouldn't, enable this and put a breakpoint in _changes_changed()
-	//#define DEBUG_CHANGES
+	// This function is NOT dead code.
+	// It is specifically for debugging redraws to help identify problems with
+	// undesired constant editor updating.
+	// The function will be called in DEV builds (and thus does not require a recompile),
+	// allowing you to place a breakpoint either at the first line or the semicolon.
+	// You can then look at the callstack to find the cause of the redraw.
+	static void _changes_changed(int p_priority) {
+		if (p_priority) {
+			;
+		}
+	}
 
-#ifdef DEBUG_CHANGES
-	_FORCE_INLINE_ static void redraw_request() {
-		changes++;
-		_changes_changed();
+public:
+	// if editor is redrawing when it shouldn't, use a DEV build and put a breakpoint in _changes_changed()
+	_FORCE_INLINE_ static void redraw_request(bool p_high_priority = true) {
+		int priority = p_high_priority ? 1 : 0;
+		changes[priority] += 1;
+#ifdef DEV_ENABLED
+		_changes_changed(priority);
+#endif
 	}
 
+#ifdef DEV_ENABLED
 #define DISPLAY_CHANGED \
-	changes++;          \
-	_changes_changed();
-
+	changes[1] += 1;    \
+	_changes_changed(1);
 #else
-	_FORCE_INLINE_ static void redraw_request() { changes++; }
-
 #define DISPLAY_CHANGED \
-	changes++;
+	changes[1] += 1;
 #endif
 
 #define BIND0R(m_r, m_name) \
@@ -736,7 +746,7 @@ public:
 
 	virtual void draw(bool p_swap_buffers, double frame_step);
 	virtual void sync();
-	virtual bool has_changed() const;
+	virtual bool has_changed(ChangedPriority p_priority = CHANGED_PRIORITY_ANY) const;
 	virtual void init();
 	virtual void finish();
 

+ 6 - 4
servers/visual/visual_server_scene.cpp

@@ -2633,7 +2633,7 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca
 			InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(ins->base_data);
 
 			if (ins->redraw_if_visible) {
-				VisualServerRaster::redraw_request();
+				VisualServerRaster::redraw_request(false);
 			}
 
 			if (ins->base_type == VS::INSTANCE_PARTICLES) {
@@ -2642,9 +2642,11 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca
 					//but if nothing is going on, don't do it.
 					keep = false;
 				} else {
-					VSG::storage->particles_request_process(ins->base);
-					//particles visible? request redraw
-					VisualServerRaster::redraw_request();
+					if (OS::get_singleton()->is_update_pending(true)) {
+						VSG::storage->particles_request_process(ins->base);
+						//particles visible? request redraw
+						VisualServerRaster::redraw_request(false);
+					}
 				}
 			}
 

+ 1 - 1
servers/visual/visual_server_wrap_mt.h

@@ -657,7 +657,7 @@ public:
 	virtual void finish();
 	virtual void draw(bool p_swap_buffers, double frame_step);
 	virtual void sync();
-	FUNC0RC(bool, has_changed)
+	FUNC1RC(bool, has_changed, ChangedPriority)
 
 	/* RENDER INFO */
 

+ 5 - 1
servers/visual_server.cpp

@@ -2230,7 +2230,7 @@ void VisualServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("free_rid", "rid"), &VisualServer::free); // shouldn't conflict with Object::free()
 
 	ClassDB::bind_method(D_METHOD("request_frame_drawn_callback", "where", "method", "userdata"), &VisualServer::request_frame_drawn_callback);
-	ClassDB::bind_method(D_METHOD("has_changed"), &VisualServer::has_changed);
+	ClassDB::bind_method(D_METHOD("has_changed", "queried_priority"), &VisualServer::has_changed, DEFVAL(CHANGED_PRIORITY_ANY));
 	ClassDB::bind_method(D_METHOD("init"), &VisualServer::init);
 	ClassDB::bind_method(D_METHOD("finish"), &VisualServer::finish);
 	ClassDB::bind_method(D_METHOD("get_render_info", "info"), &VisualServer::get_render_info);
@@ -2522,6 +2522,10 @@ void VisualServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(ENV_SSAO_BLUR_2x2);
 	BIND_ENUM_CONSTANT(ENV_SSAO_BLUR_3x3);
 
+	BIND_ENUM_CONSTANT(CHANGED_PRIORITY_ANY);
+	BIND_ENUM_CONSTANT(CHANGED_PRIORITY_LOW);
+	BIND_ENUM_CONSTANT(CHANGED_PRIORITY_HIGH);
+
 	ADD_SIGNAL(MethodInfo("frame_pre_draw"));
 	ADD_SIGNAL(MethodInfo("frame_post_draw"));
 }

+ 8 - 1
servers/visual_server.h

@@ -1103,9 +1103,15 @@ public:
 
 	/* EVENT QUEUING */
 
+	enum ChangedPriority {
+		CHANGED_PRIORITY_ANY = 0,
+		CHANGED_PRIORITY_LOW,
+		CHANGED_PRIORITY_HIGH,
+	};
+
 	virtual void draw(bool p_swap_buffers = true, double frame_step = 0.0) = 0;
 	virtual void sync() = 0;
-	virtual bool has_changed() const = 0;
+	virtual bool has_changed(ChangedPriority p_priority = CHANGED_PRIORITY_ANY) const = 0;
 	virtual void init() = 0;
 	virtual void finish() = 0;
 
@@ -1220,6 +1226,7 @@ VARIANT_ENUM_CAST(VisualServer::EnvironmentSSAOBlur);
 VARIANT_ENUM_CAST(VisualServer::InstanceFlags);
 VARIANT_ENUM_CAST(VisualServer::ShadowCastingSetting);
 VARIANT_ENUM_CAST(VisualServer::TextureType);
+VARIANT_ENUM_CAST(VisualServer::ChangedPriority);
 
 //typedef VisualServer VS; // makes it easier to use
 #define VS VisualServer