-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Fix perf degradation on web builds #11227
Fix perf degradation on web builds #11227
Conversation
@daxpedda: does this look like the right fix to you? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the ping @alice-i-cecile!
LGTM!
I also noticed that Bevy doesn't take WindowEvent::Occluded
into account when drawing. I will open a separate issue for that.
EDIT: #11229.
crates/bevy_winit/src/lib.rs
Outdated
create_window_system_state.apply(&mut app.world); | ||
let (_, winit_windows, _, _) = event_writer_system_state.get_mut(&mut app.world); | ||
for window in winit_windows.windows.values() { | ||
window.request_redraw(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preferably also call Window::request_redraw()
in WindowEvent::RedrawRequested
.
I would generally recommend not to use Event::AboutToWait
at all unless you have a very specific use-case in mind.
FYI, this has probably affected all platforms, it's just especially bad on Web because |
Tried the example "sprite" on firefox osx ; On main: Firefox_mac_profile_main_425570aa752b32ef.json.gz I'm not too sure how to read those traces so I'll refrain from drawing conclusions, I'll test on native too. |
So I did profile this locally as well now and the performance is pretty bad compared to Winit v0.28 because it's still using Unfortunately I'm pretty unfamiliar with how Bevy uses Winit exactly, but There are two ways to solve this:
|
I'm doing an audit of how bevy handles the winit loop. I've noticed there is a few different knobs, so I want to have an overall view of it before I feel confident about this PR. It's also an opportunity to document the full behavior and fix any incidental weirdness. |
Looking at the
Looks like bevy relies implicitly on VSync, with With a bit more exploration (thanks to the discussion around #11233 (discord link (not that relevant, but included for completeness sake))) I understand bevy uses the However, bevy uses pipelined rendering, so it's not as trivial as this. What really happen is a complex process:
Now, on the web, this changes, as bevy doesn't do any threading on WASM. I'm not exactly sure how it works though, but looking at the source for The |
The conclusion is that |
This is not the case on Wasm unfortunately. Using
If I understand correctly Bevy isn't calling |
Oops, sorry for saying erroneous things in #11227 (comment). It looks like the blocking call is The Hunting your comments on this repository. I understand you are suggesting using a combination of two things:
Do I understand correctly that Though it probably mixes poorly with frame pacing plugins. Footnotes |
This is a mistake imo, unfortunately the Winit docs are in need of some very serious improvements.
In either case you probably don't want to use
The thing is that frame pacing should (at least currently) not be done with the help of Winit. Rather some API is needed to query timings which should then control when rendering happens, this would require not drawing in In reality of course you want to do this in a different thread and not in the Winit event loop to begin with. Keep in mind that I'm unfamiliar with how Bevy frame pacing plugins currently function. In any case, I recommend against using Unless you have a very specific use-case in mind, this is usually not what you want. That said, on Web |
61d801b
to
6ec7ac0
Compare
The @daxpedda thank you for taking the time to walk me through all this. Yeah frame pacing is an external crate right now https://github.com/aevyrie/bevy_framepace. It uses the most naive approach of measuring roughly frame time and then sleep just before the end of the |
Fwiw. I Tested this branch on both desktop and physical mobile device. Edit: Only web targets were tested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code seems sensible, it's had expert feedback, and this fixes our performance regression. Once we get a final look-over from someone else I'll merge this in.
I really really want to make this part of our code base more clean, but fix comes first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that I'm unfamiliar with Bevy, but LGTM!
// TODO(clean): winit docs recommends calling pre_present_notify before this. | ||
// though `present()` doesn't present the frame, it schedules it to be presented | ||
// by wgpu. | ||
// https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling Window::pre_present_notify()
before SurfaceTexture::present()
has worked fine for me so far.
Looking at the Vulkan and OpenGL implementations (because this is no-op on everything except Wayland), I can't see a queue, but I'm definitely no Wgpu expert. Would be nice to get clarification here from somebody knowledgeable about Wgpu.
This PR broke iOS and Android... Changes to the event loop should be tested on all supported platforms |
@mockersf native iOS and Android targets? or web targets? |
Native! #11245 should fix it |
…dow creation (#11245) # Objective - Since #11227, Bevy doesn't work on mobile anymore. Windows are not created. ## Solution - Create initial window on mobile after the initial `Resume` event. macOS is included because it's excluded from the other initial window creation and I didn't want it to feel alone. Also, it makes sense. this is needed for Android https://github.com/bevyengine/bevy/blob/cfcb6885e3b475a93ec0fe7e88023ac0f354bbbf/crates/bevy_winit/src/lib.rs#L152 - request redraw during plugin initialisation (needed for WebGPU) - request redraw when receiving `AboutToWait` instead of at the end of the event handler. request to redraw during a `RedrawRequested` event are ignored on iOS
Objective
Solution
Event::WindowEvent { event: WindowEvent::RedrawRequested }
branch of the event loop.window.request_redraw()
Whenrunner_state.redraw_requested
ControlFlow
betweenPoll
andWait
, we always keep it atWait
, and usewindow.request_redraw()
to schedule an immediate call to the event loop.runner_state.redraw_requested
is set totrue
whenUpdateMode::Continuous
and when aRequestRedraw
event is received.Testing
I tested the WASM builds as follow:
cargo run -p build-wasm-example -- --api webgl2 bevymark python -m http.server --directory examples/wasm/ 8080 # Open browser at http://localhost:8080
On main, even spawning a couple sprites is super choppy. Even if it says "300 FPS". While on this branch, it is smooth as butter.
I also found that it fixes all choppiness on window resize (tested on Linux/X11). This was another issue from #10702 IIRC.
So here is what I tested:
wasm
:many_foxes
andbevymark
, withargh::from_env()
commented out, otherwise we get a cryptic error.PresentMode::AutoVsync
andPresentMode::AutoNoVsync
AutoVsync
limits to monitor framerate, andAutoNoVsync
doesn't.Future work
Code could be improved, I wanted a quick solution easy to review, but we really need to make the code more accessible.
actually broken on main as wellWinitSettings::desktop_app()
is completely borked.Review guide
Consider enable the non-whitespace diff to see the real change set.