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::{CompositorPools, IDirtyRectTracker, 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}
27
28impl<'a> RenderContext<'a> {
29    #[allow(clippy::too_many_arguments)]
30    fn new(
31        root_visual: Rc<ServerCompositionVisual>,
32        canvas: &'a mut dyn IDrawingContextImpl,
33        mut dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
34        pools: &'a CompositorPools,
35        matrix: Matrix,
36        mut clip: LtrbRect,
37        render_children: bool,
38        skip_root_visual_transform: bool,
39        rendering_to_bitmap_cache: bool,
40    ) -> Self {
41        if let Some(tracker) = &dirty_rects {
42            let dirty_clip = tracker.combined_rect();
43            let is_single = tracker.is_single_dirty_rect_tracker();
44            if is_single {
45                dirty_rects = None;
46            }
47            clip = clip.intersect_or_empty(dirty_clip);
48        }
49
50        Self {
51            canvas,
52            dirty_rects,
53            pools,
54            render_children,
55            root_visual,
56            walk_context: TreeWalkContext::new(pools, matrix, clip),
57            opacity: 1.0,
58            opacity_stack: pools.double_stack_pool.rent(),
59            full_skip: false,
60            used_cache: false,
61            rendered_visuals: 0,
62            visited_visuals: 0,
63            skip_next_visual_transform: skip_root_visual_transform,
64            rendering_to_bitmap_cache,
65            adorner_pushed_clip_stack: None,
66            current_adorner_layer: None,
67        }
68    }
69
70    fn handle_pre_graph_transform_clip_opacity(&mut self, visual: &ServerCompositionVisual) -> bool {
71        let Some(transformed_sub_tree_bounds) = visual.transformed_sub_tree_bounds.get() else { return false };
72        if !visual.visible() {
73            return false;
74        }
75        let visual_opacity = visual.opacity() as f64;
76        let effective_opacity = visual_opacity * self.opacity;
77        if effective_opacity <= 0.003 {
78            return false;
79        }
80
81        let mut effective_new_transform = self.walk_context.transform;
82        if let Some(own_transform) = visual.own_transform.get() {
83            if !self.skip_next_visual_transform {
84                effective_new_transform = own_transform * self.walk_context.transform;
85            }
86        }
87        self.skip_next_visual_transform = false;
88
89        let mut effective_clip = self.walk_context.clip;
90        if let Some(own_clip) = visual.own_clip_rect.get() {
91            effective_clip = effective_clip.intersect_or_empty(own_clip.transform_to_aabb(effective_new_transform));
92        }
93
94        let world_bounds = transformed_sub_tree_bounds.transform_to_aabb(self.walk_context.transform);
95        if !effective_clip.intersects(world_bounds)
96            || self.dirty_rects.as_ref().is_some_and(|d| !d.intersects(world_bounds))
97        {
98            return false;
99        }
100
101        self.rendered_visuals += 1;
102
103        // We are still in parent's coordinate space here
104
105        if visual_opacity != 1.0 {
106            self.opacity_stack.push(self.opacity);
107            self.opacity = effective_opacity;
108            self.canvas.push_opacity(visual_opacity, Some(transformed_sub_tree_bounds.to_rect()));
109        }
110
111        // Switch coordinate space to this visual's space
112
113        if visual.own_transform.get().is_some() {
114            self.walk_context.push_set_transform(effective_new_transform); // Reuse one computed before
115            self.canvas.set_transform(effective_new_transform);
116        }
117
118        if visual.own_clip_rect.get().is_some() {
119            self.walk_context.push_clip(effective_clip);
120        }
121
122        if visual.clip_to_bounds() {
123            visual.content.push_clip_to_bounds(visual, self.canvas);
124        }
125
126        if let Some(clip) = visual.clip() {
127            self.canvas.push_geometry_clip(&*clip);
128        }
129
130        true
131    }
132
133    fn dispose(mut self) {
134        let pools = self.pools;
135        self.adorner_helper_dispose();
136        pools.double_stack_pool.return_stack(self.opacity_stack);
137        self.walk_context.dispose(pools);
138    }
139}
140
141impl IServerTreeVisitor for RenderContext<'_> {
142    fn pre_subgraph(&mut self, visual: &Rc<ServerCompositionVisual>) -> bool {
143        self.visited_visuals += 1;
144        let bitmap_cache_root = self.rendering_to_bitmap_cache && Rc::ptr_eq(visual, &self.root_visual);
145        if !bitmap_cache_root {
146            // Skip those for the root visual if we are rendering to bitmap cache
147            // Push transform, clip, opacity and check if those make the visual effectively invisible
148            if !self.handle_pre_graph_transform_clip_opacity(visual) {
149                self.full_skip = true;
150                return false;
151            }
152
153            // Push adorner clip
154            if visual.adorned_visual().is_some() {
155                self.adorner_helper_render_pre_graph_push_adorner_clip(visual);
156            }
157
158            // If caching is enabled, draw from cache and skip rendering
159            if let Some(cache) = visual.cache() {
160                let (visited, rendered) = cache.draw(self.canvas);
161                self.visited_visuals += visited;
162                self.rendered_visuals += rendered;
163                self.used_cache = true;
164                return false;
165            }
166        }
167
168        if visual.render_options() != RenderOptions::default() {
169            self.canvas.push_render_options(visual.render_options());
170        }
171
172        if visual.text_options() != TextOptions::default() {
173            self.canvas.push_text_options(visual.text_options());
174        }
175
176        if let (Some(mask), Some(bounds)) = (visual.opacity_mask_brush(), visual.sub_tree_bounds.get()) {
177            self.canvas.push_opacity_mask(&*mask, bounds.to_rect());
178        }
179
180        if let (Some(effect), Some(bounds)) = (visual.effect(), visual.sub_tree_bounds.get()) {
181            if let Some(effects) = self.canvas.as_drawing_context_impl_with_effects() {
182                effects.push_effect(Some(bounds.to_rect()), &*effect);
183            }
184        }
185
186        let clip = self.walk_context.clip;
187        visual.render_core(&mut ServerVisualRenderContext::new(self.canvas), clip);
188
189        self.render_children
190    }
191
192    fn post_subgraph(&mut self, visual: &Rc<ServerCompositionVisual>) {
193        if self.full_skip {
194            self.full_skip = false;
195            return;
196        }
197
198        let bitmap_cache_root = self.rendering_to_bitmap_cache && Rc::ptr_eq(visual, &self.root_visual);
199
200        // If we've used cache, those never got pushed in PreSubgraph
201        if !self.used_cache {
202            if visual.effect().is_some() && visual.sub_tree_bounds.get().is_some() {
203                if let Some(effects) = self.canvas.as_drawing_context_impl_with_effects() {
204                    effects.pop_effect();
205                }
206            }
207
208            if visual.opacity_mask_brush().is_some() && visual.sub_tree_bounds.get().is_some() {
209                self.canvas.pop_opacity_mask();
210            }
211
212            if visual.text_options() != TextOptions::default() {
213                self.canvas.pop_text_options();
214            }
215
216            if visual.render_options() != RenderOptions::default() {
217                self.canvas.pop_render_options();
218            }
219        }
220
221        // A cached visual is always a leaf in the walk (children are skipped), so its
222        // PreSubgraph/PostSubgraph are adjacent. Reset here so the flag never leaks into a
223        // later sibling and causes its effect/mask/options pushes to be left unpopped.
224        self.used_cache = false;
225
226        // If we are rendering to bitmap cache, PreSubgraph skipped those for the root visual
227        if !bitmap_cache_root {
228            if visual.adorned_visual().is_some() {
229                self.adorner_helper_render_post_graph_push_adorner_clip(visual);
230            }
231
232            if visual.clip().is_some() {
233                self.canvas.pop_geometry_clip();
234            }
235
236            if visual.clip_to_bounds() {
237                self.canvas.pop_clip();
238            }
239
240            if visual.own_clip_rect.get().is_some() {
241                self.walk_context.pop_clip();
242            }
243
244            if visual.own_transform.get().is_some() {
245                self.walk_context.pop_transform();
246                self.canvas.set_transform(self.walk_context.transform);
247            }
248
249            if visual.opacity() != 1.0 {
250                self.canvas.pop_opacity();
251                self.opacity = self.opacity_stack.pop().expect("an opacity was pushed");
252            }
253        }
254    }
255}
256
257impl ServerCompositionVisual {
258    /// Renders the visual and, with `render_children`, its subtree onto
259    /// `canvas`. Returns the numbers of visited and rendered visuals.
260    pub fn render(
261        self: &Rc<Self>,
262        canvas: &mut dyn IDrawingContextImpl,
263        clip: LtrbRect,
264        dirty_rects: Option<Rc<dyn IDirtyRectTracker>>,
265        render_children: bool,
266        skip_root_visual_transform: bool,
267        rendering_to_bitmap_cache: bool,
268    ) -> (i32, i32) {
269        let Some(compositor) = self.compositor() else { return (0, 0) };
270        let pools = compositor.pools();
271        let transform = canvas.transform();
272        let mut render_context = RenderContext::new(
273            self.clone(),
274            canvas,
275            dirty_rects,
276            pools,
277            transform,
278            clip,
279            render_children,
280            skip_root_visual_transform,
281            rendering_to_bitmap_cache,
282        );
283        walk(&mut render_context, self, pools);
284        let result = (render_context.visited_visuals, render_context.rendered_visuals);
285        render_context.dispose();
286        result
287    }
288}