From dca552ea4896b18df038e65309fa2386482614c4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Jul 2024 10:31:02 +0200 Subject: [PATCH] Save `FrameState` of previous frame (#4761) By saving the `FrameState` of the previous frame, we make it much more versatile, and allows us to remove some other double-buffering. --- crates/egui/src/context.rs | 92 +++++++++++++++++----------------- crates/egui/src/frame_state.rs | 21 ++++---- crates/egui/src/response.rs | 3 +- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 8c08df68e91..c1c179f2ca0 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -220,18 +220,17 @@ pub struct ViewportState { pub input: InputState, - /// State that is collected during a frame and then cleared - pub frame_state: FrameState, + /// State that is collected during a frame and then cleared. + pub this_frame: FrameState, + + /// The final [`FrameState`] from last frame. + /// + /// Only read from. + pub prev_frame: FrameState, /// Has this viewport been updated this frame? pub used: bool, - /// Written to during the frame. - pub widgets_this_frame: WidgetRects, - - /// Read - pub widgets_prev_frame: WidgetRects, - /// State related to repaint scheduling. repaint: ViewportRepaintInfo, @@ -451,12 +450,12 @@ impl ContextImpl { let screen_rect = viewport.input.screen_rect; - viewport.frame_state.begin_frame(screen_rect); + viewport.this_frame.begin_frame(screen_rect); { let area_order = self.memory.areas().order_map(); - let mut layers: Vec = viewport.widgets_prev_frame.layer_ids().collect(); + let mut layers: Vec = viewport.prev_frame.widgets.layer_ids().collect(); layers.sort_by(|a, b| { if a.order == b.order { @@ -472,7 +471,7 @@ impl ContextImpl { let interact_radius = self.memory.options.style.interaction.interact_radius; crate::hit_test::hit_test( - &viewport.widgets_prev_frame, + &viewport.prev_frame.widgets, &layers, &self.memory.layer_transforms, pos, @@ -484,7 +483,7 @@ impl ContextImpl { viewport.interact_widgets = crate::interaction::interact( &viewport.interact_widgets, - &viewport.widgets_prev_frame, + &viewport.prev_frame.widgets, &viewport.hits, &viewport.input, self.memory.interaction_mut(), @@ -513,7 +512,7 @@ impl ContextImpl { builder.set_transform(accesskit::Affine::scale(pixels_per_point.into())); let mut node_builders = IdMap::default(); node_builders.insert(id, builder); - viewport.frame_state.accesskit_state = Some(AccessKitFrameState { + viewport.this_frame.accesskit_state = Some(AccessKitFrameState { node_builders, parent_stack: vec![id], }); @@ -573,12 +572,7 @@ impl ContextImpl { #[cfg(feature = "accesskit")] fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder { - let state = self - .viewport() - .frame_state - .accesskit_state - .as_mut() - .unwrap(); + let state = self.viewport().this_frame.accesskit_state.as_mut().unwrap(); let builders = &mut state.node_builders; if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); @@ -879,15 +873,27 @@ impl Context { } /// Read-only access to [`FrameState`]. + /// + /// This is only valid between [`Context::begin_frame`] and [`Context::end_frame`]. #[inline] pub(crate) fn frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { - self.write(move |ctx| reader(&ctx.viewport().frame_state)) + self.write(move |ctx| reader(&ctx.viewport().this_frame)) } /// Read-write access to [`FrameState`]. + /// + /// This is only valid between [`Context::begin_frame`] and [`Context::end_frame`]. #[inline] pub(crate) fn frame_state_mut(&self, writer: impl FnOnce(&mut FrameState) -> R) -> R { - self.write(move |ctx| writer(&mut ctx.viewport().frame_state)) + self.write(move |ctx| writer(&mut ctx.viewport().this_frame)) + } + + /// Read-only access to the [`FrameState`] from the previous frame. + /// + /// This is swapped at the end of each frame. + #[inline] + pub(crate) fn prev_frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().prev_frame)) } /// Read-only access to [`Fonts`]. @@ -1033,7 +1039,7 @@ impl Context { // We add all widgets here, even non-interactive ones, // because we need this list not only for checking for blocking widgets, // but also to know when we have reached the widget we are checking for cover. - viewport.widgets_this_frame.insert(w.layer_id, w); + viewport.this_frame.widgets.insert(w.layer_id, w); if w.sense.focusable { ctx.memory.interested_in_focus(w.id); @@ -1072,9 +1078,10 @@ impl Context { self.write(|ctx| { let viewport = ctx.viewport(); viewport - .widgets_this_frame + .this_frame + .widgets .get(id) - .or_else(|| viewport.widgets_prev_frame.get(id)) + .or_else(|| viewport.prev_frame.widgets.get(id)) .copied() }) .map(|widget_rect| self.get_response(widget_rect)) @@ -1098,7 +1105,8 @@ impl Context { enabled, } = widget_rect; - let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id)); + // previous frame + "highlight next frame" == "highlight this frame" + let highlighted = self.prev_frame_state(|fs| fs.highlight_next_frame.contains(&id)); let mut res = Response { ctx: self.clone(), @@ -1219,7 +1227,7 @@ impl Context { #[cfg(debug_assertions)] self.write(|ctx| { if ctx.memory.options.style.debug.show_interactive_widgets { - ctx.viewport().widgets_this_frame.set_info(id, make_info()); + ctx.viewport().this_frame.widgets.set_info(id, make_info()); } }); @@ -1832,7 +1840,7 @@ impl Context { let paint_widget_id = |id: Id, text: &str, color: Color32| { if let Some(widget) = - self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).copied()) + self.write(|ctx| ctx.viewport().this_frame.widgets.get(id).copied()) { paint_widget(&widget, text, color); } @@ -1840,7 +1848,7 @@ impl Context { if self.style().debug.show_interactive_widgets { // Show all interactive widgets: - let rects = self.write(|ctx| ctx.viewport().widgets_this_frame.clone()); + let rects = self.write(|ctx| ctx.viewport().this_frame.widgets.clone()); for (layer_id, rects) in rects.layers() { let painter = Painter::new(self.clone(), *layer_id, Rect::EVERYTHING); for rect in rects { @@ -1878,7 +1886,7 @@ impl Context { paint_widget_id(id, "contains_pointer", Color32::BLUE); } - let widget_rects = self.write(|w| w.viewport().widgets_this_frame.clone()); + let widget_rects = self.write(|w| w.viewport().this_frame.widgets.clone()); let mut contains_pointer: Vec = contains_pointer.iter().copied().collect(); contains_pointer.sort_by_key(|&id| { @@ -1945,7 +1953,7 @@ impl ContextImpl { viewport.repaint.frame_nr += 1; - self.memory.end_frame(&viewport.frame_state.used_ids); + self.memory.end_frame(&viewport.this_frame.used_ids); if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { let tex_mngr = &mut self.tex_manager.0.write(); @@ -1982,7 +1990,7 @@ impl ContextImpl { #[cfg(feature = "accesskit")] { crate::profile_scope!("accesskit"); - let state = viewport.frame_state.accesskit_state.take(); + let state = viewport.this_frame.accesskit_state.take(); if let Some(state) = state { let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = { @@ -2015,21 +2023,15 @@ impl ContextImpl { let mut repaint_needed = false; - { - if self.memory.options.repaint_on_widget_change { - crate::profile_function!("compare-widget-rects"); - if viewport.widgets_prev_frame != viewport.widgets_this_frame { - repaint_needed = true; // Some widget has moved - } + if self.memory.options.repaint_on_widget_change { + crate::profile_function!("compare-widget-rects"); + if viewport.prev_frame.widgets != viewport.this_frame.widgets { + repaint_needed = true; // Some widget has moved } - - std::mem::swap( - &mut viewport.widgets_prev_frame, - &mut viewport.widgets_this_frame, - ); - viewport.widgets_this_frame.clear(); } + std::mem::swap(&mut viewport.prev_frame, &mut viewport.this_frame); + if repaint_needed { self.request_repaint(ended_viewport_id, RepaintCause::new()); } else if let Some(delay) = viewport.input.wants_repaint_after() { @@ -2212,7 +2214,7 @@ impl Context { /// How much space is used by panels and windows. pub fn used_rect(&self) -> Rect { self.write(|ctx| { - let mut used = ctx.viewport().frame_state.used_by_panels; + let mut used = ctx.viewport().this_frame.used_by_panels; for (_id, window) in ctx.memory.areas().visible_windows() { used = used.union(window.rect()); } @@ -2879,7 +2881,7 @@ impl Context { ) -> Option { self.write(|ctx| { ctx.viewport() - .frame_state + .this_frame .accesskit_state .is_some() .then(|| ctx.accesskit_node_builder(id)) diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 1a4f5a71161..4e1aa390017 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -27,13 +27,18 @@ pub struct AccessKitFrameState { pub parent_stack: Vec, } -/// State that is collected during a frame and then cleared. -/// Short-term (single frame) memory. +/// State that is collected during a frame, then saved for the next frame, +/// and then cleared. +/// +/// One per viewport. #[derive(Clone)] pub struct FrameState { /// All [`Id`]s that were used this frame. pub used_ids: IdMap, + /// All widgets produced this frame. + pub widgets: WidgetRects, + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] does not change this. /// This is the area available to Window's. @@ -68,10 +73,7 @@ pub struct FrameState { #[cfg(feature = "accesskit")] pub accesskit_state: Option, - /// Highlight these widgets this next frame. Read from this. - pub highlight_this_frame: IdSet, - - /// Highlight these widgets the next frame. Write to this. + /// Highlight these widgets the next frame. pub highlight_next_frame: IdSet, #[cfg(debug_assertions)] @@ -82,6 +84,7 @@ impl Default for FrameState { fn default() -> Self { Self { used_ids: Default::default(), + widgets: Default::default(), available_rect: Rect::NAN, unused_rect: Rect::NAN, used_by_panels: Rect::NAN, @@ -90,7 +93,6 @@ impl Default for FrameState { scroll_delta: Vec2::default(), #[cfg(feature = "accesskit")] accesskit_state: None, - highlight_this_frame: Default::default(), highlight_next_frame: Default::default(), #[cfg(debug_assertions)] @@ -104,6 +106,7 @@ impl FrameState { crate::profile_function!(); let Self { used_ids, + widgets, available_rect, unused_rect, used_by_panels, @@ -112,7 +115,6 @@ impl FrameState { scroll_delta, #[cfg(feature = "accesskit")] accesskit_state, - highlight_this_frame, highlight_next_frame, #[cfg(debug_assertions)] @@ -120,6 +122,7 @@ impl FrameState { } = self; used_ids.clear(); + widgets.clear(); *available_rect = screen_rect; *unused_rect = screen_rect; *used_by_panels = Rect::NOTHING; @@ -137,7 +140,7 @@ impl FrameState { *accesskit_state = None; } - *highlight_this_frame = std::mem::take(highlight_next_frame); + highlight_next_frame.clear(); } /// How much space is still available after panels has been added. diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 8043843019f..706af9f01ab 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -609,7 +609,8 @@ impl Response { let layer_id = LayerId::new(Order::Tooltip, tooltip_id); let tooltip_has_interactive_widget = self.ctx.viewport(|vp| { - vp.widgets_prev_frame + vp.prev_frame + .widgets .get_layer(layer_id) .any(|w| w.sense.interactive()) });