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::{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 stop_at: Option<*const ServerCompositionVisual>,
29 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 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 if visual.own_transform.get().is_some() {
121 self.walk_context.push_set_transform(effective_new_transform); 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 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 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 if !self.handle_pre_graph_transform_clip_opacity(visual) {
171 self.full_skip = true;
172 return false;
173 }
174
175 if visual.adorned_visual().is_some() {
177 self.adorner_helper_render_pre_graph_push_adorner_clip(visual);
178 }
179
180 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 let lit = LitContent::begin(visual, self.canvas);
212 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 !self.used_cache {
235 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 self.used_cache = false;
264
265 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 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 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}