Low FPS on iOS, framerate dips extra when many touch events are triggered
Description
Hi!
Winit is an awesome project, I've had a great experience using it so far. Platform support has been solid but iOS has been confusing.
Note that my usage is currently on 0.30.9 and I haven't tested the unreleased changes, so if the following has already been addressed please feel free to close this!
I was able to get my project working on iOS by using the trick of calling request_redraw inside of about_to_wait instead of at the end of the RedrawRequested event. I then noticed however that something seems to be adding a lot of overhead and making the app quite slow. Particularly when many touch events are being triggered (for example when dragging a finger across the screen), the continual firing of touch events can drop my framerate to as low as 50% compared to baseline. I am not doing any expensive processing in my touch event handling.
As a sanity check I've also tested my project with the method in wgpu-in-app, and confirmed that I can get almost 2x the framerate without any dips during constant touch event handling.
I'd really appreciate any insight on what could be causing it, and if there's any way to improve / work around this.
Thanks!
Device and iOS version
iPhone 12, iOS 18.3.2
Winit version
0.30.9
Is this comment saying that relying on about_to_wait could be causing the issue? Is there any alternative for iOS?
https://github.com/bevyengine/bevy/pull/11245#discussion_r1445890320
I ran into the exact same issue on iOS. Calls to request_redraw() inside RedrawRequested were being ignored, so I moved them into about_to_wait() and that fixed the redraw problem. However, I then saw CPU usage spike to 100% whenever touch events flooded in, and the solution was to gate the deferred redraw call with a simple bool flag (only request when needed).
Put a bool field in the struct that implements ApplicationHandler:
/// When `true`, enables an iOS-specific redraw workaround.
///
/// On iOS there’s a bug where calls to `window.request_redraw()` issued during
/// the `WindowEvent::RedrawRequested` callback are ignored, but are honored
/// if deferred to the `about_to_wait` event. Setting this flag will delay the
/// redraw request until `about_to_wait`.
#[cfg(target_os = "ios")]
ios_request_redraw: bool
In window_event():
fn window_event(
&mut self,
_event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent
) {
// ...
match event {
// ...
WindowEvent::RedrawRequested => {
if self.loop_state != LoopState::Resumed {
return;
}
// Redraw...
#[cfg(not(target_os = "ios"))]
window_context.request_redraw();
#[cfg(target_os = "ios")]
{
self.ios_request_redraw = true;
}
}
// ...
}
// ...
}
In about_to_wait():
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
// ...
event_loop.set_control_flow(ControlFlow::Wait);
// TODO: We put request_redraw here for iOS. Maybe this changes in the future.
#[cfg(target_os = "ios")]
{
if (self.loop_state == LoopState::Resumed)
&& self.ios_request_redraw
{
engine.window_context.request_redraw();
}
self.ios_request_redraw = false;
}
// ...
}
Ah thank you that is helpful!
I was doing something similar but backwards. I was triggering redraw immediately inside about_to_wait with a flag to throttle it, but clearing the flag inside the RedrawRequested event instead of setting it.
Using it the way you've shown here seems to improve performance a lot! Thanks :)
Great 👍