ferroui_base/rendering/composition/server/server_composition_visual/
render.rs1use 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 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 if visual.own_transform.get().is_some() {
114 self.walk_context.push_set_transform(effective_new_transform); 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 if !self.handle_pre_graph_transform_clip_opacity(visual) {
149 self.full_skip = true;
150 return false;
151 }
152
153 if visual.adorned_visual().is_some() {
155 self.adorner_helper_render_pre_graph_push_adorner_clip(visual);
156 }
157
158 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 !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 self.used_cache = false;
225
226 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 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}