Skip to main content

cranpose_app_shell/
shell_frame.rs

1use super::*;
2
3mod dev_overlay;
4mod frame_telemetry;
5mod layout_phase;
6pub(crate) use dev_overlay::{DevOverlay, draw_dev_overlay};
7use frame_telemetry::{
8    FrameStages, RenderPhaseDirtyDiagnostics, frame_stage_telemetry_threshold_ms,
9    log_frame_stage_telemetry, log_render_phase_dirty_diagnostics,
10    render_phase_dirty_diagnostics_enabled,
11};
12
13// A frame runs its phases in order: composition, layout, render. Each phase
14// takes the token of the phase before it, and only that phase makes its
15// token, so a frame that renders before it lays out does not compile.
16
17// What composition left for the frame.
18pub(crate) struct Composed {
19    attempted: bool,
20    recomposed: bool,
21}
22
23impl Composed {
24    // A frame that starts without composition.
25    pub(crate) const SKIPPED: Self = Self {
26        attempted: false,
27        recomposed: false,
28    };
29
30    // Whether composition reconciled the tree.
31    pub(crate) fn attempted(&self) -> bool {
32        self.attempted
33    }
34
35    // Whether the reconcile changed anything.
36    pub(crate) fn recomposed(&self) -> bool {
37        self.recomposed
38    }
39}
40
41// The tree laid out after composition, and whether the frame recomposed
42// before or during layout.
43struct LaidOut {
44    recomposed: bool,
45}
46
47#[derive(Copy, Clone, Debug)]
48enum DispatchInvalidationKind {
49    Pointer,
50    Focus,
51}
52
53#[derive(Clone, Copy)]
54struct FrameDirt {
55    render_dirty: bool,
56    pointer_dirty: bool,
57    attributed: bool,
58    layer_properties_pending: bool,
59    recomposed_this_frame: bool,
60}
61
62struct SurfaceDirt {
63    draw_dirty_nodes: Vec<NodeId>,
64    layer_property_nodes: Vec<NodeId>,
65    layout_dirty_nodes: Vec<NodeId>,
66    moved_nodes: Vec<NodeId>,
67    structural_parents: Vec<NodeId>,
68    // What the scene updates in place, the shell's lists in one: kept from
69    // frame to frame for its capacity.
70    scene_updates: Vec<(NodeId, NodeUpdate)>,
71    draw_repass_pending: bool,
72    render_only_dirty: bool,
73    visual_only_partial_dirty: bool,
74    full_scene_dirty: bool,
75    needs_scene_rebuild: bool,
76}
77
78struct ScopedVisualNodes {
79    draw: Vec<NodeId>,
80    layers: Vec<NodeId>,
81}
82
83/// The nodes a surface's layout lanes hold: laid out again, and only moved.
84struct ScopedLayoutNodes {
85    reshaped: Vec<NodeId>,
86    moved: Vec<NodeId>,
87}
88
89fn any_nodes(lists: &[&[NodeId]]) -> bool {
90    lists.iter().any(|nodes| !nodes.is_empty())
91}
92
93impl SurfaceDirt {
94    fn classify(
95        frame: &FrameDirt,
96        scene_dirty: bool,
97        draw_repass_pending: bool,
98        visual_nodes: ScopedVisualNodes,
99        layout: ScopedLayoutNodes,
100        structural_parents: Vec<NodeId>,
101        scene_updates: Vec<(NodeId, NodeUpdate)>,
102    ) -> Self {
103        let ScopedVisualNodes {
104            draw: draw_dirty_nodes,
105            layers: layer_property_nodes,
106        } = visual_nodes;
107        let ScopedLayoutNodes {
108            reshaped: layout_dirty_nodes,
109            moved: moved_nodes,
110        } = layout;
111        let structural_dirty = !structural_parents.is_empty();
112
113        // Moved nodes are not part of the partial list: the scene moves them.
114        let layout_changed = !layout_dirty_nodes.is_empty() || !moved_nodes.is_empty();
115        let partial_scene_dirty = any_nodes(&[
116            &draw_dirty_nodes,
117            &layout_dirty_nodes,
118            &moved_nodes,
119            &structural_parents,
120        ]);
121        let render_only_dirty = frame.render_dirty
122            && !frame.attributed
123            && !partial_scene_dirty
124            && layer_property_nodes.is_empty()
125            && !draw_repass_pending
126            && !structural_dirty;
127        let visual_only_partial_dirty = (!draw_dirty_nodes.is_empty()
128            || !layer_property_nodes.is_empty())
129            && !layout_changed
130            && !frame.pointer_dirty
131            && !frame.recomposed_this_frame
132            && !structural_dirty;
133        let scoped_scene_dirty = scene_dirty && layout_changed;
134        let full_scene_dirty = scene_dirty && !scoped_scene_dirty && !visual_only_partial_dirty;
135        // A scoped scene change always has nodes, so `partial_scene_dirty`
136        // covers it.
137        let needs_scene_rebuild = full_scene_dirty
138            || partial_scene_dirty
139            || !layer_property_nodes.is_empty()
140            || draw_repass_pending
141            || structural_dirty;
142        let mut dirt = Self {
143            draw_dirty_nodes,
144            layer_property_nodes,
145            layout_dirty_nodes,
146            moved_nodes,
147            structural_parents,
148            scene_updates,
149            draw_repass_pending,
150            render_only_dirty,
151            visual_only_partial_dirty,
152            full_scene_dirty,
153            needs_scene_rebuild,
154        };
155        dirt.fill_scene_updates(frame.pointer_dirty);
156        dirt
157    }
158
159    // A node in two of the lists stays in both: the scene merges a node's
160    // updates as it keys them, and draws with any other change record the
161    // node's content, so sorting every id to drop the second copy would do
162    // that work twice. The laid out nodes and the structural parents are
163    // recorded again. A draw node's draws read state that changed: the
164    // scene records them around the child layers it keeps, or the whole
165    // node when its layer no longer fits it, even in a frame that composed
166    // or laid out other nodes. After a pointer pass every draw node is
167    // recorded whole, which collects its hits again.
168    fn fill_scene_updates(&mut self, pointer_dirty: bool) {
169        let draws = if pointer_dirty {
170            NodeUpdate::Content
171        } else {
172            NodeUpdate::Draws
173        };
174        let updates = &mut self.scene_updates;
175        updates.clear();
176        updates.extend(self.moved_nodes.iter().map(|&id| (id, NodeUpdate::Moved)));
177        updates.extend(
178            self.layer_property_nodes
179                .iter()
180                .map(|&id| (id, NodeUpdate::Layer)),
181        );
182        updates.extend(self.draw_dirty_nodes.iter().map(|&id| (id, draws)));
183        let recorded = self
184            .layout_dirty_nodes
185            .iter()
186            .chain(&self.structural_parents);
187        updates.extend(recorded.map(|&id| (id, NodeUpdate::Content)));
188    }
189
190    fn use_partial_update(&self) -> bool {
191        any_nodes(&[
192            &self.draw_dirty_nodes,
193            &self.layer_property_nodes,
194            &self.layout_dirty_nodes,
195            &self.moved_nodes,
196            &self.structural_parents,
197        ]) && !self.render_only_dirty
198            && !self.full_scene_dirty
199    }
200
201    fn use_visual_update(&self) -> bool {
202        self.use_partial_update() && self.visual_only_partial_dirty
203    }
204
205    fn rebuild_path(&self) -> &'static str {
206        if self.use_visual_update() {
207            "visual-update"
208        } else if self.use_partial_update() {
209            "update"
210        } else {
211            "rebuild"
212        }
213    }
214
215    fn log(&self, frame: &FrameDirt, scene_dirty: bool, path: &str) {
216        log_render_phase_dirty_diagnostics(RenderPhaseDirtyDiagnostics {
217            render_dirty: frame.render_dirty,
218            pointer_dirty: frame.pointer_dirty,
219            scene_dirty,
220            draw_repass_pending: self.draw_repass_pending,
221            draw_dirty_nodes: self.draw_dirty_nodes.len(),
222            layer_property_nodes: self.layer_property_nodes.len(),
223            layout_dirty_nodes: self.layout_dirty_nodes.len(),
224            moved_nodes: self.moved_nodes.len(),
225            structural_dirty_nodes: self.structural_parents.len(),
226            scene_updates: self.scene_updates.len(),
227            dirty_node_ids: render_phase_dirty_diagnostics_enabled().then(|| {
228                format!(
229                    "draw={:?} layers={:?} layout={:?} moved={:?} structural={:?} updates={:?}",
230                    self.draw_dirty_nodes,
231                    self.layer_property_nodes,
232                    self.layout_dirty_nodes,
233                    self.moved_nodes,
234                    self.structural_parents,
235                    self.scene_updates,
236                )
237            }),
238            render_only_dirty: self.render_only_dirty,
239            recomposed_this_frame: frame.recomposed_this_frame,
240            path,
241        });
242    }
243}
244
245struct SurfaceFrame {
246    result: FrameUpdateResult,
247    rebuilt: bool,
248}
249
250pub(crate) const IDLE_PREFETCH_MAX_PASSES: usize = 4;
251
252impl<R> AppShell<R>
253where
254    R: Renderer,
255    R::Error: Debug,
256{
257    /// Turns semantics tracking on or off. Turning it on forces a layout pass and marks every
258    /// surface dirty. Turning it off drops the cached semantics trees.
259    pub fn set_semantics_enabled(&mut self, enabled: bool) {
260        if self.app.semantics_enabled == enabled {
261            return;
262        }
263        self.app.semantics_enabled = enabled;
264        self.app.semantics_snapshot_revision = self.app.semantics_snapshot_revision.wrapping_add(1);
265        if enabled {
266            self.app.request_forced_layout_pass();
267            self.mark_all_dirty();
268        } else {
269            for surface in &mut self.surfaces {
270                surface.semantics_tree = None;
271            }
272        }
273    }
274
275    /// Sets how the platform should vote the display's frame rate for this
276    /// app. The default, [`FrameRatePreference::Auto`], mirrors Compose: the
277    /// panel's fastest rate while frames are being produced, no preference
278    /// when the scene is still.
279    pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
280        self.surfaces[0].frame_rate_preference = preference;
281    }
282
283    /// The app's current display frame-rate preference. Read by platform
284    /// backends each frame; the vote itself is applied by the backend that
285    /// owns the native window.
286    pub fn frame_rate_preference(&self) -> FrameRatePreference {
287        self.surfaces[0].frame_rate_preference
288    }
289
290    pub(crate) fn process_frame(&mut self) -> FrameUpdateResult {
291        let _app_context = self.app.app_context.enter_scope();
292        self.process_frame_in_context(Composed::SKIPPED)
293    }
294
295    // Composition, the first phase of a frame: recomposes the invalid scopes.
296    // Out of line: most frames compose nothing, and the update keeps only
297    // the call.
298    #[inline(never)]
299    pub(crate) fn compose_phase(&mut self) -> Composed {
300        let Some(root_key) = self.app.composition.root_key() else {
301            return Composed::SKIPPED;
302        };
303        let recomposed = match self
304            .app
305            .composition
306            .reconcile(root_key, &mut *self.app.content)
307        {
308            Ok(changed) => {
309                log::trace!(target: "cranpose::input", "reconcile changed={changed}");
310                if changed {
311                    self.app.fps_monitor.record_recomposition();
312                    if self.app.composition_tree_needs_layout() {
313                        self.app.request_layout_pass();
314                    }
315                    request_render_invalidation();
316                }
317                changed
318            }
319            Err(NodeError::Missing { id }) => {
320                log::debug!("Recomposition skipped: node {id} no longer exists");
321                self.app.request_layout_pass();
322                request_render_invalidation();
323                false
324            }
325            Err(err) => {
326                log::error!("recomposition failed: {err}");
327                self.app.request_layout_pass();
328                request_render_invalidation();
329                false
330            }
331        };
332        Composed {
333            attempted: true,
334            recomposed,
335        }
336    }
337
338    pub(crate) fn process_frame_in_context(&mut self, composed: Composed) -> FrameUpdateResult {
339        let mut stages = FrameStages::start(frame_stage_telemetry_threshold_ms().is_some());
340
341        self.app.app_context.start_text_frame();
342        #[cfg(feature = "installed-fonts")]
343        self.apply_installed_fonts();
344        self.sync_window_roots_in_context();
345        let laid_out = self.lay_out(composed, &mut stages);
346
347        self.run_dispatch_queues();
348        self.update_modal_focus();
349        self.reveal_new_focus();
350
351        stages.reached();
352
353        self.app
354            .app_context
355            .clear_transient_scroll_motion_contexts();
356
357        let result = self.run_render_phase_in_context(laid_out);
358        stages.reached();
359        log_frame_stage_telemetry(&stages);
360        result
361    }
362
363    /// Whether a lazy list waits for [`Self::run_idle_prefetch`].
364    pub fn has_idle_prefetch_work(&self) -> bool {
365        self.app
366            .app_context
367            .enter(cranpose_ui::has_lazy_prefetch_requests)
368    }
369
370    /// Composes lazy list items that frames left beyond their viewports for
371    /// later, in layout passes, at most four of them. A pass runs while
372    /// `deadline`, if any, still leaves room for the recent cost of one full pass
373    /// and `frame_can_start`, asked before each pass, says the next frame
374    /// cannot start yet. None runs while the next frame owes a measure or a
375    /// layout, as a scroll leaves one ([`cranpose_ui::lazy_prefetch_pass_due`]).
376    /// Call it while waiting for the next frame. Returns whether a pass ran.
377    pub fn run_idle_prefetch(
378        &mut self,
379        deadline: Option<Instant>,
380        mut frame_can_start: impl FnMut(&mut R) -> bool,
381    ) -> bool {
382        let _app_context = self.app.app_context.enter_scope();
383        let mut passes = 0;
384        while passes < IDLE_PREFETCH_MAX_PASSES
385            && cranpose_ui::lazy_prefetch_pass_due()
386            && deadline.is_none_or(|deadline| {
387                Instant::now() + cranpose_ui::lazy_prefetch_pass_cost() <= deadline
388            })
389            && !frame_can_start(self.renderer())
390        {
391            cranpose_ui::with_lazy_prefetch_pass(|| {
392                cranpose_ui::drain_lazy_prefetch_requests(cranpose_ui::schedule_measure_repass);
393                self.run_layout_phase_in_context();
394                cranpose_ui::warm_prefetched_slices(&mut self.app.composition.applier_mut());
395            });
396            passes += 1;
397        }
398        passes > 0
399    }
400
401    /// Gives every renderer the fonts installed since the last frame, before
402    /// this frame lays text out.
403    #[cfg(feature = "installed-fonts")]
404    fn apply_installed_fonts(&mut self) {
405        let generation = cranpose_render_common::installed_fonts::installed_font_generation();
406        if generation == self.app.installed_fonts {
407            return;
408        }
409        self.app.installed_fonts = generation;
410        for surface in &mut self.surfaces {
411            surface
412                .renderer
413                .apply_installed_fonts(&self.app.app_context);
414        }
415    }
416
417    fn run_dispatch_queues(&mut self) {
418        if self.app.app_context.has_pending_pointer_repasses() {
419            self.clear_dispatch_flags(DispatchInvalidationKind::Pointer);
420        }
421        if self.app.app_context.has_pending_focus_invalidations() {
422            self.clear_dispatch_flags(DispatchInvalidationKind::Focus);
423        }
424        self.app.flush_semantics_invalidations();
425    }
426
427    /// Clears the `kind` flag of the nodes its queue holds. Out of line: a
428    /// frame checks the queues and seldom finds work in them, so the frame's
429    /// own code stays small.
430    #[inline(never)]
431    fn clear_dispatch_flags(&mut self, kind: DispatchInvalidationKind) {
432        let mut applier = self.app.composition.applier_mut();
433        let clear = |node_id| match clear_dispatch_invalidation(&mut applier, node_id, kind) {
434            Ok(true) => log::trace!("Cleared the {kind:?} flag of node #{node_id}"),
435            Ok(false) => {}
436            Err(err) => {
437                log::debug!("Could not clear the {kind:?} flag of node #{node_id}: {err}");
438            }
439        };
440        match kind {
441            DispatchInvalidationKind::Pointer => process_pointer_repasses(clear),
442            DispatchInvalidationKind::Focus => process_focus_invalidations(clear),
443        }
444    }
445
446    fn take_structural_change_parents(&mut self) {
447        self.pending_dirty_nodes.clear();
448        if cranpose_core::DIAGNOSTICS.disable_structural_dirt.is_set() {
449            return;
450        }
451        if self.app.composition.root().is_none() {
452            return;
453        }
454        self.app
455            .composition
456            .applier_mut()
457            .take_structural_change_parents_into(&mut self.pending_dirty_nodes);
458        // A child's z index orders it among its siblings, in its parent's
459        // layer: the parent changed as a child list does.
460        let reordered = self.pending_dirty_nodes.len();
461        self.app
462            .app_context
463            .append_z_order_changes(&mut self.pending_dirty_nodes);
464        if self.pending_dirty_nodes.len() > reordered {
465            self.reordered_children_to_parents(reordered);
466        }
467    }
468
469    /// Replaces each child from `from` on in the pending dirty nodes, whose z
470    /// index changed, with its parent. Out of line, as z indexes seldom
471    /// change.
472    #[inline(never)]
473    fn reordered_children_to_parents(&mut self, from: usize) {
474        let mut applier = self.app.composition.applier_mut();
475        for node in &mut self.pending_dirty_nodes[from..] {
476            if let Some(parent) = applier.get_mut(*node).ok().and_then(|child| child.parent()) {
477                *node = parent;
478            }
479        }
480    }
481
482    #[cfg(test)]
483    pub(crate) fn run_render_phase(&mut self) -> FrameUpdateResult {
484        let _app_context = self.app.app_context.enter_scope();
485        let laid_out = self.lay_out(Composed::SKIPPED, &mut FrameStages::start(false));
486        self.run_render_phase_in_context(laid_out)
487    }
488
489    // Render, the last phase: draws what layout placed.
490    #[expect(
491        clippy::needless_pass_by_value,
492        reason = "a phase takes the token of the phase before it, which only that phase makes"
493    )]
494    fn run_render_phase_in_context(&mut self, laid_out: LaidOut) -> FrameUpdateResult {
495        let inspector_revision = if self.app.inspector_projector.is_some()
496            && self
497                .surfaces
498                .iter()
499                .any(|surface| surface.inspector.state.open)
500        {
501            self.semantics_snapshot_revision()
502        } else {
503            0
504        };
505        let context = &self.app.app_context;
506        context.tick_cursor_blink();
507        let render_dirty = context.take_render_invalidation();
508        let pointer_dirty = context.take_pointer_invalidation();
509        context.take_focus_invalidation();
510        for surface in &mut self.surfaces {
511            surface.scoped_draw_nodes.clear();
512            surface.scoped_layer_property_nodes.clear();
513            surface.structural_scene_nodes.clear();
514        }
515        self.app
516            .app_context
517            .take_draw_repass_nodes_into(&mut self.pending_dirty_nodes);
518        route_nodes_by_surface(
519            &mut self.app,
520            &mut self.surfaces,
521            self.pending_dirty_nodes.iter().copied(),
522            SurfaceDirtyLane::Draw,
523            &mut self.routing_scratch,
524        );
525        self.app
526            .app_context
527            .take_layer_property_repass_nodes_into(&mut self.pending_layer_property_nodes);
528        route_nodes_by_surface(
529            &mut self.app,
530            &mut self.surfaces,
531            self.pending_layer_property_nodes.iter().copied(),
532            SurfaceDirtyLane::LayerProperties,
533            &mut self.routing_scratch,
534        );
535        self.take_structural_change_parents();
536        route_nodes_by_surface(
537            &mut self.app,
538            &mut self.surfaces,
539            self.pending_dirty_nodes.iter().copied(),
540            SurfaceDirtyLane::Structural,
541            &mut self.routing_scratch,
542        );
543        self.pending_dirty_nodes.clear();
544        self.pending_layer_property_nodes.clear();
545        let _ = self.app.app_context.has_focused_field();
546        let attributed = self.surfaces.iter().any(|surface| {
547            !surface.scoped_draw_nodes.is_empty()
548                || !surface.scoped_layer_property_nodes.is_empty()
549                || !surface.structural_scene_nodes.is_empty()
550                || !surface.scoped_layout_scene_nodes.is_empty()
551                || !surface.scoped_moved_scene_nodes.is_empty()
552        });
553        let layer_properties_pending = self
554            .surfaces
555            .iter()
556            .any(|surface| !surface.scoped_layer_property_nodes.is_empty());
557        let frame = FrameDirt {
558            render_dirty,
559            pointer_dirty,
560            attributed,
561            layer_properties_pending,
562            recomposed_this_frame: laid_out.recomposed,
563        };
564
565        let mut result = FrameUpdateResult::default();
566        for surface in &mut self.surfaces {
567            let mut frame = render_surface(&mut self.app, surface, &frame);
568            frame.result.visual_changed |=
569                inspector::refresh(&mut self.app, surface, inspector_revision);
570            surface.last_update = frame.result;
571            surface.frame_owed |= frame.result.visual_changed;
572            result.visual_changed |= frame.result.visual_changed;
573            result.structure_changed |= frame.result.structure_changed;
574            result.content_redrawn |= frame.rebuilt;
575        }
576        if result.content_redrawn {
577            self.prune_stale_draw_observations();
578        }
579        result.content_moved = std::mem::take(&mut self.app.content_moved);
580        result
581    }
582
583    /// Drops the draw observations of nodes no surface's render graph holds
584    /// any more, once every [`OBSERVATION_PRUNE_INTERVAL`] rebuilt frames:
585    /// finding them walks every node of every graph, and an observation
586    /// left behind in between costs no more than a redraw request for a
587    /// node nothing draws.
588    fn prune_stale_draw_observations(&mut self) {
589        self.app.rebuilt_frames_since_observation_prune += 1;
590        if self.app.rebuilt_frames_since_observation_prune < OBSERVATION_PRUNE_INTERVAL {
591            return;
592        }
593        self.app.rebuilt_frames_since_observation_prune = 0;
594        let mut retained_visual_nodes = HashSet::default();
595        let mut any_graph = false;
596        for surface in &mut self.surfaces {
597            any_graph |= surface
598                .renderer
599                .scene()
600                .collect_retained_visual_observation_nodes(&mut surface.retained_visual_nodes);
601            retained_visual_nodes.extend(surface.retained_visual_nodes.iter().copied());
602        }
603        if any_graph {
604            cranpose_ui::prune_draw_observations_to_nodes(&retained_visual_nodes);
605        }
606    }
607}
608
609/// Rebuilt frames between prunes of stale draw observations: once a second
610/// at 60 Hz.
611pub(crate) const OBSERVATION_PRUNE_INTERVAL: u32 = 60;
612
613fn render_surface<R>(
614    app: &mut ShellApp,
615    surface: &mut RootSurface<R>,
616    frame: &FrameDirt,
617) -> SurfaceFrame
618where
619    R: Renderer,
620    R::Error: Debug,
621{
622    surface.begin_frame();
623    let draw_dirty_nodes = std::mem::take(&mut surface.scoped_draw_nodes);
624    let layer_property_nodes = std::mem::take(&mut surface.scoped_layer_property_nodes);
625    let structural_parents = std::mem::take(&mut surface.structural_scene_nodes);
626    let draw_repass_pending = !draw_dirty_nodes.is_empty();
627    let mut draw_dirty_nodes = refresh_draw_nodes(app, surface, draw_dirty_nodes);
628    if frame.render_dirty && !frame.layer_properties_pending && !draw_repass_pending {
629        draw_dirty_nodes = refresh_retained_redraw_nodes(app, surface, draw_dirty_nodes);
630    }
631    let layout = ScopedLayoutNodes {
632        reshaped: std::mem::take(&mut surface.scoped_layout_scene_nodes),
633        moved: std::mem::take(&mut surface.scoped_moved_scene_nodes),
634    };
635    let scene_updates = std::mem::take(&mut surface.scene_updates);
636    let scene_dirty = surface.scene_dirty;
637    let mut dirt = SurfaceDirt::classify(
638        frame,
639        scene_dirty,
640        draw_repass_pending,
641        ScopedVisualNodes {
642            draw: draw_dirty_nodes,
643            layers: layer_property_nodes,
644        },
645        layout,
646        structural_parents,
647        scene_updates,
648    );
649
650    if !dirt.needs_scene_rebuild {
651        dirt.log(
652            frame,
653            scene_dirty,
654            if dirt.render_only_dirty {
655                "present-retained"
656            } else {
657                "skip"
658            },
659        );
660        surface.scoped_layout_scene_nodes = dirt.layout_dirty_nodes;
661        surface.scoped_moved_scene_nodes = dirt.moved_nodes;
662        surface.scoped_draw_nodes = dirt.draw_dirty_nodes;
663        surface.scoped_layer_property_nodes = dirt.layer_property_nodes;
664        surface.structural_scene_nodes = dirt.structural_parents;
665        surface.scene_updates = dirt.scene_updates;
666        if dirt.render_only_dirty && app.dev_options.fps_counter {
667            draw_dev_overlay(app, surface);
668        }
669        return SurfaceFrame {
670            result: FrameUpdateResult {
671                visual_changed: dirt.render_only_dirty,
672                ..FrameUpdateResult::default()
673            },
674            rebuilt: false,
675        };
676    }
677
678    surface.scene_dirty = false;
679    let structure_changed = !dirt.render_only_dirty && !dirt.use_visual_update();
680    rebuild_surface_scene(app, surface, frame, scene_dirty, &dirt);
681    if dev_overlay_belongs_on(surface.id, app.dev_options.fps_counter) {
682        draw_dev_overlay(app, surface);
683    }
684    dirt.layout_dirty_nodes.clear();
685    dirt.moved_nodes.clear();
686    surface.scoped_moved_scene_nodes = dirt.moved_nodes;
687    surface.scoped_draw_nodes = dirt.draw_dirty_nodes;
688    surface.scoped_layer_property_nodes = dirt.layer_property_nodes;
689    surface.structural_scene_nodes = dirt.structural_parents;
690    surface.scene_updates = dirt.scene_updates;
691    surface.scoped_layout_scene_nodes = dirt.layout_dirty_nodes;
692    SurfaceFrame {
693        result: FrameUpdateResult {
694            visual_changed: true,
695            structure_changed,
696            ..FrameUpdateResult::default()
697        },
698        rebuilt: true,
699    }
700}
701
702fn rebuild_surface_scene<R>(
703    app: &mut ShellApp,
704    surface: &mut RootSurface<R>,
705    frame: &FrameDirt,
706    scene_dirty: bool,
707    dirt: &SurfaceDirt,
708) where
709    R: Renderer,
710    R::Error: Debug,
711{
712    let viewport_size = surface.viewport_size();
713    let Some(root) = surface.root_node(app) else {
714        surface.renderer.scene_mut().clear();
715        return;
716    };
717    let mut applier = app.composition.applier_mut();
718    dirt.log(frame, scene_dirty, dirt.rebuild_path());
719    let rebuild_result = if dirt.use_visual_update() {
720        // Nothing was composed or laid out: the partial nodes are the draw
721        // lane's, whose draws read state that changed.
722        surface.renderer.update_visual_scene_from_applier(
723            &mut applier,
724            root,
725            viewport_size,
726            &dirt.scene_updates,
727        )
728    } else if dirt.use_partial_update() {
729        surface.renderer.update_scene_from_applier(
730            &mut applier,
731            root,
732            viewport_size,
733            &dirt.scene_updates,
734        )
735    } else {
736        surface
737            .renderer
738            .rebuild_scene_from_applier(&mut applier, root, viewport_size)
739    };
740    if let Err(err) = rebuild_result {
741        log::error!("renderer rebuild failed: {err:?}");
742        surface.renderer.scene_mut().clear();
743    }
744}
745
746fn dev_overlay_belongs_on(id: RootId, fps_counter: bool) -> bool {
747    fps_counter && matches!(id, RootId::Primary)
748}
749
750fn refresh_draw_nodes<R: Renderer>(
751    app: &mut ShellApp,
752    surface: &mut RootSurface<R>,
753    mut dirty_nodes: Vec<NodeId>,
754) -> Vec<NodeId> {
755    if dirty_nodes.is_empty() {
756        return dirty_nodes;
757    }
758    let Some(layout_tree) = surface.layout_tree.as_mut() else {
759        return dirty_nodes;
760    };
761
762    dirty_nodes.sort_unstable();
763    dirty_nodes.dedup();
764    let dirty_set: HashSet<NodeId> = dirty_nodes.iter().copied().collect();
765    let mut applier = app.composition.applier_mut();
766    let refresh_scope = build_draw_refresh_scope(&mut applier, &dirty_set);
767    refresh_layout_box_data(
768        &mut applier,
769        layout_tree.root_mut(),
770        &refresh_scope,
771        &dirty_set,
772    );
773    dirty_nodes
774}
775
776fn refresh_retained_redraw_nodes<R: Renderer>(
777    app: &mut ShellApp,
778    surface: &mut RootSurface<R>,
779    mut dirty_nodes: Vec<NodeId>,
780) -> Vec<NodeId> {
781    dirty_nodes.clear();
782    let Some(root) = surface.root_node(app) else {
783        return dirty_nodes;
784    };
785    {
786        let mut applier = app.composition.applier_mut();
787        collect_retained_redraw_nodes(
788            &mut applier,
789            &app.app_context,
790            root,
791            &mut dirty_nodes,
792            &mut surface.redraw_walk,
793        );
794    }
795    refresh_draw_nodes(app, surface, dirty_nodes)
796}
797
798fn clear_dispatch_invalidation(
799    applier: &mut MemoryApplier,
800    node_id: NodeId,
801    invalidation: DispatchInvalidationKind,
802) -> Result<bool, NodeError> {
803    match invalidation {
804        DispatchInvalidationKind::Pointer => {
805            match applier.with_node::<LayoutNode, _>(node_id, |node| {
806                let needs_pointer_pass = node.needs_pointer_pass();
807                if needs_pointer_pass {
808                    node.clear_needs_pointer_pass();
809                }
810                needs_pointer_pass
811            }) {
812                Ok(cleared) => Ok(cleared),
813                Err(NodeError::TypeMismatch { .. }) => applier
814                    .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
815                        let needs_pointer_pass = node.needs_pointer_pass();
816                        if needs_pointer_pass {
817                            node.clear_needs_pointer_pass();
818                        }
819                        needs_pointer_pass
820                    }),
821                Err(err) => Err(err),
822            }
823        }
824        DispatchInvalidationKind::Focus => {
825            match applier.with_node::<LayoutNode, _>(node_id, |node| {
826                let needs_focus_sync = node.needs_focus_sync();
827                if needs_focus_sync {
828                    node.clear_needs_focus_sync();
829                }
830                needs_focus_sync
831            }) {
832                Ok(cleared) => Ok(cleared),
833                Err(NodeError::TypeMismatch { .. }) => applier
834                    .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
835                        let needs_focus_sync = node.needs_focus_sync();
836                        if needs_focus_sync {
837                            node.clear_needs_focus_sync();
838                        }
839                        needs_focus_sync
840                    }),
841                Err(err) => Err(err),
842            }
843        }
844    }
845}
846
847pub(crate) fn build_draw_refresh_scope(
848    applier: &mut MemoryApplier,
849    dirty_nodes: &HashSet<NodeId>,
850) -> HashSet<NodeId> {
851    let mut refresh_scope =
852        HashSet::with_capacity_and_hasher(dirty_nodes.len(), Default::default());
853    for &dirty_node in dirty_nodes {
854        let mut current = Some(dirty_node);
855        while let Some(node_id) = current {
856            if !refresh_scope.insert(node_id) {
857                break;
858            }
859            current = applier.get_mut(node_id).ok().and_then(|node| node.parent());
860        }
861    }
862    refresh_scope
863}
864
865/// Collects into `dirty_nodes` the nodes under `root` whose redraw flag was
866/// set before they had an id, clearing the flags: a draw repass names every
867/// other node whose content changed. A node under another window root, or
868/// not attached yet, stays noted in `context` for a later frame. `pending`
869/// keeps its storage for the next frame.
870fn collect_retained_redraw_nodes(
871    applier: &mut MemoryApplier,
872    context: &AppContext,
873    root: NodeId,
874    dirty_nodes: &mut Vec<NodeId>,
875    pending: &mut Vec<NodeId>,
876) {
877    pending.clear();
878    context.take_retained_redraw_nodes_into(pending);
879    for &id in pending.iter() {
880        match redraw_place(applier, id, root) {
881            RedrawPlace::Here => {
882                if take_redraw_flag(applier, id) {
883                    dirty_nodes.push(id);
884                }
885            }
886            RedrawPlace::Elsewhere => context.keep_retained_redraw_node(id),
887            RedrawPlace::Gone => {}
888        }
889    }
890}
891
892/// Where a node noted for a redraw lies for the surface of `root`.
893enum RedrawPlace {
894    /// Under `root` and under no other window root: the surface takes it.
895    Here,
896    /// Under another window root, or not attached to a root yet.
897    Elsewhere,
898    /// The node is gone.
899    Gone,
900}
901
902fn redraw_place(applier: &mut MemoryApplier, id: NodeId, root: NodeId) -> RedrawPlace {
903    let mut current = id;
904    loop {
905        if current == root {
906            return RedrawPlace::Here;
907        }
908        let Ok(node) = applier.get_mut(current) else {
909            return if current == id {
910                RedrawPlace::Gone
911            } else {
912                RedrawPlace::Elsewhere
913            };
914        };
915        let parent = node.parent();
916        if node
917            .as_any_mut()
918            .downcast_mut::<LayoutNode>()
919            .is_some_and(|layout| layout.is_window_root())
920        {
921            return RedrawPlace::Elsewhere;
922        }
923        let Some(parent) = parent else {
924            return RedrawPlace::Elsewhere;
925        };
926        current = parent;
927    }
928}
929
930/// Clears the redraw flag of the layout or subcompose node `id`, and returns
931/// whether it was set.
932fn take_redraw_flag(applier: &mut MemoryApplier, id: NodeId) -> bool {
933    let Ok(node) = applier.get_mut(id) else {
934        return false;
935    };
936    let any = node.as_any_mut();
937    if let Some(layout) = any.downcast_mut::<LayoutNode>() {
938        let redraw = layout.needs_redraw();
939        layout.clear_needs_redraw();
940        return redraw;
941    }
942    match any.downcast_mut::<SubcomposeLayoutNode>() {
943        Some(subcompose) => {
944            let redraw = subcompose.needs_redraw();
945            subcompose.clear_needs_redraw();
946            redraw
947        }
948        None => false,
949    }
950}
951
952fn refresh_layout_box_data(
953    applier: &mut MemoryApplier,
954    layout: &mut LayoutBox,
955    refresh_scope: &HashSet<NodeId>,
956    dirty_nodes: &HashSet<NodeId>,
957) {
958    if !refresh_scope.contains(&layout.node_id) {
959        return;
960    }
961
962    if dirty_nodes.contains(&layout.node_id) {
963        let refreshed = applier
964            .with_node::<LayoutNode, _>(layout.node_id, |node| {
965                node.clear_needs_redraw();
966                (
967                    node.modifier(),
968                    node.resolved_modifiers(),
969                    node.modifier_slices_snapshot(),
970                    node.semantics_configuration(),
971                )
972            })
973            .or_else(|_| {
974                applier.with_node::<SubcomposeLayoutNode, _>(layout.node_id, |node| {
975                    node.clear_needs_redraw();
976                    (
977                        node.modifier(),
978                        node.resolved_modifiers(),
979                        node.modifier_slices_snapshot(),
980                        node.semantics_configuration(),
981                    )
982                })
983            });
984        if let Ok((modifier, resolved_modifiers, slices, semantics)) = refreshed {
985            layout.node_data.modifier = modifier;
986            layout.node_data.resolved_modifiers = resolved_modifiers;
987            layout.node_data.modifier_slices = slices;
988            layout.node_data.semantics = semantics.map(Rc::new);
989        }
990    }
991
992    for child in &mut layout.children {
993        refresh_layout_box_data(applier, child, refresh_scope, dirty_nodes);
994    }
995}
996
997#[cfg(test)]
998#[path = "tests/shell_frame_tests.rs"]
999mod tests;