Skip to content

Commit

Permalink
Save FrameState of previous frame (#4761)
Browse files Browse the repository at this point in the history
By saving the `FrameState` of the previous frame, we make it much more
versatile, and allows us to remove some other double-buffering.
  • Loading branch information
emilk authored Jul 3, 2024
1 parent 1b06c69 commit dca552e
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 55 deletions.
92 changes: 47 additions & 45 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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<LayerId> = viewport.widgets_prev_frame.layer_ids().collect();
let mut layers: Vec<LayerId> = viewport.prev_frame.widgets.layer_ids().collect();

layers.sort_by(|a, b| {
if a.order == b.order {
Expand All @@ -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,
Expand All @@ -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(),
Expand Down Expand Up @@ -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],
});
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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<R>(&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<R>(&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<R>(&self, reader: impl FnOnce(&FrameState) -> R) -> R {
self.write(move |ctx| reader(&ctx.viewport().prev_frame))
}

/// Read-only access to [`Fonts`].
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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))
Expand All @@ -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(),
Expand Down Expand Up @@ -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());
}
});

Expand Down Expand Up @@ -1832,15 +1840,15 @@ 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);
}
};

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 {
Expand Down Expand Up @@ -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<Id> = contains_pointer.iter().copied().collect();
contains_pointer.sort_by_key(|&id| {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -2879,7 +2881,7 @@ impl Context {
) -> Option<R> {
self.write(|ctx| {
ctx.viewport()
.frame_state
.this_frame
.accesskit_state
.is_some()
.then(|| ctx.accesskit_node_builder(id))
Expand Down
21 changes: 12 additions & 9 deletions crates/egui/src/frame_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ pub struct AccessKitFrameState {
pub parent_stack: Vec<Id>,
}

/// 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<Rect>,

/// 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.
Expand Down Expand Up @@ -68,10 +73,7 @@ pub struct FrameState {
#[cfg(feature = "accesskit")]
pub accesskit_state: Option<AccessKitFrameState>,

/// 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)]
Expand All @@ -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,
Expand All @@ -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)]
Expand All @@ -104,6 +106,7 @@ impl FrameState {
crate::profile_function!();
let Self {
used_ids,
widgets,
available_rect,
unused_rect,
used_by_panels,
Expand All @@ -112,14 +115,14 @@ impl FrameState {
scroll_delta,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_this_frame,
highlight_next_frame,

#[cfg(debug_assertions)]
has_debug_viewed_this_frame,
} = self;

used_ids.clear();
widgets.clear();
*available_rect = screen_rect;
*unused_rect = screen_rect;
*used_by_panels = Rect::NOTHING;
Expand All @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
});
Expand Down

0 comments on commit dca552e

Please sign in to comment.