ferroui_base/rendering/composition/server/dirty_rects/
multi_dirty_rect_tracker.rs1use super::i_dirty_rect_tracker::{full_union, pixel_rect_from_rect_unscaled, VisualizeRandom};
2use super::multi_dirty_rect_tracker_c_dirty_region::CDirtyRegion2;
3use super::{IDirtyRectCollector, IDirtyRectTracker};
4use crate::media::immutable::ImmutableSolidColorBrush;
5use crate::media::Color;
6use crate::platform::{IDrawingContextImpl, IPlatformRenderInterface, IPlatformRenderInterfaceRegion, LtrbRect};
7use crate::Thickness;
8use std::cell::{Cell, Ref, RefCell};
9use std::rc::Rc;
10
11pub struct MultiDirtyRectTracker {
14 max_overhead: f64,
15 regions: RefCell<CDirtyRegion2>,
16 clip_region: Rc<dyn IPlatformRenderInterfaceRegion>,
17 inflated_rects: RefCell<Vec<LtrbRect>>,
18 random: VisualizeRandom,
19 combined_rect: Cell<LtrbRect>,
20}
21
22impl MultiDirtyRectTracker {
23 pub fn new(platform_render: &dyn IPlatformRenderInterface, max_dirty_rects: i32, max_overhead: f64) -> Self {
24 Self {
25 max_overhead,
26 regions: RefCell::new(CDirtyRegion2::new(max_dirty_rects)),
27 clip_region: platform_render.create_region(),
28 inflated_rects: RefCell::new(Vec::new()),
29 random: VisualizeRandom::new(),
30 combined_rect: Cell::new(LtrbRect::default()),
31 }
32 }
33
34 pub fn inflated_rects(&self) -> Ref<'_, [LtrbRect]> {
37 Ref::map(self.inflated_rects.borrow(), |rects| rects.as_slice())
38 }
39}
40
41impl IDirtyRectCollector for MultiDirtyRectTracker {
42 fn add_rect(&self, rect: LtrbRect) {
43 self.regions.borrow_mut().add(rect);
44 }
45}
46
47impl IDirtyRectTracker for MultiDirtyRectTracker {
48 fn finalize_frame(&self, bounds: LtrbRect) {
49 let mut inflated_rects = self.inflated_rects.borrow_mut();
50 inflated_rects.clear();
51 self.clip_region.reset();
52
53 let mut regions = self.regions.borrow_mut();
54 let dirty_regions = regions.get_uninflated_dirty_regions();
55
56 let mut combined: Option<LtrbRect> = None;
57 for rect in dirty_regions {
58 let inflated = rect.inflate(Thickness::uniform(1.0)).intersect_or_empty(bounds);
59 inflated_rects.push(inflated);
60 self.clip_region.add_rect(pixel_rect_from_rect_unscaled(inflated));
61 combined = full_union(combined, Some(inflated));
62 }
63
64 self.combined_rect.set(combined.unwrap_or_default());
65 }
66
67 fn begin_draw(&self, ctx: &mut dyn IDrawingContextImpl) {
68 ctx.push_clip_region(&*self.clip_region);
69 }
70
71 fn end_draw(&self, ctx: &mut dyn IDrawingContextImpl) {
72 ctx.pop_clip();
73 }
74
75 fn is_empty(&self) -> bool {
76 self.regions.borrow().is_empty()
77 }
78
79 fn intersects(&self, rect: LtrbRect) -> bool {
80 self.inflated_rects.borrow().iter().any(|r| r.intersects(rect))
81 }
82
83 fn initialize(&self, bounds: LtrbRect) {
84 self.regions.borrow_mut().initialize(bounds, self.max_overhead);
85 self.inflated_rects.borrow_mut().clear();
86 self.clip_region.reset();
87 self.combined_rect.set(LtrbRect::default());
88 }
89
90 fn visualize(&self, context: &mut dyn IDrawingContextImpl) {
91 let brush = ImmutableSolidColorBrush::new(Color::new(
92 150,
93 self.random.next(255) as u8,
94 self.random.next(255) as u8,
95 self.random.next(255) as u8,
96 ));
97 context.draw_region(Some(&brush), None, &*self.clip_region);
98 }
99
100 fn combined_rect(&self) -> LtrbRect {
101 self.combined_rect.get()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::super::i_dirty_rect_tracker::tests::{MockDrawingContext, MockRenderInterface};
108 use super::*;
109 use crate::platform::LtrbPixelRect;
110
111 const BOUNDS: LtrbRect = LtrbRect::new(0.0, 0.0, 1000.0, 1000.0);
112
113 #[test]
114 fn finalize_inflates_by_one_and_clips_to_bounds() {
115 let platform = MockRenderInterface::default();
116 let tracker = MultiDirtyRectTracker::new(&platform, 4, 0.0);
117 let region = platform.regions.borrow()[0].clone();
118 tracker.initialize(BOUNDS);
119 assert!(tracker.is_empty());
120
121 tracker.add_rect(LtrbRect::new(0.0, 0.0, 10.0, 10.0));
122 tracker.add_rect(LtrbRect::new(500.5, 500.5, 510.2, 520.0));
123 assert!(!tracker.is_empty());
124 assert!(tracker.inflated_rects().is_empty());
125
126 tracker.finalize_frame(BOUNDS);
127 assert_eq!(
129 vec![LtrbRect::new(499.0, 499.0, 512.0, 521.0), LtrbRect::new(0.0, 0.0, 11.0, 11.0)],
130 tracker.inflated_rects().to_vec()
131 );
132 assert_eq!(
133 vec![LtrbPixelRect::new(499, 499, 512, 521), LtrbPixelRect::new(0, 0, 11, 11)],
134 *region.rects.borrow()
135 );
136 assert_eq!(LtrbRect::new(0.0, 0.0, 512.0, 521.0), tracker.combined_rect());
137
138 assert!(tracker.intersects(LtrbRect::new(10.5, 10.5, 20.0, 20.0)));
139 assert!(!tracker.intersects(LtrbRect::new(11.0, 11.0, 499.0, 499.0)));
140 assert!(tracker.intersects(LtrbRect::new(100.0, 100.0, 499.5, 499.5)));
141 }
142
143 #[test]
144 fn initialize_resets_everything() {
145 let platform = MockRenderInterface::default();
146 let tracker = MultiDirtyRectTracker::new(&platform, 4, 0.0);
147 let region = platform.regions.borrow()[0].clone();
148 tracker.initialize(BOUNDS);
149 tracker.add_rect(LtrbRect::new(5.0, 5.0, 10.0, 10.0));
150 tracker.finalize_frame(BOUNDS);
151 assert_eq!(1, region.rects.borrow().len());
152
153 tracker.initialize(BOUNDS);
154 assert!(tracker.is_empty());
155 assert!(tracker.inflated_rects().is_empty());
156 assert!(region.rects.borrow().is_empty());
157 assert_eq!(LtrbRect::default(), tracker.combined_rect());
158 tracker.finalize_frame(BOUNDS);
159 assert_eq!(LtrbRect::default(), tracker.combined_rect());
160 }
161
162 #[test]
163 fn rects_added_before_initialize_are_dropped() {
164 let platform = MockRenderInterface::default();
166 let tracker = MultiDirtyRectTracker::new(&platform, 4, 0.0);
167 tracker.add_rect(LtrbRect::new(5.0, 5.0, 10.0, 10.0));
168 assert!(tracker.is_empty());
169 }
170
171 #[test]
172 fn invalid_rect_makes_the_whole_surface_dirty() {
173 let platform = MockRenderInterface::default();
174 let tracker = MultiDirtyRectTracker::new(&platform, 4, 0.0);
175 tracker.initialize(BOUNDS);
176 tracker.add_rect(LtrbRect::new(5.0, 5.0, 10.0, 10.0));
177 tracker.add_rect(LtrbRect::new(f64::NAN, 5.0, 10.0, 10.0));
178 tracker.finalize_frame(BOUNDS);
179 assert_eq!(vec![BOUNDS], tracker.inflated_rects().to_vec());
181 assert_eq!(BOUNDS, tracker.combined_rect());
182 }
183
184 #[test]
185 fn draw_uses_the_region_clip() {
186 let platform = MockRenderInterface::default();
187 let tracker = MultiDirtyRectTracker::new(&platform, 4, 0.0);
188 tracker.initialize(BOUNDS);
189 tracker.add_rect(LtrbRect::new(5.0, 5.0, 10.0, 10.0));
190 tracker.finalize_frame(BOUNDS);
191 let mut ctx = MockDrawingContext::default();
192 tracker.begin_draw(&mut ctx);
193 tracker.end_draw(&mut ctx);
194 tracker.visualize(&mut ctx);
195 assert_eq!(
196 vec![
197 "push_clip_region [LtrbPixelRect { left: 4, top: 4, right: 11, bottom: 11 }]".to_string(),
198 "pop_clip".to_string(),
199 "region alpha 150 rects 1".to_string()
200 ],
201 ctx.log
202 );
203 }
204}