Repository: godotengine/godot (language: cpp)

Issue:
Game and editor freeze when clicking on the game's title bar
### Tested versions

reproducible in v4.4.beta1.official [d33da79d3] through v4.4.beta3.official [06acfccf8]
not reproducible in v4.4.dev7.official [46c8f8c5c] and earlier

### System information

Godot v4.4.beta3 - Windows 11 (build 26100) - Multi-window, 2 monitors - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3050 (NVIDIA; 32.0.15.6636) - AMD Ryzen 5 5600G with Radeon Graphics (12 threads)

### Issue description

When using the embed game option, touching the title bar of the window containing the game freezes the game and the editor for several seconds (usually just a couple but sometimes it's seemingly permanent)

If the floating workspace option is on, the freeze only happens when interacting with the game window's title bar.

If the floating workspace option is off and the game is embedded directly in the editor's window, the freeze happens when interacting with the editor's title bar.

If embedding is disabled altogether, the title bar is fine and can be interacted with without freezing anything.

The embedded toolbar (the bar under the title bar and above the game) can be interacted with just fine, the same with the game itself.

The resizing of the game's or the editor's window does not cause freezing. Though the game's viewport lags behind the window, which does not happen if embedding is disabled.

The title bar buttons (minimize, maximize, and close) are reacting to hover fine.
Clicking the minimize button seems to work every time without freezing.
Clicking the maximize button works sometimes, but sometimes can cause permanent freeze.
Clicking the close button works sometimes, but sometimes can cause permanent freeze.

Freezing does not seem to increase CPU or memory usage.

This is also independent on the renderer: Forward+, Mobile, and Compatibility are all affected.

### Steps to reproduce

1. Open any project in Godot 4.4 beta 3
2. Make sure the "embed game on next play" is on
3. Run the project
4. Click or drag on the title bar of the window in which the game is embedded
5. The windows freeze 😭

### Minimal reproduction project (MRP)

N/A (happens in any project)


Discussion / hints:
Confirmed on Windows 11. I'm bisecting it now.
Bisecting points to 9d2a4c03be5e06b08cefc9a7676a5fc71a4e546e / https://github.com/godotengine/godot/pull/99010, CC @Hilderin. As @bruvzg pointed out in RC, this may be a preexisting bug with embedded windows.

The behavior I can reproduce looks like this. With a floating window with an embedded game view, when dragging the floating game window, the game itself keeps running but the window freezes for several seconds before dragging.

https://github.com/user-attachments/assets/13c90036-28f3-4cd8-8323-bc64a18bb46c

I focused the game view itself first (note the blue outline) because I suspected that focus has something to do with the problem. If the containing window is focused, not the game view itself, I can't reproduce the freeze before dragging:

https://github.com/user-attachments/assets/0ebc09e5-cc5f-4d3a-91a5-f2496b960f65

System info: Godot v4.4.beta3.mono - Windows 11 (build 26100) - Multi-window, 2 monitors - OpenGL 3 (Compatibility) - NVIDIA GeForce RTX 3080 (NVIDIA; 32.0.15.6636) - AMD Ryzen 9 7950X 16-Core Processor (32 threads)

Fix patch:
diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
index fa274f242ccd..f604e977ce58 100644
--- a/editor/plugins/game_view_plugin.cpp
+++ b/editor/plugins/game_view_plugin.cpp
@@ -243,20 +243,29 @@ void GameView::_show_update_window_wrapper() {
 	Size2i size = floating_window_rect.size;
 	int screen = floating_window_screen;
 
-	Size2 wrapped_margins_size = window_wrapper->get_margins_size();
-	Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
-	offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
-	offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
-
 	// Obtain the size around the embedded process control. Usually, the difference between the game view's get_size
 	// and the embedded control should work. However, when the control is hidden and has never been displayed,
 	// the size of the embedded control is not calculated.
 	Size2 old_min_size = embedded_process->get_custom_minimum_size();
 	embedded_process->set_custom_minimum_size(Size2i());
-	Size2 min_size = get_minimum_size();
+
+	Size2 embedded_process_min_size = get_minimum_size();
+	Size2 wrapped_margins_size = window_wrapper->get_margins_size();
+	Size2 wrapped_min_size = window_wrapper->get_minimum_size();
+	Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
+
+	// On the first startup, the global position of the embedded process control is invalid because it was
+	// never displayed. We will calculated it manually using the minimum size of the window.
+	if (offset_embedded_process == Point2()) {
+		offset_embedded_process.y = wrapped_min_size.y;
+	}
+	offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
+	offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
+	offset_embedded_process += window_wrapper->get_margins_top_left();
+
 	embedded_process->set_custom_minimum_size(old_min_size);
 
-	Point2 size_diff_embedded_process = Point2(0, min_size.y) + embedded_process->get_margins_size();
+	Point2 size_diff_embedded_process = Point2(0, embedded_process_min_size.y) + embedded_process->get_margins_size();
 
 	if (placement.position != Point2i(INT_MAX, INT_MAX)) {
 		position = placement.position - offset_embedded_process;
@@ -786,8 +795,21 @@ void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_argumen
 
 	// Be sure to have the correct window size in the embedded_process control.
 	_update_embed_window_size();
-
 	Rect2i rect = embedded_process->get_screen_embedded_window_rect();
+
+	// When using the floating window, we need to force the position and size from the
+	// editor/project settings, because the get_screen_embedded_window_rect of the
+	// embedded_process will be updated only on the next frame.
+	if (p_idx == 0 && window_wrapper->get_window_enabled()) {
+		EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
+		if (placement.position != Point2i(INT_MAX, INT_MAX)) {
+			rect.position = placement.position;
+		}
+		if (placement.size != Size2i()) {
+			rect.size = placement.size;
+		}
+	}
+
 	N = r_arguments.insert_after(N, "--position");
 	N = r_arguments.insert_after(N, itos(rect.position.x) + "," + itos(rect.position.y));
 	N = r_arguments.insert_after(N, "--resolution");
diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp
index 4d82df70db96..6d24a9fd1a06 100644
--- a/editor/window_wrapper.cpp
+++ b/editor/window_wrapper.cpp
@@ -338,6 +338,14 @@ Size2 WindowWrapper::get_margins_size() {
 	return Size2(margins->get_margin_size(SIDE_LEFT) + margins->get_margin_size(SIDE_RIGHT), margins->get_margin_size(SIDE_TOP) + margins->get_margin_size(SIDE_RIGHT));
 }
 
+Size2 WindowWrapper::get_margins_top_left() {
+	if (!margins) {
+		return Size2();
+	}
+
+	return Size2(margins->get_margin_size(SIDE_LEFT), margins->get_margin_size(SIDE_TOP));
+}
+
 void WindowWrapper::grab_window_focus() {
 	if (get_window_enabled() && is_visible()) {
 		window->grab_focus();
diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h
index 360c2858ae3a..3fedf3979e25 100644
--- a/editor/window_wrapper.h
+++ b/editor/window_wrapper.h
@@ -86,6 +86,7 @@ class WindowWrapper : public MarginContainer {
 	void set_window_title(const String &p_title);
 	void set_margins_enabled(bool p_enabled);
 	Size2 get_margins_size();
+	Size2 get_margins_top_left();
 	void grab_window_focus();
 
 	void set_override_close_request(bool p_enabled);
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 6f92cc0d6219..ceed5a9d5e4a 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -6172,6 +6172,15 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
 
 		wd.parent_hwnd = p_parent_hwnd;
 
+		// Detach the input queue from the parent window.
+		// This prevents the embedded window from waiting on the main window's input queue,
+		// causing lags input lags when resizing or moving the main window.
+		if (p_parent_hwnd) {
+			DWORD mainThreadId = GetWindowThreadProcessId(owner_hwnd, nullptr);
+			DWORD embeddedThreadId = GetCurrentThreadId();
+			AttachThreadInput(embeddedThreadId, mainThreadId, FALSE);
+		}
+
 		if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
 			wd.fullscreen = true;
 			if (p_mode == WINDOW_MODE_FULLSCREEN) {


Test patch: