godot icon indicating copy to clipboard operation
godot copied to clipboard

Title Bar interactions halt frame updates

Open Repiteo opened this issue 2 years ago • 5 comments

Godot version

4.0.stable

System information

Windows 11, Forward+ (applicable to all)

Issue description

Attempting to click and hold a game window's title bar will pause all frame updates. This behavior occurs for both windows created by the editor & exported exes.

This pause will last up to half a second (rarely, a full second) if the window is held that entire time. The pause cuts off preemptively if the mouse moves, dragging the window. Holding the "exit" button will pause frame updates indefinitely, regardless of mouse movement; only releasing the button will resume frame updates (assuming the mouse isn't still hovering over the button, otherwise the application closes). Physics ticks still occur, capping at max_physics_steps_per_frame.

Steps to reproduce

https://user-images.githubusercontent.com/17489292/223154654-028192aa-890d-432f-a5d1-cf90fbb6043d.mp4

Minimal reproduction project

BugReport.zip

Repiteo avatar Mar 06 '23 15:03 Repiteo

It is related to this section of code, but it is likely required for Windows management but not sure what a workaround would be, removing the timer signal here will freeze it until let go

I suspect this is to prevent errors with events while moving the window, but not sure why processing comes back on after the delay and why it cannot process immediately, at the same time what is the use case of having the game able to run while this occurs?

Explained here: #5516

https://github.com/godotengine/godot/blob/3695fe5a573678c6491a6a33f19f9329d3256a48/platform/windows/display_server_windows.cpp#L3386-L3398

AThousandShips avatar Mar 06 '23 17:03 AThousandShips

Okay, so that is indeed somewhat related. It seems like the workaround it's implementing is generally working, as when WM_ENTERSIZEMOVE is properly called it goes into the fallback timer as expected. Then WM_EXITSIZEMOVE is called when releasing the mouse button. For maximizing/minimizing and resizing the window this method works exactly as expected

The issue is this check fails to account for holding the title bar without moving, but for some reason recognizes it should be updating after about half a second. This updating recognizes the WM_ENTERSIZEMOVE case, so I guess there's some buffer at play

The "close" issue seems to be entirely seperate, as that process doesn't call either WM_ENTERSIZEMOVE or WM_EXITSIZEMOVE. It likely needs its own specific check similar to this old resize-window solution, as the indefinite-pause seems much more like what that pull request was aiming to solve (albeit for moving a window)

https://user-images.githubusercontent.com/17489292/223236318-f85a89fa-5388-468f-851e-5567f28c71a7.mp4

Repiteo avatar Mar 06 '23 21:03 Repiteo

Looks like I spoke too soon with the maximizing/minimizing working as expected, as their buttons share the same bug as the close button

https://user-images.githubusercontent.com/17489292/223510310-cdd5050a-3ab3-4d86-b95d-6c614969fdcf.mp4

Repiteo avatar Mar 07 '23 18:03 Repiteo

Can you reproduce this in 3.5.2?

Calinou avatar Mar 09 '23 21:03 Calinou

@Calinou Yep; identical symptoms

https://user-images.githubusercontent.com/17489292/224191046-cb63c713-1950-4ad1-8cc5-35f46ca8bba4.mp4

BugReportBackport.zip

Repiteo avatar Mar 10 '23 00:03 Repiteo

This is a bit of an odd one, so let me write this down before I forget:

When you click on the title bar, a WM_NCLBUTTONDOWN message with nHittest == HTCAPTION is posted to the window. This in turn generates a WM_SYSCOMMAND with SC_MOVE (the exact value is 0xF012, "unofficially documented" as SC_DRAGMOVE), which when passed to DefWindowProc will start the window move process.

The window move process itself runs a blocking message loop, which does not return until the move process is completed or cancelled. Godot (as well as most Windows applications) relies on this loop to send messages to its WndProc. If the mouse has not moved, the first messages we receive are WM_CAPTURECHANGED, WM_GETMINMAXINFO, and WM_ENTERSIZEMOVE. However, before this happens, there is a roughly 0.5 second interval in which we receive absolutely nothing. I've tried setting a timer in WM_SYSCOMMAND but it has no effect (the message pump simply wasn't running).

I couldn't really find much info about this but I did find one relevant comment in Chromium's source: https://chromium.googlesource.com/chromium/src/+/b2bb9a538c11705a6b3ae210e23adcd7bb2f6e4b/ui/views/win/hwnd_message_handler.cc#3532

  // If we are receive a WM_NCLBUTTONDOWN messsage for the caption, the
  // following things happen.
  // 1. This message is defproced which will post a WM_SYSCOMMAND message with
  //    SC_MOVE and a constant 0x0002 which is documented on msdn as
  //    SC_DRAGMOVE.
  // 2. The WM_SYSCOMMAND message is defproced in our handler which works
  //    correctly in all cases except the case when we click on the caption
  //    and hold. The defproc appears to try and detect whether a mouse move
  //    is going to happen presumably via the DragEnter or a similar
  //    implementation which in its modal loop appears to only peek for
  //    mouse input briefly.
  // 3. Our workhorse message pump relies on the tickler posted message to get
  //    control during modal loops which does not happen in the above case for
  //    a while leading to the problem where activity on the page pauses
  //    briefly or at times stops for a while.
  // To fix this we don't defproc the WM_NCLBUTTONDOWN message and instead wait
  // for the subsequent WM_NCMOUSEMOVE/WM_MOUSEMOVE message. Once we receive
  // these messages and the mouse actually moved, we defproc the
  // WM_NCLBUTTONDOWN message.

Full context on https://codereview.chromium.org/1504333013 .

I think we will have to chuck this into the "Windows quirks" bucket and find some kind of hack / workaround. The way Chromium deals with it seems a bit scary but I guess it is worth trying.

alvinhochun avatar Jul 16 '24 14:07 alvinhochun