gpui_authoring/window/frame_pipeline.rs
1//! The phases one frame goes through, as a seam.
2//!
3//! A window renders by handing its frame to a [`FramePipeline`]. The default,
4//! [`StandardImmediatePipeline`], re-evaluates the view tree, lays it out and
5//! paints what it finds — the immediate-mode loop described in the
6//! [authoring guide](crate::_authoring). Installing a different one is how a
7//! window renders some other way without knowing about it; see the
8//! [frame pipeline guide](crate::_frame_pipeline).
9//!
10//! Every phase falls back to the `Window` method that implements it, so a
11//! pipeline can override one phase and leave the rest alone.
12
13use crate::{
14 App, ArenaClearNeeded, FocusId, PreparedRoots, Window, WindowMetrics, window::ElementArenaScope,
15};
16
17/// The phases of drawing one frame, in the order [`FramePipeline::draw`] runs
18/// them.
19///
20/// A frame starts with [`should_render`](Self::should_render), which decides
21/// whether the frame's work happens at all. The rest are the passes it is made
22/// of, and each one falls back to the [`Window`] method implementing it, so a
23/// pipeline overrides only what it wants to change and a pass that is not
24/// overridden behaves exactly as [`StandardImmediatePipeline`] does.
25///
26/// See the [frame pipeline guide](crate::_frame_pipeline) for what each pass is
27/// for, how far a pipeline outside this crate can go, and how to install one.
28pub trait FramePipeline: 'static {
29 /// Whether the frame the application is about to draw should be drawn.
30 ///
31 /// Defaults to drawing it. Answering `false` leaves the window exactly as it
32 /// was — no roots gathered, nothing laid out, nothing painted — and leaves the
33 /// work the frame would have done pending, so the next frame draws it. This is
34 /// where a pipeline paces frames: dropping the ones the display cannot keep up
35 /// with, or coalescing several invalidations into one frame.
36 ///
37 /// A window that stays dirty keeps asking, so answer `false` to defer a frame
38 /// rather than to drop it forever, and pace using the `metrics` handed in
39 /// rather than ignoring them.
40 ///
41 /// `is_dirty` says whether something changed since the last frame, as opposed
42 /// to the platform asking for one on its own. Two frames are drawn whatever
43 /// this returns: a window's first frame, and one forced after the GPU device
44 /// was lost, whose cached content may not be replayable.
45 fn should_render(&mut self, is_dirty: bool, _metrics: &WindowMetrics) -> bool {
46 is_dirty
47 }
48
49 /// Opens the frame: samples the platform window, resets the scratch state
50 /// the frame rebuilds, and takes ownership of the invalidations this frame
51 /// owes.
52 fn begin_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
53 window.begin_frame(cx);
54 }
55
56 /// Gathers the roots this frame will draw.
57 ///
58 /// The roots are not laid out yet, so this is where a pipeline can decide what
59 /// the frame is made of before any of it is measured.
60 fn evaluate_roots(&mut self, window: &mut Window<'_>, cx: &mut App) -> PreparedRoots {
61 window.evaluate_roots(cx)
62 }
63
64 /// Lays out and prepaints `roots`, then hit tests the pointer against what
65 /// they registered.
66 ///
67 /// Returning from here means the frame's geometry is settled and its hitboxes
68 /// are registered, so a pipeline can inspect or replace the roots before they
69 /// paint.
70 fn layout_roots(&mut self, window: &mut Window<'_>, roots: &mut PreparedRoots, cx: &mut App) {
71 window.layout_roots(roots, cx);
72 }
73
74 /// Paints `roots`, in the order they stack.
75 fn paint_roots(&mut self, window: &mut Window<'_>, roots: PreparedRoots, cx: &mut App) {
76 window.paint_roots(roots, cx);
77 }
78
79 /// Lays out and paints the window's roots: the window's own view tree, plus
80 /// a prompt, drag image or tooltip if the frame has one.
81 ///
82 /// Runs this pipeline's [evaluate](Self::evaluate_roots),
83 /// [layout](Self::layout_roots) and [paint](Self::paint_roots) passes in
84 /// sequence, so overriding one of those is enough to intervene between them.
85 /// [`Window::draw_roots`] is the same three passes with no pipeline in the
86 /// way.
87 fn draw_roots(&mut self, window: &mut Window<'_>, cx: &mut App) {
88 let mut roots = self.evaluate_roots(window, cx);
89 self.layout_roots(window, &mut roots, cx);
90 self.paint_roots(window, roots, cx);
91 }
92
93 /// Closes the painted frame: records the views it touched and hands the
94 /// platform the input handler the frame asked for.
95 fn finish_frame(&mut self, window: &mut Window<'_>, cx: &mut App) {
96 window.finish_frame(cx);
97 }
98
99 /// Retires the painted frame, swaps it in, and dispatches the focus changes
100 /// the swap produced.
101 ///
102 /// Returns the focus that was current before the listeners ran: they may
103 /// move it, and the caller has to tell those moves apart from its own.
104 fn complete_frame(&mut self, window: &mut Window<'_>, cx: &mut App) -> Option<FocusId> {
105 window.complete_frame(cx)
106 }
107
108 /// Closes the frame out and marks it ready to present.
109 fn end_frame(
110 &mut self,
111 window: &mut Window<'_>,
112 cx: &mut App,
113 focus_before_listeners: Option<FocusId>,
114 ) {
115 window.end_frame(cx, focus_before_listeners);
116 }
117
118 /// Draws one frame by running the phases above in order.
119 ///
120 /// Override this to drive the phases differently, or override a single phase
121 /// to change what happens inside it.
122 fn draw(&mut self, window: &mut Window<'_>, cx: &mut App) -> ArenaClearNeeded {
123 // Drain every draw in profiler builds so a previous frame's
124 // first-invalidation timestamp can't be attributed to this one.
125 #[cfg(feature = "profiler")]
126 let frame_dirty = window.core.invalidator.take_frame_dirty();
127 #[cfg(feature = "profiler")]
128 window.core.window_profiler.begin_draw();
129
130 // Set up the per-App arena for element allocation during this draw.
131 // This ensures that multiple test Apps have isolated arenas.
132 let arena_scope = ElementArenaScope::enter(&cx.element_arena);
133
134 self.begin_frame(window, cx);
135 window.restore_input_handler();
136 if !cx.mode.skip_drawing() {
137 self.draw_roots(window, cx);
138 #[cfg(feature = "profiler")]
139 {
140 let viewport_size = window.core.viewport_size;
141 let scale_factor = window.scale_factor();
142 window.core.debug_frame_overlay.paint(
143 &mut window.frame_state.next_frame.scene,
144 viewport_size,
145 scale_factor,
146 );
147 }
148 }
149 self.finish_frame(window, cx);
150 let focus_before_listeners = self.complete_frame(window, cx);
151 self.end_frame(window, cx, focus_before_listeners);
152
153 #[cfg(feature = "profiler")]
154 {
155 window
156 .core
157 .window_profiler
158 .set_refresh_interval(window.refresh_interval(cx));
159 let draw_duration = window
160 .core
161 .window_profiler
162 .end_draw(frame_dirty.dirty_at, frame_dirty.invalidations);
163 window.core.debug_frame_overlay.record_frame(draw_duration);
164 }
165
166 // Exit the scope to obtain the arena-clear token this draw owes; the
167 // scope's teardown itself happens in `ElementArenaScope::drop`.
168 arena_scope.exit(&cx.element_arena)
169 }
170}
171
172/// The pipeline a window draws with unless another one is installed.
173///
174/// Draws every frame from scratch: the view tree is re-evaluated, laid out and
175/// painted, so what is on screen is always a function of current state.
176pub struct StandardImmediatePipeline;
177
178impl FramePipeline for StandardImmediatePipeline {}