ferroui_base/rendering/composition/server/server_composition_visual/
update.rs1use super::walker::{walk, IServerTreeVisitor, TreeWalkContext};
2use super::ServerCompositionVisual;
3use crate::media::EffectExtensions;
4use crate::platform::LtrbRect;
5use crate::rendering::composition::server::{CompositorPools, IDirtyRectCollector};
6use crate::Matrix;
7use std::rc::Rc;
8
9struct UpdateContext {
10 context: TreeWalkContext,
11 dirty_region: Rc<dyn IDirtyRectCollector>,
12 dirty_region_disable_count: i32,
13 dirty_region_disable_count_stack: Vec<i32>,
14 dirty_region_collector_stack: Vec<Rc<dyn IDirtyRectCollector>>,
15}
16
17impl UpdateContext {
18 fn new(pools: &CompositorPools, dirty_rects: Rc<dyn IDirtyRectCollector>, transform: Matrix, clip: LtrbRect) -> Self {
19 Self {
20 dirty_region: dirty_rects,
21 context: TreeWalkContext::new(pools, transform, clip),
22 dirty_region_disable_count: 0,
23 dirty_region_disable_count_stack: pools.int_stack_pool.rent(),
24 dirty_region_collector_stack: pools.dirty_rect_collector_stack_pool.rent(),
25 }
26 }
27
28 fn are_dirty_regions_disabled(&self) -> bool {
29 self.dirty_region_disable_count != 0
30 }
31
32 fn push_cache_if_needed(&mut self, visual: &ServerCompositionVisual) {
33 if let Some(cache) = visual.cache() {
34 let collector: Rc<dyn IDirtyRectCollector> = cache;
35 self.dirty_region_collector_stack.push(std::mem::replace(&mut self.dirty_region, collector));
36 self.dirty_region_disable_count_stack.push(self.dirty_region_disable_count);
37 self.dirty_region_disable_count = 0;
38 self.context.push_set_transform(Matrix::IDENTITY);
39 self.context.reset_clip(LtrbRect::INFINITE);
40 }
41 }
42
43 fn pop_cache_if_needed(&mut self, visual: &ServerCompositionVisual) {
44 if let Some(cache) = visual.cache() {
45 self.context.pop_clip();
46 self.context.pop_transform();
47 self.dirty_region = self.dirty_region_collector_stack.pop().expect("a collector was pushed");
48 self.dirty_region_disable_count = self.dirty_region_disable_count_stack.pop().expect("a count was pushed");
49 if cache.is_dirty() {
50 self.add_to_dirty_region(visual.sub_tree_bounds.get());
51 }
52 }
53 }
54
55 fn need_to_push_bounds_affecting_properties(node: &ServerCompositionVisual) -> bool {
56 node.is_dirty_for_render_in_subgraph.get()
57 || node.needs_to_add_extra_dirty_rect_to_dirty_region.get()
58 || node.content_changed.get()
59 }
60
61 fn finalize_subtree_bounds(node: &ServerCompositionVisual) {
62 if !node.visible() {
65 node.sub_tree_bounds.set(None);
66 }
67
68 if let Some(mut bounds) = node.sub_tree_bounds.get() {
69 if let Some(effect) = node.effect() {
70 bounds = bounds.inflate(EffectExtensions::get_effect_output_padding(Some(&*effect)));
71 node.sub_tree_bounds.set(Some(bounds));
72 }
73
74 if let Some(clip) = node.own_clip_rect.get() {
75 node.sub_tree_bounds.set(bounds.intersect_or_null(clip));
76 }
77 }
78
79 match (node.sub_tree_bounds.get(), node.own_transform.get()) {
80 (None, _) => node.transformed_sub_tree_bounds.set(None),
81 (Some(bounds), Some(transform)) => {
82 node.transformed_sub_tree_bounds.set(Some(bounds.transform_to_aabb(transform)))
83 }
84 (Some(bounds), None) => node.transformed_sub_tree_bounds.set(Some(bounds)),
85 }
86
87 node.enqueue_for_readback_update();
88 }
89
90 fn add_to_dirty_region(&self, bounds: Option<LtrbRect>) {
91 let Some(bounds) = bounds else { return };
92 if self.dirty_region_disable_count != 0 {
93 return;
94 }
95
96 let transformed = bounds.transform_to_aabb(self.context.transform).intersect_or_empty(self.context.clip);
97 if transformed.is_zero_size() {
98 return;
99 }
100
101 self.dirty_region.add_rect(transformed);
102 }
103
104 fn push_bounds_affecting_properties(&mut self, node: &ServerCompositionVisual) {
105 if let Some(transform) = node.own_transform.get() {
106 self.context.push_transform(transform);
107 }
108
109 if let Some(clip) = node.own_clip_rect.get() {
110 self.context.push_clip(clip.transform_to_aabb(self.context.transform));
111 }
112 }
113
114 fn pop_bounds_affecting_properties(&mut self, node: &ServerCompositionVisual) {
115 if node.own_transform.get().is_some() {
116 self.context.pop_transform();
117 }
118 if node.own_clip_rect.get().is_some() {
119 self.context.pop_clip();
120 }
121 }
122
123 fn dispose(self, pools: &CompositorPools) {
124 pools.int_stack_pool.return_stack(self.dirty_region_disable_count_stack);
125 pools.dirty_rect_collector_stack_pool.return_stack(self.dirty_region_collector_stack);
126 self.context.dispose(pools);
127 }
128}
129
130impl IServerTreeVisitor for UpdateContext {
131 fn pre_subgraph(&mut self, node: &Rc<ServerCompositionVisual>) -> bool {
132 let visit_children = node.is_dirty_for_render_in_subgraph.get() || node.needs_bounding_box_update.get();
133
134 if node.needs_bounding_box_update.get() && node.opacity_mask_brush().is_some() {
137 node.is_dirty_for_render.set(true);
138 }
139
140 if node.is_dirty_for_render.get()
144 || (node.is_dirty_for_render_in_subgraph.get() && node.content.has_effect(node))
145 {
146 if node.needs_bounding_box_update.get() && !self.are_dirty_regions_disabled() {
149 self.add_to_dirty_region(node.transformed_sub_tree_bounds.get());
154 }
155
156 self.dirty_region_disable_count += 1;
159 }
160
161 if Self::need_to_push_bounds_affecting_properties(node) {
166 if !self.are_dirty_regions_disabled() {
169 self.push_bounds_affecting_properties(node);
170 }
171
172 self.push_cache_if_needed(node);
173 }
174
175 if node.needs_bounding_box_update.get() {
176 node.sub_tree_bounds.set(node.own_content_bounds.get());
180 }
181
182 visit_children
183 }
184
185 fn post_subgraph(&mut self, node: &Rc<ServerCompositionVisual>) {
186 let parent = node.parent();
187
188 if node.needs_bounding_box_update.get() {
189 Self::finalize_subtree_bounds(node);
194 }
195
196 if let Some(parent) = parent {
199 if parent.needs_bounding_box_update.get() {
201 parent
202 .sub_tree_bounds
203 .set(LtrbRect::full_union(parent.sub_tree_bounds.get(), node.transformed_sub_tree_bounds.get()));
204 }
205 }
206
207 if node.needs_to_add_extra_dirty_rect_to_dirty_region.get() {
213 self.add_to_dirty_region(Some(node.extra_dirty_rect.get()));
214 }
215
216 if Self::need_to_push_bounds_affecting_properties(node) {
219 self.pop_cache_if_needed(node);
220
221 if !self.are_dirty_regions_disabled() {
222 self.pop_bounds_affecting_properties(node);
223 }
224 }
225
226 if node.is_dirty_for_render.get() || (node.is_dirty_for_render_in_subgraph.get() && node.effect().is_some()) {
230 self.dirty_region_disable_count -= 1;
231 self.add_to_dirty_region(node.transformed_sub_tree_bounds.get());
232 }
233
234 node.is_dirty_for_render.set(false);
235 node.is_dirty_for_render_in_subgraph.set(false);
236 node.needs_bounding_box_update.set(false);
237 node.needs_to_add_extra_dirty_rect_to_dirty_region.set(false);
238 node.content_changed.set(false);
239 }
240}
241
242impl ServerCompositionVisual {
243 pub fn update_root(self: &Rc<Self>, tracker: Rc<dyn IDirtyRectCollector>, transform: Matrix, clip: LtrbRect) {
244 let Some(compositor) = self.compositor() else { return };
245 let pools = compositor.pools();
246 let mut context = UpdateContext::new(pools, tracker, transform, clip);
247 walk(&mut context, self, pools);
248 context.dispose(pools);
249 }
250}