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 if let Some(padding) = node.content.sub_tree_bounds_padding(node) {
75 bounds = bounds.inflate(padding);
76 node.sub_tree_bounds.set(Some(bounds));
77 }
78
79 if let Some(clip) = node.own_clip_rect.get() {
80 node.sub_tree_bounds.set(bounds.intersect_or_null(clip));
81 }
82 }
83
84 match (node.sub_tree_bounds.get(), node.own_transform.get()) {
85 (None, _) => node.transformed_sub_tree_bounds.set(None),
86 (Some(bounds), Some(transform)) => {
87 node.transformed_sub_tree_bounds.set(Some(bounds.transform_to_aabb(transform)))
88 }
89 (Some(bounds), None) => node.transformed_sub_tree_bounds.set(Some(bounds)),
90 }
91
92 node.enqueue_for_readback_update();
93 }
94
95 fn add_to_dirty_region(&self, bounds: Option<LtrbRect>) {
96 let Some(bounds) = bounds else { return };
97 if self.dirty_region_disable_count != 0 {
98 return;
99 }
100
101 let transformed = bounds.transform_to_aabb(self.context.transform).intersect_or_empty(self.context.clip);
102 if transformed.is_zero_size() {
103 return;
104 }
105
106 self.dirty_region.add_rect(transformed);
107 }
108
109 fn push_bounds_affecting_properties(&mut self, node: &ServerCompositionVisual) {
110 if let Some(transform) = node.own_transform.get() {
111 self.context.push_transform(transform);
112 }
113
114 if let Some(clip) = node.own_clip_rect.get() {
115 self.context.push_clip(clip.transform_to_aabb(self.context.transform));
116 }
117 }
118
119 fn pop_bounds_affecting_properties(&mut self, node: &ServerCompositionVisual) {
120 if node.own_transform.get().is_some() {
121 self.context.pop_transform();
122 }
123 if node.own_clip_rect.get().is_some() {
124 self.context.pop_clip();
125 }
126 }
127
128 fn dispose(self, pools: &CompositorPools) {
129 pools.int_stack_pool.return_stack(self.dirty_region_disable_count_stack);
130 pools.dirty_rect_collector_stack_pool.return_stack(self.dirty_region_collector_stack);
131 self.context.dispose(pools);
132 }
133}
134
135impl IServerTreeVisitor for UpdateContext {
136 fn pre_subgraph(&mut self, node: &Rc<ServerCompositionVisual>) -> bool {
137 let visit_children = node.is_dirty_for_render_in_subgraph.get() || node.needs_bounding_box_update.get();
138
139 if node.needs_bounding_box_update.get() && node.opacity_mask_brush().is_some() {
142 node.is_dirty_for_render.set(true);
143 }
144
145 if node.is_dirty_for_render.get()
149 || (node.is_dirty_for_render_in_subgraph.get() && node.content.has_effect(node))
150 {
151 if node.needs_bounding_box_update.get() && !self.are_dirty_regions_disabled() {
154 self.add_to_dirty_region(node.transformed_sub_tree_bounds.get());
159 }
160
161 self.dirty_region_disable_count += 1;
164 }
165
166 if Self::need_to_push_bounds_affecting_properties(node) {
171 if !self.are_dirty_regions_disabled() {
174 self.push_bounds_affecting_properties(node);
175 }
176
177 self.push_cache_if_needed(node);
178 }
179
180 if node.needs_bounding_box_update.get() {
181 if node.content.has_update_pass_content_bounds() {
187 node.own_content_bounds
188 .set(node.compute_own_content_bounds().and_then(LtrbRect::null_if_zero_size));
189 }
190 node.sub_tree_bounds.set(node.own_content_bounds.get());
191 }
192
193 visit_children
194 }
195
196 fn post_subgraph(&mut self, node: &Rc<ServerCompositionVisual>) {
197 let parent = node.parent();
198
199 if node.needs_bounding_box_update.get() {
200 Self::finalize_subtree_bounds(node);
205 }
206
207 if let Some(parent) = parent {
210 if parent.needs_bounding_box_update.get() {
212 parent
213 .sub_tree_bounds
214 .set(LtrbRect::full_union(parent.sub_tree_bounds.get(), node.transformed_sub_tree_bounds.get()));
215 }
216 }
217
218 if node.needs_to_add_extra_dirty_rect_to_dirty_region.get() {
224 self.add_to_dirty_region(Some(node.extra_dirty_rect.get()));
225 }
226
227 if Self::need_to_push_bounds_affecting_properties(node) {
230 self.pop_cache_if_needed(node);
231
232 if !self.are_dirty_regions_disabled() {
233 self.pop_bounds_affecting_properties(node);
234 }
235 }
236
237 if node.is_dirty_for_render.get()
243 || (node.is_dirty_for_render_in_subgraph.get() && node.content.has_effect(node))
244 {
245 self.dirty_region_disable_count -= 1;
246 self.add_to_dirty_region(node.transformed_sub_tree_bounds.get());
247 }
248
249 node.is_dirty_for_render.set(false);
250 node.is_dirty_for_render_in_subgraph.set(false);
251 node.needs_bounding_box_update.set(false);
252 node.needs_to_add_extra_dirty_rect_to_dirty_region.set(false);
253 node.content_changed.set(false);
254 }
255}
256
257impl ServerCompositionVisual {
258 pub fn update_root(self: &Rc<Self>, tracker: Rc<dyn IDirtyRectCollector>, transform: Matrix, clip: LtrbRect) {
259 let Some(compositor) = self.compositor() else { return };
260 let pools = compositor.pools();
261 let mut context = UpdateContext::new(pools, tracker, transform, clip);
262 walk(&mut context, self, pools);
263 context.dispose(pools);
264 }
265}