git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

src/wpe/damage.rs (7.7K)

  1 //! What changed between the frame a tab's image holds and the one about to be
  2 //! read, so that only that much is copied out of the engine's buffer and
  3 //! uploaded.
  4 //!
  5 //! WebKit reports each frame's damage — what it repainted since the frame
  6 //! before — once `PropagateDamagingInformation` is on (`host.rs` turns it on
  7 //! for every webview). A frame that is handed back unread still changed the
  8 //! picture, so its damage is kept and folded into the next frame of the same
  9 //! view; that is what lets the readback skip frames *and* copy only regions.
 10 //!
 11 //! The whole point is the idle cost. A page that can scroll has WebKit
 12 //! repainting its overlay scrollbar about sixty times a second, for good
 13 //! (WebKit 2.52; measured in a shadow, and MiniBrowser does it too), and a
 14 //! page with a sliding banner repaints that band. Reading the whole window
 15 //! for each — 35 MB a frame at 3840x2400 — was most of the browser's own CPU
 16 //! on such pages.
 17 
 18 /// A rectangle of the buffer in pixels: `(x, y, width, height)`.
 19 pub type Rect = (u32, u32, u32, u32);
 20 
 21 /// More rectangles than this and they are merged into their bounding box:
 22 /// each one is its own row loop and its own copy region, and a frame that
 23 /// changed in that many places is better read as one.
 24 const MAX_RECTS: usize = 16;
 25 
 26 /// Damage that covers this share of the frame or more is read whole: one
 27 /// contiguous copy beats many row copies adding up to nearly the same bytes.
 28 const FULL_SHARE: f64 = 0.5;
 29 
 30 /// What changed in one view since its frame was last read.
 31 #[derive(Debug, Clone, PartialEq)]
 32 pub enum Damage {
 33     /// Unknown or everything: read the whole frame.
 34     Full,
 35     /// Only these, in buffer pixels, not yet clamped to the buffer.
 36     Rects(Vec<(i32, i32, i32, i32)>),
 37 }
 38 
 39 impl Damage {
 40     /// Fold one frame's report in. A frame that reports no rectangles did
 41     /// not say what it changed — that is what an engine without damage
 42     /// propagation sends — so it counts as everything.
 43     pub fn add(&mut self, frame: &[(i32, i32, i32, i32)]) {
 44         if frame.is_empty() {
 45             *self = Damage::Full;
 46             return;
 47         }
 48         if let Damage::Rects(rects) = self {
 49             for r in frame {
 50                 if !rects.contains(r) {
 51                     rects.push(*r);
 52                 }
 53             }
 54         }
 55     }
 56 
 57     /// The regions to copy from a `width` x `height` buffer, or `None` when
 58     /// the whole frame should be read instead.
 59     pub fn regions(&self, width: u32, height: u32) -> Option<Vec<Rect>> {
 60         let Damage::Rects(raw) = self else { return None };
 61         let mut rects: Vec<Rect> = Vec::new();
 62         for r in raw.iter().filter_map(|&r| clamp(r, width, height)) {
 63             // Two reports can clamp to the same rectangle.
 64             if !rects.contains(&r) {
 65                 rects.push(r);
 66             }
 67         }
 68         // A rectangle inside another adds nothing but a second copy of it.
 69         // (No two are equal by now, so containment is strict.)
 70         let mut i = 0;
 71         while i < rects.len() {
 72             let inner = rects[i];
 73             if rects.iter().enumerate().any(|(j, &outer)| j != i && contains(outer, inner)) {
 74                 rects.remove(i);
 75             } else {
 76                 i += 1;
 77             }
 78         }
 79         if rects.len() > MAX_RECTS {
 80             rects = vec![bounding(&rects)];
 81         }
 82         let area: u64 = rects.iter().map(|r| r.2 as u64 * r.3 as u64).sum();
 83         if area as f64 >= FULL_SHARE * width as f64 * height as f64 {
 84             return None;
 85         }
 86         Some(rects)
 87     }
 88 }
 89 
 90 fn clamp((x, y, w, h): (i32, i32, i32, i32), width: u32, height: u32) -> Option<Rect> {
 91     let x0 = x.max(0) as i64;
 92     let y0 = y.max(0) as i64;
 93     let x1 = (x as i64 + w as i64).min(width as i64);
 94     let y1 = (y as i64 + h as i64).min(height as i64);
 95     (x1 > x0 && y1 > y0).then(|| (x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32))
 96 }
 97 
 98 fn contains(outer: Rect, inner: Rect) -> bool {
 99     inner.0 >= outer.0
100         && inner.1 >= outer.1
101         && inner.0 + inner.2 <= outer.0 + outer.2
102         && inner.1 + inner.3 <= outer.1 + outer.3
103 }
104 
105 fn bounding(rects: &[Rect]) -> Rect {
106     let x0 = rects.iter().map(|r| r.0).min().unwrap_or(0);
107     let y0 = rects.iter().map(|r| r.1).min().unwrap_or(0);
108     let x1 = rects.iter().map(|r| r.0 + r.2).max().unwrap_or(0);
109     let y1 = rects.iter().map(|r| r.1 + r.3).max().unwrap_or(0);
110     (x0, y0, x1 - x0, y1 - y0)
111 }
112 
113 /// Copy `regions` of a 4-byte-per-pixel image whose rows are `stride` bytes
114 /// apart into `out`, each region tightly packed, one after another — the
115 /// layout `cce_ui::vk::update_pixel_regions` takes.
116 ///
117 /// # Safety
118 /// `src` must be readable for every byte of every region at `stride`.
119 pub unsafe fn pack(src: *const u8, stride: usize, regions: &[Rect], out: &mut [u8]) {
120     let mut at = 0usize;
121     for &(x, y, w, h) in regions {
122         let row = w as usize * 4;
123         for r in 0..h as usize {
124             let from = src.add((y as usize + r) * stride + x as usize * 4);
125             std::ptr::copy_nonoverlapping(from, out.as_mut_ptr().add(at), row);
126             at += row;
127         }
128     }
129 }
130 
131 /// Bytes [`pack`] writes for `regions`.
132 pub fn packed_len(regions: &[Rect]) -> usize {
133     regions.iter().map(|r| r.2 as usize * r.3 as usize * 4).sum()
134 }
135 
136 #[cfg(test)]
137 mod tests {
138     use super::*;
139 
140     #[test]
141     fn a_frame_without_rects_is_everything() {
142         let mut d = Damage::Rects(vec![]);
143         d.add(&[(0, 0, 10, 10)]);
144         d.add(&[]);
145         assert_eq!(d, Damage::Full);
146         // And stays so: later reports cannot narrow an unknown change.
147         d.add(&[(0, 0, 1, 1)]);
148         assert_eq!(d.regions(100, 100), None);
149     }
150 
151     #[test]
152     fn skipped_frames_accumulate_without_repeats() {
153         let mut d = Damage::Rects(vec![]);
154         d.add(&[(1238, 0, 42, 720)]);
155         d.add(&[(1238, 0, 42, 720)]);
156         d.add(&[(0, 0, 10, 10)]);
157         assert_eq!(d.regions(1280, 720), Some(vec![(1238, 0, 42, 720), (0, 0, 10, 10)]));
158     }
159 
160     #[test]
161     fn rects_are_clamped_to_the_buffer() {
162         // The engine's first frame reports its pre-resize size.
163         let mut d = Damage::Rects(vec![]);
164         d.add(&[(-5, -5, 20, 20), (1270, 710, 50, 50)]);
165         assert_eq!(d.regions(1280, 720), Some(vec![(0, 0, 15, 15), (1270, 710, 10, 10)]));
166         let mut off = Damage::Rects(vec![]);
167         off.add(&[(2000, 0, 10, 10)]);
168         assert_eq!(off.regions(1280, 720), Some(vec![]));
169     }
170 
171     #[test]
172     fn contained_rects_are_dropped() {
173         let mut d = Damage::Rects(vec![]);
174         d.add(&[(0, 0, 100, 100), (10, 10, 5, 5)]);
175         assert_eq!(d.regions(1000, 1000), Some(vec![(0, 0, 100, 100)]));
176     }
177 
178     #[test]
179     fn large_damage_reads_the_whole_frame() {
180         let mut d = Damage::Rects(vec![]);
181         d.add(&[(0, 0, 1280, 400)]);
182         assert_eq!(d.regions(1280, 720), None);
183     }
184 
185     #[test]
186     fn many_rects_merge_into_their_bounds() {
187         let mut d = Damage::Rects(vec![]);
188         let many: Vec<_> = (0..20).map(|i| (i * 10, 0, 5, 5)).collect();
189         d.add(&many);
190         assert_eq!(d.regions(1280, 720), Some(vec![(0, 0, 195, 5)]));
191     }
192 
193     #[test]
194     fn pack_copies_each_region_tightly() {
195         // A 4x3 image, stride padded to 5 pixels; each pixel's bytes are its
196         // index, so what lands where is readable.
197         let (w, h, stride) = (4usize, 3usize, 20usize);
198         let mut src = vec![0xEEu8; stride * h];
199         for y in 0..h {
200             for x in 0..w {
201                 src[y * stride + x * 4..][..4].fill((y * w + x) as u8);
202             }
203         }
204         let regions = [(1, 0, 2, 2), (3, 2, 1, 1)];
205         let mut out = vec![0u8; packed_len(&regions)];
206         unsafe { pack(src.as_ptr(), stride, &regions, &mut out) };
207         let px: Vec<u8> = out.chunks(4).map(|p| p[0]).collect();
208         assert_eq!(px, vec![1, 2, 5, 6, 11]);
209     }
210 }