Skip to main content

ferroui_base/rendering/composition/server/server_composition_visual/
render.rs

1use super::walker::{walk, IServerTreeVisitor, TreeWalkContext};
2use super::ServerCompositionVisual;
3use crate::media::{RenderOptions, TextOptions};
4use crate::platform::{IDrawingContextImpl, LtrbRect};
5use crate::rendering::composition::server::{render_scope, CompositorPools, IDirtyRectTracker, LitContent, ServerVisualRenderContext};
6use crate::Matrix;
7use std::rc::Rc;
8
9pub(super) struct RenderContext<'a> {
10    pub(super) canvas: &'a mut dyn IDrawingContextImpl,
11    dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
12    pub(super) pools: &'a CompositorPools,
13    render_children: bool,
14    walk_context: TreeWalkContext,
15    opacity_stack: Vec<f64>,
16    opacity: f64,
17    full_skip: bool,
18    used_cache: bool,
19    rendered_visuals: i32,
20    visited_visuals: i32,
21    pub(super) root_visual: Rc<ServerCompositionVisual>,
22    skip_next_visual_transform: bool,
23    rendering_to_bitmap_cache: bool,
24    pub(super) adorner_pushed_clip_stack: Option<Vec<i32>>,
25    pub(super) current_adorner_layer: Option<Rc<ServerCompositionVisual>>,
26    /// The visual the render stops at (a backdrop render): it and
27    /// everything after it in paint order are not drawn.
28    stop_at: Option<*const ServerCompositionVisual>,
29    /// Whether the render reached `stop_at`.
30    stopped: bool,
31}
32
33impl<'a> RenderContext<'a> {
34    #[allow(clippy::too_many_arguments)]
35    fn new(
36        root_visual: Rc<ServerCompositionVisual>,
37        canvas: &'a mut dyn IDrawingContextImpl,
38        mut dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
39        pools: &'a CompositorPools,
40        matrix: Matrix,
41        mut clip: LtrbRect,
42        render_children: bool,
43        skip_root_visual_transform: bool,
44        rendering_to_bitmap_cache: bool,
45    ) -> Self {
46        if let Some(tracker) = &dirty_rects {
47            let dirty_clip = tracker.combined_rect();
48            let is_single = tracker.is_single_dirty_rect_tracker();
49            if is_single {
50                dirty_rects = None;
51            }
52            clip = clip.intersect_or_empty(dirty_clip);
53        }
54
55        Self {
56            canvas,
57            dirty_rects,
58            pools,
59            render_children,
60            root_visual,
61            walk_context: TreeWalkContext::new(pools, matrix, clip),
62            opacity: 1.0,
63            opacity_stack: pools.double_stack_pool.rent(),
64            full_skip: false,
65            used_cache: false,
66            rendered_visuals: 0,
67            visited_visuals: 0,
68            skip_next_visual_transform: skip_root_visual_transform,
69            rendering_to_bitmap_cache,
70            adorner_pushed_clip_stack: None,
71            current_adorner_layer: None,
72            stop_at: None,
73            stopped: false,
74        }
75    }
76
77    fn handle_pre_graph_transform_clip_opacity(&mut self, visual: &ServerCompositionVisual) -> bool {
78        let Some(transformed_sub_tree_bounds) = visual.transformed_sub_tree_bounds.get() else { return false };
79        if !visual.visible() {
80            return false;
81        }
82        let visual_opacity = visual.opacity() as f64;
83        let effective_opacity = visual_opacity * self.opacity;
84        if effective_opacity <= 0.003 {
85            return false;
86        }
87
88        let mut effective_new_transform = self.walk_context.transform;
89        if let Some(own_transform) = visual.own_transform.get() {
90            if !self.skip_next_visual_transform {
91                effective_new_transform = own_transform * self.walk_context.transform;
92            }
93        }
94        self.skip_next_visual_transform = false;
95
96        let mut effective_clip = self.walk_context.clip;
97        if let Some(own_clip) = visual.own_clip_rect.get() {
98            effective_clip = effective_clip.intersect_or_empty(own_clip.transform_to_aabb(effective_new_transform));
99        }
100
101        let world_bounds = transformed_sub_tree_bounds.transform_to_aabb(self.walk_context.transform);
102        if !effective_clip.intersects(world_bounds)
103            || self.dirty_rects.as_ref().is_some_and(|d| !d.intersects(world_bounds))
104        {
105            return false;
106        }
107
108        self.rendered_visuals += 1;
109
110        // We are still in parent's coordinate space here
111
112        if visual_opacity != 1.0 {
113            self.opacity_stack.push(self.opacity);
114            self.opacity = effective_opacity;
115            self.canvas.push_opacity(visual_opacity, Some(transformed_sub_tree_bounds.to_rect()));
116        }
117
118        // Switch coordinate space to this visual's space
119
120        if visual.own_transform.get().is_some() {
121            self.walk_context.push_set_transform(effective_new_transform); // Reuse one computed before
122            self.canvas.set_transform(effective_new_transform);
123        }
124
125        if visual.own_clip_rect.get().is_some() {
126            self.walk_context.push_clip(effective_clip);
127        }
128
129        if visual.clip_to_bounds() {
130            visual.content.push_clip_to_bounds(visual, self.canvas);
131        }
132
133        if let Some(clip) = visual.clip() {
134            self.canvas.push_geometry_clip(&*clip);
135        }
136
137        // The WinUI clip object (not from upstream), in the space of the
138        // visual.
139        if let Some(shape) = visual.composition_clip_shape() {
140            shape.push(self.canvas);
141        }
142
143        true
144    }
145
146    fn dispose(mut self) {
147        let pools = self.pools;
148        self.adorner_helper_dispose();
149        pools.double_stack_pool.return_stack(self.opacity_stack);
150        self.walk_context.dispose(pools);
151    }
152}
153
154impl IServerTreeVisitor for RenderContext<'_> {
155    fn pre_subgraph(&mut self, visual: &Rc<ServerCompositionVisual>) -> bool {
156        // A backdrop render stops at the visual that paints the backdrop
157        // (winui-materials.md).
158        if let Some(stop_at) = self.stop_at {
159            if self.stopped || std::ptr::eq(Rc::as_ptr(visual), stop_at) {
160                self.stopped = true;
161                self.full_skip = true;
162                return false;
163            }
164        }
165        self.visited_visuals += 1;
166        let bitmap_cache_root = self.rendering_to_bitmap_cache && Rc::ptr_eq(visual, &self.root_visual);
167        if !bitmap_cache_root {
168            // Skip those for the root visual if we are rendering to bitmap cache
169            // Push transform, clip, opacity and check if those make the visual effectively invisible
170            if !self.handle_pre_graph_transform_clip_opacity(visual) {
171                self.full_skip = true;
172                return false;
173            }
174
175            // Push adorner clip
176            if visual.adorned_visual().is_some() {
177                self.adorner_helper_render_pre_graph_push_adorner_clip(visual);
178            }
179
180            // If caching is enabled, draw from cache and skip rendering
181            if let Some(cache) = visual.cache() {
182                let (visited, rendered) = cache.draw(self.canvas);
183                self.visited_visuals += visited;
184                self.rendered_visuals += rendered;
185                self.used_cache = true;
186                return false;
187            }
188        }
189
190        if visual.render_options() != RenderOptions::default() {
191            self.canvas.push_render_options(visual.render_options());
192        }
193
194        if visual.text_options() != TextOptions::default() {
195            self.canvas.push_text_options(visual.text_options());
196        }
197
198        if let (Some(mask), Some(bounds)) = (visual.opacity_mask_brush(), visual.sub_tree_bounds.get()) {
199            self.canvas.push_opacity_mask(&*mask, bounds.to_rect());
200        }
201
202        if let (Some(effect), Some(bounds)) = (visual.effect(), visual.sub_tree_bounds.get()) {
203            if let Some(effects) = self.canvas.as_drawing_context_impl_with_effects() {
204                effects.push_effect(Some(bounds.to_rect()), &*effect);
205            }
206        }
207
208        let clip = self.walk_context.clip;
209        // The WinUI lights that reach the content (not from upstream): the
210        // content is drawn into a layer and the light map over it.
211        let lit = LitContent::begin(visual, self.canvas);
212        // The brushes the content paints learn which visual paints them
213        // (backdrops, scene lighting).
214        let canvas = &mut *self.canvas;
215        render_scope::with_painting_visual(visual, || {
216            visual.render_core(&mut ServerVisualRenderContext::new(canvas), clip);
217        });
218        if let Some(lit) = lit {
219            lit.end(self.canvas);
220        }
221
222        self.render_children
223    }
224
225    fn post_subgraph(&mut self, visual: &Rc<ServerCompositionVisual>) {
226        if self.full_skip {
227            self.full_skip = false;
228            return;
229        }
230
231        let bitmap_cache_root = self.rendering_to_bitmap_cache && Rc::ptr_eq(visual, &self.root_visual);
232
233        // If we've used cache, those never got pushed in PreSubgraph
234        if !self.used_cache {
235            // What the content pushed for its children (not from upstream).
236            let canvas = &mut *self.canvas;
237            render_scope::with_painting_visual(visual, || {
238                visual.content.render_after_children(visual, &mut ServerVisualRenderContext::new(canvas));
239            });
240
241            if visual.effect().is_some() && visual.sub_tree_bounds.get().is_some() {
242                if let Some(effects) = self.canvas.as_drawing_context_impl_with_effects() {
243                    effects.pop_effect();
244                }
245            }
246
247            if visual.opacity_mask_brush().is_some() && visual.sub_tree_bounds.get().is_some() {
248                self.canvas.pop_opacity_mask();
249            }
250
251            if visual.text_options() != TextOptions::default() {
252                self.canvas.pop_text_options();
253            }
254
255            if visual.render_options() != RenderOptions::default() {
256                self.canvas.pop_render_options();
257            }
258        }
259
260        // A cached visual is always a leaf in the walk (children are skipped), so its
261        // PreSubgraph/PostSubgraph are adjacent. Reset here so the flag never leaks into a
262        // later sibling and causes its effect/mask/options pushes to be left unpopped.
263        self.used_cache = false;
264
265        // If we are rendering to bitmap cache, PreSubgraph skipped those for the root visual
266        if !bitmap_cache_root {
267            if visual.adorned_visual().is_some() {
268                self.adorner_helper_render_post_graph_push_adorner_clip(visual);
269            }
270
271            if let Some(shape) = visual.composition_clip_shape() {
272                shape.pop(self.canvas);
273            }
274
275            if visual.clip().is_some() {
276                self.canvas.pop_geometry_clip();
277            }
278
279            if visual.clip_to_bounds() {
280                self.canvas.pop_clip();
281            }
282
283            if visual.own_clip_rect.get().is_some() {
284                self.walk_context.pop_clip();
285            }
286
287            if visual.own_transform.get().is_some() {
288                self.walk_context.pop_transform();
289                self.canvas.set_transform(self.walk_context.transform);
290            }
291
292            if visual.opacity() != 1.0 {
293                self.canvas.pop_opacity();
294                self.opacity = self.opacity_stack.pop().expect("an opacity was pushed");
295            }
296        }
297    }
298}
299
300impl ServerCompositionVisual {
301    /// Renders the visual and, with `render_children`, its subtree onto
302    /// `canvas`. Returns the numbers of visited and rendered visuals.
303    pub fn render(
304        self: &Rc<Self>,
305        canvas: &mut dyn IDrawingContextImpl,
306        clip: LtrbRect,
307        dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
308        render_children: bool,
309        skip_root_visual_transform: bool,
310        rendering_to_bitmap_cache: bool,
311    ) -> (i32, i32) {
312        self.render_with_stop(
313            canvas,
314            clip,
315            dirty_rects,
316            render_children,
317            skip_root_visual_transform,
318            rendering_to_bitmap_cache,
319            None,
320        )
321    }
322
323    /// Renders the subtree of the visual onto `canvas` up to `stop_at`: the
324    /// visuals before it in paint order, not it and not those after it
325    /// (the backdrop of `stop_at`, winui-materials.md). The transform of the
326    /// canvas is that of the render the backdrop belongs to.
327    pub(crate) fn render_up_to(
328        self: &Rc<Self>,
329        canvas: &mut dyn IDrawingContextImpl,
330        clip: LtrbRect,
331        stop_at: &Rc<ServerCompositionVisual>,
332        skip_root_visual_transform: bool,
333        rendering_to_bitmap_cache: bool,
334    ) {
335        self.render_with_stop(
336            canvas,
337            clip,
338            None,
339            true,
340            skip_root_visual_transform,
341            rendering_to_bitmap_cache,
342            Some(Rc::as_ptr(stop_at)),
343        );
344    }
345
346    #[allow(clippy::too_many_arguments)]
347    fn render_with_stop(
348        self: &Rc<Self>,
349        canvas: &mut dyn IDrawingContextImpl,
350        clip: LtrbRect,
351        dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
352        render_children: bool,
353        skip_root_visual_transform: bool,
354        rendering_to_bitmap_cache: bool,
355        stop_at: Option<*const ServerCompositionVisual>,
356    ) -> (i32, i32) {
357        let Some(compositor) = self.compositor() else { return (0, 0) };
358        let pools = compositor.pools();
359        let transform = canvas.transform();
360        let root = render_scope::RenderRoot {
361            visual: self.clone(),
362            transform,
363            clip,
364            skip_root_visual_transform,
365            rendering_to_bitmap_cache,
366        };
367        render_scope::with_render_root(root, || {
368            let mut render_context = RenderContext::new(
369                self.clone(),
370                canvas,
371                dirty_rects,
372                pools,
373                transform,
374                clip,
375                render_children,
376                skip_root_visual_transform,
377                rendering_to_bitmap_cache,
378            );
379            render_context.stop_at = stop_at;
380            walk(&mut render_context, self, pools);
381            let result = (render_context.visited_visuals, render_context.rendered_visuals);
382            render_context.dispose();
383            result
384        })
385    }
386}