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

Abierto
#26,525 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
45/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
macos, rust

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
Rust
Estrellas
49.7k
Forks
5k
Merge medio
3 d 19 h
PR fusionados (30 d)
134

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de bevyengine/bevy

Todos los issues de bevyengine/bevy

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.