Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
BigZaphod opened this issue Aug 21, 2022 · 16 comments · Fixed by #83096
Open

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

BigZaphod opened this issue Aug 21, 2022 · 16 comments · Fixed by #83096

Comments

@BigZaphod
Copy link

Godot version

v3.5.stable.official [991bb6a]

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:

Screen.Recording.2022-08-21.at.4.43.46.PM.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
Copy link
Member

Calinou commented Aug 22, 2022

Related to #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
Copy link
Author

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

Behaviors I'm seeing:

  • Minimizing the game window causes the same problem. As far as I can tell, it makes no difference.

  • Switching away with CMD-Tab causes it, too, as long as the game window ends up hidden (so if I CMD-Tab back to the editor window and it's fully overlapping the game window).

  • Low processor mode mitigates it but it still clearly goes up when the game window is behind. (This sample goes from about 10% cpu when active to about 20-25% when the window is hidden and low processor mode is enabled.)

@bruvzg
Copy link
Member

bruvzg commented Aug 22, 2022

Related to #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
Copy link
Member

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
Copy link
Author

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
Copy link
Member

Calinou commented Aug 22, 2022

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 godotengine/godot-proposals#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
Copy link
Author

BigZaphod commented Aug 22, 2022

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
Copy link
Member

Calinou commented Aug 22, 2022

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
Copy link

cubez commented Aug 27, 2022

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
Copy link

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

bool DisplayServerMacOS::window_can_draw(WindowID p_window) const {
return window_get_mode(p_window) != WINDOW_MODE_MINIMIZED;
}

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
Copy link

hiulit commented Apr 21, 2023

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
Copy link
Member

Calinou commented Apr 21, 2023

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 godotengine/godot-proposals#2001 (which I've modified to mention low-processor mode).

github-merge-queue bot pushed a commit to bevyengine/bevy that referenced this issue Aug 2, 2023
…s not visible (#7611)

Fixes #5856. Fixes #8080. Fixes #9040.

# Objective

We need to limit the update rate of games whose windows are not visible
(minimized or completely occluded). Compositors typically ignore the
VSync settings of windows that aren't visible. That, combined with the
lack of rendering work, results in a scenario where an app becomes
completely CPU-bound and starts updating without limit.

There are currently three update modes.
- `Continuous` updates an app as often as possible.
- `Reactive` updates when new window or device events have appeared, a
timer expires, or a redraw is requested.
- `ReactiveLowPower` is the same as `Reactive` except it ignores device
events (e.g. general mouse movement).

The problem is that the default "game" settings are set to `Contiuous`
even when no windows are visible.

### More Context

- libsdl-org/SDL#1871
- glfw/glfw#680
- godotengine/godot#19741
- godotengine/godot#64708

## Solution

Change the default "unfocused" `UpdateMode` for games to
`ReactiveLowPower` just like desktop apps. This way, even when the
window is occluded, the app still updates at a sensible rate or if
something about the window changes. I chose 20Hz arbitrarily.
@akien-mga akien-mga added this to the 4.2 milestone Oct 10, 2023
@akien-mga
Copy link
Member

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
Copy link
Author

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 [57a6813] 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
Copy link
Member

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

@lawnjelly lawnjelly reopened this Oct 11, 2023
@lawnjelly lawnjelly modified the milestones: 4.2, 3.x Oct 11, 2023
Subserial pushed a commit to Subserial/bevy_winit_hook that referenced this issue Jan 24, 2024
…s not visible (#7611)

Fixes #5856. Fixes #8080. Fixes #9040.

# Objective

We need to limit the update rate of games whose windows are not visible
(minimized or completely occluded). Compositors typically ignore the
VSync settings of windows that aren't visible. That, combined with the
lack of rendering work, results in a scenario where an app becomes
completely CPU-bound and starts updating without limit.

There are currently three update modes.
- `Continuous` updates an app as often as possible.
- `Reactive` updates when new window or device events have appeared, a
timer expires, or a redraw is requested.
- `ReactiveLowPower` is the same as `Reactive` except it ignores device
events (e.g. general mouse movement).

The problem is that the default "game" settings are set to `Contiuous`
even when no windows are visible.

### More Context

- libsdl-org/SDL#1871
- glfw/glfw#680
- godotengine/godot#19741
- godotengine/godot#64708

## Solution

Change the default "unfocused" `UpdateMode` for games to
`ReactiveLowPower` just like desktop apps. This way, even when the
window is occluded, the app still updates at a sensible rate or if
something about the window changes. I chose 20Hz arbitrarily.
@stuartcarnie
Copy link
Contributor

stuartcarnie commented Jun 27, 2024

For reference, using 374807f, 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment