Skip to content

Add example to test the smoothness/accuracy of the timestep #4865

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

Conversation

nfagerlund
Copy link
Contributor

@nfagerlund nfagerlund commented May 29, 2022

Objective

Bevy's timestep is sometimes inaccurate, which can manifest as hiccups and stutters in what should
be smooth motion. This is most clearly visible when using pixel-perfect movement with large
visible pixels.

It would be good to have some kind of shared baseline for any conversations about hiccups, stutters, and other time-based jank!

Solution

This PR adds a new example (in the stress tests directory) that scrolls a background behind a character at 120
pixels per second; by default, it makes sure to always move in whole-pixel increments. You can modify
some behavior while it's running by pressing keys:

  • P: cycle between different window::PresentModes. (NOTE: Window.set_present_mode() doesn't appear to actually work at the moment! Not sure what's up there and would appreciate any hints.)
  • W: cycle between different window::WindowModes. (NOTE: Window.set_resolution() doesn't appear to do anything? I might be using it wrong?)
  • T: cycle between updating scroll position based on Time.delta_seconds(), and updating it by
    just assuming a 16.667 millisecond timestep (only useful in PresentMode::Fifo on a 60fps display).
  • M: cycle between whole-pixel movement and simple sub-pixel movement.

Changelog

Added a time_smoothness example (under stress_tests) for qualitatively evaluating the smoothness and accuracy of delta time.


Videos

A couple of videos of weird stuttering in action on Windows.

This computer is a desktop with a Haswell Core i3-4160 (entry-level processor from 2014), 8gb of ram, and a GTX 1060 (3gb) video card, running Windows 10 Home 21H2. It's quite modest! But it runs a wide range of commercial games with solid and respectable performance, and scrolling a background probably shouldn't look as bad as it does here.

Windowed mode:

2022-05-24.00-03-43.bevy.windowed.mp4

Borderless fullscreen:

2022-05-24.00-05-52.bevy.fullscreen.mp4

@alice-i-cecile alice-i-cecile added C-Examples An addition or correction to our examples C-Performance A change motivated by improving speed, memory usage or compile times A-Core labels May 29, 2022
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm impressed; this should be very useful! A few administrative notes:

  1. Please add a module style doc comment to the top of your example.
  2. Please add the example to examples/README.md

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I suggested on Discord, I'd like to see some numbers reported periodically in the console. To start, I think "mean, standard deviation, min and max frame time" for the last 10 seconds should be enough to give testers a better sense of what's going on.

You should be able to get this information from the FrameTimeDiagnosticsPlugin (I think in a resource); ping if you can't figure it out.

@nfagerlund
Copy link
Contributor Author

@alice-i-cecile Thanks for the review! I'll add the comments/links now, and I'll look into doing math on the diags resource later this week.

@alice-i-cecile
Copy link
Member

@nfagerlund you can press "Resolve Comment" on the feedback that you've addressed, which keeps the thread tidier for reviewers :)

@nfagerlund nfagerlund force-pushed the nf/may22-time-smoothness-stresstest branch from f2b244c to 7e80881 Compare June 8, 2022 06:20
@nfagerlund
Copy link
Contributor Author

@alice-i-cecile Ok! I've added some frame time analysis to the example. I had to roll my own diagnostic, since it turns out the built-in one only keeps a non-configurable 20 frames, but now it prints this every 10 seconds:

2022-06-08T06:21:55.395210Z  INFO time_smoothness: -------------------------
2022-06-08T06:21:55.395538Z  INFO time_smoothness: Average frame time: 0.016681
2022-06-08T06:21:55.395631Z  INFO time_smoothness: Standard deviation: 0.000569
2022-06-08T06:21:55.395689Z  INFO time_smoothness: Shortest frame:     0.006377
2022-06-08T06:21:55.395752Z  INFO time_smoothness: 95th percentile:    0.016797
2022-06-08T06:21:55.395840Z  INFO time_smoothness: 99th percentile:    0.017011
2022-06-08T06:21:55.395928Z  INFO time_smoothness: 99.5th percentile:  0.017293
2022-06-08T06:21:55.396014Z  INFO time_smoothness: Longest frame:      0.026963
2022-06-08T06:21:55.396077Z  INFO time_smoothness: -------------------------

I still need to go deal with Clippy, but figured I'd paste that before the output scrolls out of my terminal.

@nfagerlund nfagerlund force-pushed the nf/may22-time-smoothness-stresstest branch from 7e80881 to c996988 Compare June 8, 2022 06:51
@nfagerlund
Copy link
Contributor Author

OK, all hail clippy, TIL about .copied().

@alice-i-cecile
Copy link
Member

Awesome, that's super useful both as a snippet and as a testing tool.

@nfagerlund
Copy link
Contributor Author

Hmm, I can't tell why CI is failing.

| ^^^^^^^^^^^^^^^^^^^^^^^^use of undeclared type `OrthographicCameraBundle`
# ...
| ^^^^^^^^^^^^^^use of undeclared type `UiCameraBundle`

I think those are in the prelude?? It compiles and runs?? What's going on here.

@hymm
Copy link
Contributor

hymm commented Jun 8, 2022

They were renamed/refactored in this PR #4745.

@nfagerlund
Copy link
Contributor Author

Oh, I also want to surface a question from the original post that maybe got lost — I have code in there that calls Window.set_present_mode() and Window.set_resolution(), and neither one of those seems to actually work. Anyone know what's up with those?

@nfagerlund
Copy link
Contributor Author

@hymm oh! Hmm, so the CI job must be merging or rebasing, instead of running on the branch. I'll rebase later and sort out the rename. thank you!

@alice-i-cecile
Copy link
Member

Oh, I also want to surface a question from the original post that maybe got lost — I have code in there that calls Window.set_present_mode() and Window.set_resolution(), and neither one of those seems to actually work. Anyone know what's up with those?

IIRC you had to set this before calling .add_default_plugins, due to a quirk in the way winit owns the control loop :(

@nfagerlund
Copy link
Contributor Author

Oh... huh. I mean, I'm adding the window descriptor resource before that, so the initial settings take effect just fine. But so, can they just not be changed at runtime? Why would the functions on Window exist, then? I don't think you can even get hold of a Window struct until the app's running, yeah?

Maybe I should remove those config bits and just tell users to edit and recompile. :/

@aevyrie
Copy link
Member

aevyrie commented Jun 8, 2022

Could you change the stats to ms instead of s? Easier to read at those scales.

@alice-i-cecile
Copy link
Member

But so, can they just not be changed at runtime? Why would the functions on Window exist, then? I don't think you can even get hold of a Window struct until the app's running, yeah?

This is about my feeling. Can you open an issue?

Bevy's timestep is sometimes inaccurate, and sometimes it misses its frame timings. Both
of these can manifest as hiccups and stutters in what should be smooth motion. This is
most clearly visible when using pixel-perfect movement with large visible pixels.

This new example (in the stress tests directory) scrolls a background behind a character at 120
pixels per second; by default, it makes sure to always move in whole-pixel increments. You can modify
some behavior while it's running by pressing keys:

- P: cycle between different `window::PresentMode`s.
- W: cycle between different `window::WindowMode`s.
- T: cycle between updating scroll position based on `Time.delta_seconds()`, and updating it by
  just assuming a 16.667 millisecond timestep (only useful in `PresentMode::Fifo` on a 60fps display).
- M: cycle between whole-pixel movement and simple sub-pixel movement.
@nfagerlund nfagerlund force-pushed the nf/may22-time-smoothness-stresstest branch from 80580eb to af82dff Compare June 27, 2022 00:57
@nfagerlund
Copy link
Contributor Author

OK, I'm back!!

  • I changed the frame time analysis to report in milliseconds.
  • I updated stuff that had fallen out of date with main, including the new camera types and the blurry-by-default texture smoothing. And I think I got the new examples metadata into shape correctly; we'll see in a sec.
  • I filed an issue for those inert Window methods, Window.set_resolution() and set_present_mode() don't have expected side-effects #5111 -- it looks like they're intended to work, so I left the example using them as advertised.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I'd be more comfortable merging this if you can drop a comment on the broken window commands linking to the issue in question.

I think it's probably worth leaving them in, if only to make it easy to test fixes to that. Other opinions would be welcome though.

Outside of that question, I'm very happy with this now.

Copy link
Contributor

@hymm hymm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

The back of the Y in bevy is getting cut off for me on every other scroll by.

}

fn std_deviation(diagnostic: &Diagnostic) -> Option<f64> {
if let Some(average) = diagnostic.average() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the average is being calculated both in this function and in the above function. Could we remove one of these?

const BG_WIDTH: f32 = 755.0;
const BG_HEIGHT: f32 = 363.0;
const BG_TILES: usize = 3;
const BG_SPEED: f32 = 120.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a recommendation here to change this number for different monitors. This is always going to hit the "big jump" warning on a 30Hz monitor and will probably always look jumpy on a 144Hz monitor.

@hymm hymm self-requested a review January 28, 2023 19:06
@ItsDoot ItsDoot added the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Aug 19, 2024
@bas-ie
Copy link
Contributor

bas-ie commented Oct 2, 2024

Closing as part of backlog cleanup: still seems like an interesting example, but would need significant revision and may not be as relevant as Bevy matures.

@bas-ie bas-ie closed this Oct 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Examples An addition or correction to our examples C-Performance A change motivated by improving speed, memory usage or compile times S-Adopt-Me The original PR author has no intent to complete this work. Pick me up!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants