Repository: godotengine/godot (language: cpp)

Issue:
Resizing a floating game window is slow
### Tested versions

- Reproducible in 4.4 Beta2 and 4.4 Beta1
- While in 4.3stable it is somewhat slow, it got considerably worse on 4.4 Beta1

### System information

Godot v4.4.beta1 - Windows 10 (build 19045) - Multi-window, 1 monitor - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1050 (NVIDIA; 31.0.15.5212) - Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz (4 threads)

### Issue description

When trying to resize the floating game window, the movement is not smooth and appears as low FPS

https://github.com/user-attachments/assets/7b58aa9e-a46e-4898-9537-adc5fab1279a

(video is from beta1 but also reproducible on beta2)

### Steps to reproduce

Create new project with empty Node2D scene and hit play, then resize game window

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
cc @Hilderin 
- Related to https://github.com/godotengine/godot/issues/39222.

Resizing a window will always be a slow operation with Vulkan as recreating the swapchain takes a while (definitely longer than OpenGL). Since game window embedding is used, Godot has to resize *two* swapchains every time the window is resized: one for the floating window, and one for the game being embedded (which is a second floating window).

Unfortunately, I don't see a way to optimize this, as a lot of the slowness is down to the OS and graphics driver here. If you switch to the Compatibility rendering method, it should appear to be noticeably faster.

We can however make the slowness less appearant by not requiring the game window to have its resize operation completed before you can see changes to the floating window. This means they will visibly go out of sync during resize operations, but it may be preferable to do this if it means it's smoother to resize the window.
> Resizing a window will always be a slow operation with Vulkan

That may as well be true, but I'd profile the resize before making a final judgement! It's always possible there's something else unexpected going on that's eating resources.
I'd do this myself but unfortunately there's no embedding available for my system yet!
After some testing the performance issue was caused by the `NOTIFICATION_PROCESS` and `NOTIFICATION_RESIZED` which triggered a deferred call to `EmbeddedProcess::_update_embedded_process` each frame which was really unnecessarily. I'll create a PR with a fix. It will not affect the re-rendering but rather the unresponsiveness of the window.

Fix patch:
diff --git a/editor/plugins/embedded_process.cpp b/editor/plugins/embedded_process.cpp
index 10c7188b5c53..1638c437125d 100644
--- a/editor/plugins/embedded_process.cpp
+++ b/editor/plugins/embedded_process.cpp
@@ -40,20 +40,6 @@ void EmbeddedProcess::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			window = get_window();
 		} break;
-		case NOTIFICATION_PROCESS: {
-			_check_focused_process_id();
-			_check_mouse_over();
-
-			// We need to detect when the control globally changes location or size on the screen.
-			// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
-			// resized parent to siblings controls that can affect global position.
-			Rect2i new_global_rect = get_global_rect();
-			if (last_global_rect != new_global_rect) {
-				last_global_rect = new_global_rect;
-				queue_update_embedded_process();
-			}
-
-		} break;
 		case NOTIFICATION_DRAW: {
 			_draw();
 		} break;
@@ -192,7 +178,7 @@ void EmbeddedProcess::embed_process(OS::ProcessID p_pid) {
 	current_process_id = p_pid;
 	start_embedding_time = OS::get_singleton()->get_ticks_msec();
 	embedding_grab_focus = has_focus();
-	set_process(true);
+	timer_update_embedded_process->start();
 	set_notify_transform(true);
 
 	// Attempt to embed the process, but if it has just started and the window is not ready yet,
@@ -209,7 +195,7 @@ void EmbeddedProcess::reset() {
 	start_embedding_time = 0;
 	embedding_grab_focus = false;
 	timer_embedding->stop();
-	set_process(false);
+	timer_update_embedded_process->stop();
 	set_notify_transform(false);
 	queue_redraw();
 }
@@ -242,18 +228,31 @@ bool EmbeddedProcess::_is_embedded_process_updatable() {
 }
 
 void EmbeddedProcess::queue_update_embedded_process() {
-	if (updated_embedded_process_queued || !_is_embedded_process_updatable()) {
-		return;
-	}
-
 	updated_embedded_process_queued = true;
+}
 
-	callable_mp(this, &EmbeddedProcess::_update_embedded_process).call_deferred();
+void EmbeddedProcess::_timer_update_embedded_process_timeout() {
+	_check_focused_process_id();
+	_check_mouse_over();
+
+	if (!updated_embedded_process_queued) {
+		// We need to detect when the control globally changes location or size on the screen.
+		// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
+		// resized parent to siblings controls that can affect global position.
+		Rect2i new_global_rect = get_global_rect();
+		if (last_global_rect != new_global_rect) {
+			last_global_rect = new_global_rect;
+			queue_update_embedded_process();
+		}
+	}
+
+	if (updated_embedded_process_queued) {
+		updated_embedded_process_queued = false;
+		_update_embedded_process();
+	}
 }
 
 void EmbeddedProcess::_update_embedded_process() {
-	updated_embedded_process_queued = false;
-
 	if (!_is_embedded_process_updatable()) {
 		return;
 	}
@@ -352,6 +351,12 @@ EmbeddedProcess::EmbeddedProcess() {
 	timer_embedding->set_one_shot(true);
 	add_child(timer_embedding);
 	timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout));
+
+	timer_update_embedded_process = memnew(Timer);
+	timer_update_embedded_process->set_wait_time(0.1);
+	add_child(timer_update_embedded_process);
+	timer_update_embedded_process->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_update_embedded_process_timeout));
+
 	set_focus_mode(FOCUS_ALL);
 }
 
diff --git a/editor/plugins/embedded_process.h b/editor/plugins/embedded_process.h
index 14ad44fdaedd..3e800923a75d 100644
--- a/editor/plugins/embedded_process.h
+++ b/editor/plugins/embedded_process.h
@@ -48,6 +48,7 @@ class EmbeddedProcess : public Control {
 
 	Window *window = nullptr;
 	Timer *timer_embedding = nullptr;
+	Timer *timer_update_embedded_process = nullptr;
 
 	const int embedding_timeout = 45000;
 
@@ -61,6 +62,7 @@ class EmbeddedProcess : public Control {
 	void _try_embed_process();
 	void _update_embedded_process();
 	void _timer_embedding_timeout();
+	void _timer_update_embedded_process_timeout();
 	void _draw();
 	void _check_mouse_over();
 	void _check_focused_process_id();
diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
index d96ee851374e..f87f38d5a620 100644
--- a/editor/plugins/game_view_plugin.cpp
+++ b/editor/plugins/game_view_plugin.cpp
@@ -977,6 +977,11 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
 	game_size_label = memnew(Label());
 	main_menu_hbox->add_child(game_size_label);
 	game_size_label->hide();
+	// Setting the minimum size prevents the game workspace from resizing indefinitely
+	// due to the label size oscillating by a few pixels when the game is in stretch mode
+	// and the game workspace is at its minimum size.
+	game_size_label->set_custom_minimum_size(Size2(80 * EDSCALE, 0));
+	game_size_label->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT);
 
 	panel = memnew(Panel);
 	add_child(panel);


Test patch: