godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
86.61k stars 19.31k forks source link

Game consumes 100% CPU if the window is covered by another OS window (macOS) #64708

Open BigZaphod opened 1 year ago

BigZaphod commented 1 year ago

Godot version

v3.5.stable.official [991bb6ac7]

System information

macOS 12.5.1 (21G83), Apple M1 Max MacBook Pro

Issue description

Simply covering up the game's window with another OS window causes CPU usage to spike to 100%. You can see this in action in the video:

https://user-images.githubusercontent.com/453721/185812416-203ed323-0884-4a5b-950d-c93d099716c9.mov

Steps to reproduce

Run the attached project (or any project, really - the attached one is essentially empty and has no scripts) on macOS. Drag a window over top of the game's window (it needs to cover it completely). Watch as CPU usage skyrockets.

My uninformed suspicion is that macOS detects when the window is covered and stops sending rendering/v-sync events to the app in an effort to save power. Godot may be waiting for those events in an inefficient polling loop and when they stop coming that loop starts burning up cycles.

Godot now has the distinction of being the first app on my M1 Max that's actually managed to get the fan to run because of this. I noticed it because I have a habit of leaving the game open behind other windows when I switch back to the editor to keep working on it. I don't think this is an uncommon habit, tbh, and this behavior would have caused Godot to very rapidly deplete my laptop's battery if I hadn't happened to be running while plugged in.

Minimal reproduction project

Godot 100% CPU Bug.zip

Calinou commented 1 year ago

Related to https://github.com/godotengine/godot/issues/26605. Enabling Update Vital Only in the Editor Settings may reduce CPU usage (as well as increasing Low Processor Mode Sleep Usec to something like 33000).

Does minimizing the window instead of alt-tabbing avoid this?

cc @lawnjelly @bruvzg

BigZaphod commented 1 year ago

