Add InputState::stable_dt
(#1625)
This provides a better estimate of a typical frametime in reactive mode. From the docstring of `stable_dt`: Time since last frame (in seconds), but gracefully handles the first frame after sleeping in reactive mode. In reactive mode (available in e.g. `eframe`), `egui` only updates when there is new input or something animating. This can lead to large gaps of time (sleep), leading to large [`Self::unstable_dt`]. If `egui` requested a repaint the previous frame, then `egui` will use `stable_dt = unstable_dt;`, but if `egui` did not not request a repaint last frame, then `egui` will assume `unstable_dt` is too large, and will use `stable_dt = predicted_dt;`. This means that for the first frame after a sleep, `stable_dt` will be a prediction of the delta-time until the next frame, and in all other situations this will be an accurate measurement of time passed since the previous frame. Note that a frame can still stall for various reasons, so `stable_dt` can still be unusually large in some situations. When animating something, it is recommended that you use something like `stable_dt.min(0.1)` - this will give you smooth animations when the framerate is good (even in reactive mode), but will avoid large jumps when framerate is bad, and will effectively slow down the animation when FPS drops below 10.
This commit is contained in:
parent
931e716b97
commit
2e4138d050
3 changed files with 54 additions and 8 deletions
|
@ -6,8 +6,9 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui-w
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
* Add `*_released` & `*_clicked` methods for `PointerState` ([#1582](https://github.com/emilk/egui/pull/1582)).
|
* Added `*_released` & `*_clicked` methods for `PointerState` ([#1582](https://github.com/emilk/egui/pull/1582)).
|
||||||
* Optimize painting of filled circles (e.g. for scatter plots) by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
|
* Optimized painting of filled circles (e.g. for scatter plots) by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
|
||||||
|
* Added `InputState::stable_dt`: a more stable estimate for the delta-time in reactive mode ([#1625](https://github.com/emilk/egui/pull/1625)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Fixed `ImageButton`'s changing background padding on hover ([#1595](https://github.com/emilk/egui/pull/1595)).
|
* Fixed `ImageButton`'s changing background padding on hover ([#1595](https://github.com/emilk/egui/pull/1595)).
|
||||||
|
|
|
@ -50,13 +50,15 @@ struct ContextImpl {
|
||||||
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
||||||
repaint_requests: u32,
|
repaint_requests: u32,
|
||||||
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
|
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
|
||||||
|
requested_repaint_last_frame: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextImpl {
|
impl ContextImpl {
|
||||||
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
|
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
|
||||||
self.memory.begin_frame(&self.input, &new_raw_input);
|
self.memory.begin_frame(&self.input, &new_raw_input);
|
||||||
|
|
||||||
self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input);
|
self.input = std::mem::take(&mut self.input)
|
||||||
|
.begin_frame(new_raw_input, self.requested_repaint_last_frame);
|
||||||
|
|
||||||
if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() {
|
if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() {
|
||||||
self.input.pixels_per_point = new_pixels_per_point;
|
self.input.pixels_per_point = new_pixels_per_point;
|
||||||
|
@ -803,6 +805,7 @@ impl Context {
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
self.write().requested_repaint_last_frame = needs_repaint;
|
||||||
|
|
||||||
let shapes = self.drain_paint_lists();
|
let shapes = self.drain_paint_lists();
|
||||||
|
|
||||||
|
|
|
@ -67,14 +67,43 @@ pub struct InputState {
|
||||||
|
|
||||||
/// Time since last frame, in seconds.
|
/// Time since last frame, in seconds.
|
||||||
///
|
///
|
||||||
/// This can be very unstable in reactive mode (when we don't paint each frame)
|
/// This can be very unstable in reactive mode (when we don't paint each frame).
|
||||||
/// so it can be smart to use e.g. `unstable_dt.min(1.0 / 30.0)`.
|
/// For animations it is therefore better to use [`Self::stable_dt`].
|
||||||
pub unstable_dt: f32,
|
pub unstable_dt: f32,
|
||||||
|
|
||||||
|
/// Estimated time until next frame (provided we repaint right away).
|
||||||
|
///
|
||||||
/// Used for animations to get instant feedback (avoid frame delay).
|
/// Used for animations to get instant feedback (avoid frame delay).
|
||||||
/// Should be set to the expected time between frames when painting at vsync speeds.
|
/// Should be set to the expected time between frames when painting at vsync speeds.
|
||||||
|
///
|
||||||
|
/// On most integrations this has a fixed value of `1.0 / 60.0`, so it is not a very accurate estimate.
|
||||||
pub predicted_dt: f32,
|
pub predicted_dt: f32,
|
||||||
|
|
||||||
|
/// Time since last frame (in seconds), but gracefully handles the first frame after sleeping in reactive mode.
|
||||||
|
///
|
||||||
|
/// In reactive mode (available in e.g. `eframe`), `egui` only updates when there is new input
|
||||||
|
/// or something is animating.
|
||||||
|
/// This can lead to large gaps of time (sleep), leading to large [`Self::unstable_dt`].
|
||||||
|
///
|
||||||
|
/// If `egui` requested a repaint the previous frame, then `egui` will use
|
||||||
|
/// `stable_dt = unstable_dt;`, but if `egui` did not not request a repaint last frame,
|
||||||
|
/// then `egui` will assume `unstable_dt` is too large, and will use
|
||||||
|
/// `stable_dt = predicted_dt;`.
|
||||||
|
///
|
||||||
|
/// This means that for the first frame after a sleep,
|
||||||
|
/// `stable_dt` will be a prediction of the delta-time until the next frame,
|
||||||
|
/// and in all other situations this will be an accurate measurement of time passed
|
||||||
|
/// since the previous frame.
|
||||||
|
///
|
||||||
|
/// Note that a frame can still stall for various reasons, so `stable_dt` can
|
||||||
|
/// still be unusually large in some situations.
|
||||||
|
///
|
||||||
|
/// When animating something, it is recommended that you use something like
|
||||||
|
/// `stable_dt.min(0.1)` - this will give you smooth animations when the framerate is good
|
||||||
|
/// (even in reactive mode), but will avoid large jumps when framerate is bad,
|
||||||
|
/// and will effectively slow down the animation when FPS drops below 10.
|
||||||
|
pub stable_dt: f32,
|
||||||
|
|
||||||
/// Which modifier keys are down at the start of the frame?
|
/// Which modifier keys are down at the start of the frame?
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
|
|
||||||
|
@ -97,8 +126,9 @@ impl Default for InputState {
|
||||||
pixels_per_point: 1.0,
|
pixels_per_point: 1.0,
|
||||||
max_texture_side: 2048,
|
max_texture_side: 2048,
|
||||||
time: 0.0,
|
time: 0.0,
|
||||||
unstable_dt: 1.0 / 6.0,
|
unstable_dt: 1.0 / 60.0,
|
||||||
predicted_dt: 1.0 / 6.0,
|
predicted_dt: 1.0 / 60.0,
|
||||||
|
stable_dt: 1.0 / 60.0,
|
||||||
modifiers: Default::default(),
|
modifiers: Default::default(),
|
||||||
keys_down: Default::default(),
|
keys_down: Default::default(),
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
|
@ -108,9 +138,18 @@ impl Default for InputState {
|
||||||
|
|
||||||
impl InputState {
|
impl InputState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn begin_frame(mut self, new: RawInput) -> InputState {
|
pub fn begin_frame(mut self, new: RawInput, requested_repaint_last_frame: bool) -> InputState {
|
||||||
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
||||||
let unstable_dt = (time - self.time) as f32;
|
let unstable_dt = (time - self.time) as f32;
|
||||||
|
|
||||||
|
let stable_dt = if requested_repaint_last_frame {
|
||||||
|
// we should have had a repaint straight away,
|
||||||
|
// so this should be trustable.
|
||||||
|
unstable_dt
|
||||||
|
} else {
|
||||||
|
new.predicted_dt
|
||||||
|
};
|
||||||
|
|
||||||
let screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
|
let screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
|
||||||
self.create_touch_states_for_new_devices(&new.events);
|
self.create_touch_states_for_new_devices(&new.events);
|
||||||
for touch_state in self.touch_states.values_mut() {
|
for touch_state in self.touch_states.values_mut() {
|
||||||
|
@ -150,6 +189,7 @@ impl InputState {
|
||||||
time,
|
time,
|
||||||
unstable_dt,
|
unstable_dt,
|
||||||
predicted_dt: new.predicted_dt,
|
predicted_dt: new.predicted_dt,
|
||||||
|
stable_dt,
|
||||||
modifiers: new.modifiers,
|
modifiers: new.modifiers,
|
||||||
keys_down,
|
keys_down,
|
||||||
events: new.events.clone(), // TODO: remove clone() and use raw.events
|
events: new.events.clone(), // TODO: remove clone() and use raw.events
|
||||||
|
@ -788,6 +828,7 @@ impl InputState {
|
||||||
time,
|
time,
|
||||||
unstable_dt,
|
unstable_dt,
|
||||||
predicted_dt,
|
predicted_dt,
|
||||||
|
stable_dt,
|
||||||
modifiers,
|
modifiers,
|
||||||
keys_down,
|
keys_down,
|
||||||
events,
|
events,
|
||||||
|
@ -830,6 +871,7 @@ impl InputState {
|
||||||
1e3 * unstable_dt
|
1e3 * unstable_dt
|
||||||
));
|
));
|
||||||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||||
|
ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt));
|
||||||
ui.label(format!("modifiers: {:#?}", modifiers));
|
ui.label(format!("modifiers: {:#?}", modifiers));
|
||||||
ui.label(format!("keys_down: {:?}", keys_down));
|
ui.label(format!("keys_down: {:?}", keys_down));
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
|
Loading…
Reference in a new issue