changed_windows calls slow current_monitor() on every Window change (macOS)

Aperta
#26,525 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
macos, rust

Direzione di ricerca

Start with listen_for_ime_input_when_text_input_focused and update_ime_position in the bevy_ui_widgets text input plugin, which write ime_enabled and ime_position on Window every frame. Then read changed_windows in bevy_winit, where current_monitor() runs for every changed window. Done when those writes happen only on real value changes and the monitor lookup is skipped when position, size and scale factor are unchanged, which you can check by sampling an idle macOS run.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

C-Bug C-Performance S-Needs-Triage

Bevy version

  • Original: 0.19.0
  • Current: 0.20.1 (the code involved is unchanged on main as of 2026-11-13)

Relevant system information

  • cargo 1.98.0-nightly (598ab48ec 2026-06-17)
  • macOS 26 (Darwin 25.5), Apple Silicon, 120 Hz ProMotion display
  • DefaultPlugins with default features (so bevy_ui_widgets is included)

What's performing poorly?

After upgrading our game from 0.19.0 to 0.20.1, it dropped from 120 to 60 fps under vsync on macOS. It's a 3D game using DefaultPlugins; we use no Bevy text inputs (all text fields are egui).

Profiling showed bevy_winit::system::changed_windows running every frame, even with no input at all. Most of its time is spent in winit::Window::current_monitor(). On macOS that goes through CGDisplayCreateUUIDFromDisplayID / SLSCopyDisplayUUID, a round trip to the window server costing about 1 ms per call. That took roughly 10% of the main thread, enough to push frames past the 8.3 ms budget of a 120 Hz display.

There are two causes.

1. TextInputPlugin marks the window changed every frame. In bevy_ui_widgets:

  • listen_for_ime_input_when_text_input_focused does window.ime_enabled = ime_wanted; every frame, unconditionally;
  • update_ime_position does window.ime_position = ...; every frame while a text input is focused.

Both writes go through Mut<Window>, so Changed<Window> fires even when the value is unchanged, and changed_windows does its full sync every frame. This affects any app using DefaultPlugins, whether it has text inputs or not.

2. changed_windows looks up the monitor on every Window change. Even with cause 1 gone, changed_windows calls current_monitor() for every window that changed that frame. Moving the mouse changes the Window (bevy_winit writes the cursor position into it on CursorMoved). In a game the cursor moves on most frames (aiming, clicking, camera drag), so the ~1 ms lookup still runs on most frames during normal play.

To reproduce on macOS: any app with DefaultPlugins and a window; profile it while idle and while moving the mouse over the window, and look for changed_windows → current_monitor.

Before and After Traces

These are 10-second macOS sample profiles of the same 0.20.1 build, idle (no input), uncapped present mode. I don't have a 0.19 trace.

  • Before (TextInputPlugin enabled, the default): changed_windows → current_monitor in 526 of 5271 main-thread samples. create_monitors in 251.
  • After (DefaultPlugins.build().disable::<bevy::ui_widgets::TextInputPlugin>()): changed_windows absent. create_monitors in 2. Main-thread time in App::update down about 20% (4479 → 3546 samples).

before.txt
after.txt

I haven't profiled the mouse-moving case (cause 2) separately.

Additional information

  • Workaround for cause 1: disable TextInputPlugin if you don't use Bevy text inputs. Cause 2 has no workaround short of patching bevy_winit.
  • Possible fix for cause 1: only assign ime_enabled / ime_position when the value differs.
  • Possible fix for cause 2: a window can only end up on another monitor when its position, size or scale factor changes. changed_windows could store those from the last lookup and only call current_monitor() when one differs, or when the window has no OnMonitor yet. outer_position(), outer_size() and scale_factor() are cheap on macOS (they read the NSWindow frame). Where the position isn't available (Wayland), it could fall back to the current behaviour.
Lingua principale
Rust
Stelle
49.7k
Fork
5k
Merge medio
3g 19h
PR unite (30g)
134

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di bevyengine/bevy

Tutte le issue di bevyengine/bevy

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.