(Accidentally closed this instead of commenting... SIGH. Hopefully that didn't mess something up.)

Behaviors I'm seeing:

bruvzg commented 1 year ago

Related to https://github.com/godotengine/godot/issues/26605. Enabling Update Vital Only in the Editor Settings may reduce CPU usage (as well as increasing Low Processor Mode Sleep Usec to something like 33000).

This will affect only the editor, if you want the same behavior from the game, you should process focus-in/focus-out notifications to set/unset low CPU mode, like:

func _notification(what):
    if what == NOTIFICATION_APPLICATION_FOCUS_IN:
        OS.set_low_processor_usage_mode(false)
    elif what == NOTIFICATION_APPLICATION_FOCUS_OUT:
        OS.set_low_processor_usage_mode_sleep_usec(33000)
        OS.set_low_processor_usage_mode(true)
lawnjelly commented 1 year ago

Yes as @bruvzg says, vital updates is only for editor, not for running game.

100% CPU usage is expected when vsync is not working, and I've occasionally seen this happen on linux (when in a game menu I think) when vsync was set to on, and the frame rate jumped from 60 to 1000 or so. No idea what was causing this or been able to produce an MRP - this was in a menu mode of the game where only 2d and no 3d was rendered. (Maybe it was wrongly being caused by a lose focus message or something?)

BigZaphod commented 1 year ago

100% CPU usage is expected when vsync is not working

I don't see how this should be expected or acceptable behavior. When vsync is enabled the expectation is that the app will wait until the notification to draw and therefore if that notification never comes, then it should never draw and continue waiting. Falling back to a 100% CPU-eating state just because the vsync signal doesn't come when the app wanted it to isn't right at all and circumvents the whole reason macOS is likely stopping sending vsync signals in this case which is to save power.

Luckily this is very trivial to replicate on macOS, so if the same problem is somehow occurring on other operating systems perhaps the cause is similar in that Godot is perhaps expecting vsync signals at some rate and when it doesn't get them at that rate it becomes upset and starts acting as if vsync is disabled rather than waiting patiently for the vsync signal to arrive.

Calinou commented 1 year ago

Now that Godot can get the screen refresh rate, I wonder if we should enforce a FPS limit slightly higher than the monitor refresh rate when V-Sync is enabled. This would also help in situations where V-Sync fails to kick in, which has been shown to happen in several occasions.

One downside of this is that if you move the window from a monitor to another with a higher refresh rate, it will remain capped to the start monitor's refresh rate unless we have some kind of logic to periodically update the framerate cap.

This will affect only the editor, if you want the same behavior from the game, you should process focus-in/focus-out notifications to set/unset low CPU mode, like:

Automatically enabling low processor mode on focus loss is something I hadn't thought of before. I think this should be done by default (perhaps disableable with a project setting if needed).

If this is done, then https://github.com/godotengine/godot-proposals/issues/2001 would adjust the low processor mode sleep duration instead of enabling a FPS cap with Engine.target_fps when the window is unfocused or minimized.

BigZaphod commented 1 year ago

I added this script (slightly modified from @bruvzg's suggestion because the notification names didn't exist):

func _notification(what):
    if what == NOTIFICATION_WM_FOCUS_IN:
        print("focus in")
        OS.set_low_processor_usage_mode(false)
    elif what == NOTIFICATION_WM_FOCUS_OUT:
        print("focus out")
        OS.set_low_processor_usage_mode_sleep_usec(33000)
        OS.set_low_processor_usage_mode(true)

And it does help - the CPU usage of the empty project sits around 9-10% when it's in the foreground but the moment I click over to Activity Monitor with this script active in the scene, it drops to around 3% or so. Covering the game window or hiding it, etc. doesn't change anything - it still stays around 3%.

If something like this was the default behavior I think it'd made a lot of sense.

Curiously, though, the editor itself was only consuming something like 1-2% CPU while it was backgrounded in this situation, so clearly Godot somehow knows how to do the right thing already but it's not carrying that behavior over to the game window for whatever reason.

Calinou commented 1 year ago

Curiously, though, the editor itself was only consuming something like 1-2% CPU while it was backgrounded in this situation, so clearly Godot somehow knows how to do the right thing already but it's not carrying that behavior over to the game window for whatever reason.

The editor has low processor mode enabled by default, and is limited to 10 FPS when unfocused (sleep duration is set to 100000). This is not done in projects by default, but it's technically feasible as I said above.

cubez commented 1 year ago

Just wanted to add that I also have seen this behaviour in previous 3.x versions on Mac OS and would love to see this added for projects.

lostminds commented 1 year ago

While I'm not getting 100% CPU or GPU on Godot 4.0.beta9 mobile renderer (macOS 13.0.1), I'm seeing the same weird issue that the CPU and GPU load goes up instead of down when the game window is completely obscured.

If it's any help NSWindow (the macOS window class) has a property occlusionState that could be used to determine if any part of the window is visible on screen to check if it should draw. Perhaps for example in connection to https://github.com/godotengine/godot/blob/56b828eb82861747b687433c41a8215cfdf8831f/platform/macos/display_server_macos.mm#L2955-L2957 Currently just seems to check for the window being minimized, and when minimizing the window the CPU and GPU goes down to almost 0, so that works. Maybe it should also check for the window being hidden, as in visible=false

hiulit commented 1 year ago

Automatically enabling low processor mode on focus loss is something I hadn't thought of before. I think this should be done by default (perhaps disableable with a project setting if needed).

I'm having the same problem with unfocused windows raising the CPU to ~50% on my Mac and we were discussing on Twitter about adding a toggle on the Project Settings to enable/disable low_processor_usage_mode to false/true when the windows if focused/unfocused.

I don't know if it should be enabled by default, but a toggle would be definately the way to go. Should be open a new proposal for that?

For now I'm using this (Godot 3.x):

func _notification(what: int) -> void:
    if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT:
        OS.low_processor_usage_mode = true

    if what == MainLoop.NOTIFICATION_WM_FOCUS_IN:
        OS.low_processor_usage_mode = false
Calinou commented 1 year ago

I don't know if it should be enabled by default, but a toggle would be definately the way to go. Should be open a new proposal for that?

This is already being tracked in https://github.com/godotengine/godot-proposals/issues/2001 (which I've modified to mention low-processor mode).

akien-mga commented 9 months ago

Could you test #83096 and see if it fixes the issue? You can download build artifacts from the CI there: https://github.com/godotengine/godot/pull/83096/checks ("Artifacts" drop down list in the top right area.)

BigZaphod commented 9 months ago

Could you test #83096 and see if it fixes the issue? You can download build artifacts from the CI there: https://github.com/godotengine/godot/pull/83096/checks ("Artifacts" drop down list in the top right area.)

A quick test seems to suggest this appears to work for me. When I run the sample project that was attached to the original bug report in this new version of Godot (after letting Godot upgrade it of course), it runs with around 10-12% CPU while visible (which seems right), but then when I cover the app's window up entirely with another window, CPU usage drops to around 3% (sometimes less) and sits at the same level the editor's own process seems to sit at. The same happens when I minimize the window.

For a point of comparison, I ran this same project using v4.2.dev6.official [57a6813bb] and there's no CPU usage change at all when I cover the game window - it remains using ~10-12% CPU. So the bug where it exploded to 100% CPU must have been fixed elsewhere since this was first reported, but it seems that this new change improves the situation even more.

lawnjelly commented 9 months ago

Reopening as still needs fixing in 3.x (original issue was for 3.x) so changing milestone.

stuartcarnie commented 1 week ago

For reference, using 374807f427eec5ee7caebfc509a158fe715a6bfe, CPU is about 12% when the window is not occluded. When fully occluded, the CPU usage drops to 2% for both Vulkan and Metal renderers