1use kurbo::Rect;
2use std::collections::{HashMap, HashSet, VecDeque};
3
4pub struct LayerSurfaceSize {
6 pub logical_width: f32,
7 pub logical_height: f32,
8 pub surface_width: u32,
9 pub surface_height: u32,
10 pub dpr: [f32; 2],
11}
12
13pub struct LayerSurfaceEntry {
15 pub key: String,
16 pub host_signature: String,
17 pub origin_x: f32,
18 pub origin_y: f32,
19 pub replay_priority: i32,
20 pub surface: LayerSurfaceSize,
21}
22
23pub struct LayerSurfaceLayout {
25 pub surfaces: Vec<LayerSurfaceEntry>,
26 pub active: bool,
27}
28
29#[derive(Clone, Copy, Debug)]
30pub struct VisibleSurfaceEscape {
31 pub previous_bounds: Rect,
32 pub visible_bounds: Rect,
33 pub visible_surfaces: usize,
34 pub escaped_visible_surfaces: usize,
35 pub max_gap_x: f64,
36 pub max_gap_y: f64,
37}
38
39#[derive(Clone, Copy, Debug, Default)]
40pub enum LayoutChangeKind {
41 #[default]
42 Unchanged,
43 OriginOnly,
44 Resized,
45}
46
47pub fn surface_intersects_coverage_bounds(
48 bounds: &Rect,
49 origin_x: f64,
50 origin_y: f64,
51 width: f64,
52 height: f64,
53) -> bool {
54 if !bounds.x0.is_finite()
55 || !bounds.y0.is_finite()
56 || !bounds.x1.is_finite()
57 || !bounds.y1.is_finite()
58 {
59 return true;
60 }
61
62 let x1 = origin_x + width;
63 let y1 = origin_y + height;
64 bounds.x1 >= origin_x && bounds.x0 <= x1 && bounds.y1 >= origin_y && bounds.y0 <= y1
65}
66
67pub fn visible_surface_escape(
68 previous_surface_bounds: &[Rect],
69 current_surfaces: &[LayerSurfaceEntry],
70) -> Option<VisibleSurfaceEscape> {
71 let previous_bounds = union_bounds(previous_surface_bounds.iter().copied())?;
72 let mut visible_bounds: Option<Rect> = None;
73 let mut visible_surfaces = 0;
74 let mut escaped_visible_surfaces = 0;
75 let mut max_gap_x: f64 = 0.0;
76 let mut max_gap_y: f64 = 0.0;
77
78 for surface in current_surfaces
79 .iter()
80 .filter(|surface| surface.replay_priority <= 0)
81 {
82 visible_surfaces += 1;
83 let bounds = surface_bounds(surface);
84 visible_bounds = Some(match visible_bounds {
85 Some(acc) => acc.union(bounds),
86 None => bounds,
87 });
88
89 if !rect_contains(&previous_bounds, &bounds) {
90 escaped_visible_surfaces += 1;
91 let (gap_x, gap_y) = rect_escape_gap(&previous_bounds, &bounds);
92 max_gap_x = max_gap_x.max(gap_x);
93 max_gap_y = max_gap_y.max(gap_y);
94 }
95 }
96
97 let visible_bounds = visible_bounds?;
98 (escaped_visible_surfaces > 0).then_some(VisibleSurfaceEscape {
99 previous_bounds,
100 visible_bounds,
101 visible_surfaces,
102 escaped_visible_surfaces,
103 max_gap_x,
104 max_gap_y,
105 })
106}
107
108pub fn replay_batches_by_priority(mut entries: Vec<(usize, i32)>) -> Vec<Vec<usize>> {
109 if entries.is_empty() {
110 return Vec::new();
111 }
112 entries.sort_by_key(|(index, priority)| (*priority, *index));
113
114 let mut batches = Vec::new();
118 let mut current_priority = entries[0].1;
119 let mut current_batch = Vec::new();
120 for (index, priority) in entries {
121 if priority != current_priority {
122 batches.push(current_batch);
123 current_batch = Vec::new();
124 current_priority = priority;
125 }
126 current_batch.push(index);
127 }
128 batches.push(current_batch);
129 batches
130}
131
132#[derive(Clone, Copy, Debug)]
134pub struct ReplayPriorityEntry {
135 pub index: usize,
136 pub priority: i32,
137 previous_bounds: Rect,
138 current_bounds: Rect,
139}
140
141impl ReplayPriorityEntry {
142 pub fn new(index: usize, priority: i32, previous_bounds: Rect, current_bounds: Rect) -> Self {
143 Self {
144 index,
145 priority,
146 previous_bounds,
147 current_bounds,
148 }
149 }
150}
151
152#[derive(Clone, Copy)]
153enum ReplayDirectionAxis {
154 X,
155 Y,
156}
157
158const REPLAY_DIRECTION_EPSILON: f64 = 0.5;
159
160pub fn replay_batches_by_directional_priority(entries: &[ReplayPriorityEntry]) -> Vec<Vec<usize>> {
162 if entries.is_empty() {
163 return Vec::new();
164 }
165
166 let Some((axis, direction_sign)) = replay_direction(&entries) else {
167 return replay_batches_by_priority(
168 entries
169 .iter()
170 .map(|entry| (entry.index, entry.priority))
171 .collect(),
172 );
173 };
174
175 let mut keyed_entries: Vec<_> = entries
176 .iter()
177 .map(|entry| {
178 (
179 entry.index,
180 entry.priority,
181 directional_lane_key(entry.current_bounds, axis, direction_sign),
182 )
183 })
184 .collect();
185 keyed_entries.sort_by_key(|(index, priority, lane)| (*priority, *lane, *index));
186
187 let mut batches = Vec::new();
188 let mut current_priority = keyed_entries[0].1;
189 let mut current_lane = keyed_entries[0].2;
190 let mut current_batch = Vec::new();
191 for (index, priority, lane) in keyed_entries {
192 if priority != current_priority || lane != current_lane {
193 batches.push(current_batch);
194 current_batch = Vec::new();
195 current_priority = priority;
196 current_lane = lane;
197 }
198 current_batch.push(index);
199 }
200 batches.push(current_batch);
201 batches
202}
203
204fn replay_direction(entries: &[ReplayPriorityEntry]) -> Option<(ReplayDirectionAxis, f64)> {
205 let (dx, dy) = entries.iter().fold((0.0, 0.0), |(dx, dy), entry| {
206 (
207 dx + rect_center_x(&entry.current_bounds) - rect_center_x(&entry.previous_bounds),
208 dy + rect_center_y(&entry.current_bounds) - rect_center_y(&entry.previous_bounds),
209 )
210 });
211
212 if dx.abs() < REPLAY_DIRECTION_EPSILON && dy.abs() < REPLAY_DIRECTION_EPSILON {
213 return None;
214 }
215
216 if dx.abs() >= dy.abs() {
217 Some((ReplayDirectionAxis::X, dx.signum()))
218 } else {
219 Some((ReplayDirectionAxis::Y, dy.signum()))
220 }
221}
222
223fn directional_lane_key(bounds: Rect, axis: ReplayDirectionAxis, direction_sign: f64) -> i64 {
224 let coord = match axis {
225 ReplayDirectionAxis::X => rect_center_x(&bounds),
226 ReplayDirectionAxis::Y => rect_center_y(&bounds),
227 }
228 .round() as i64;
229
230 if direction_sign.is_sign_positive() {
231 -coord
232 } else {
233 coord
234 }
235}
236
237fn rect_center_x(bounds: &Rect) -> f64 {
238 (bounds.x0 + bounds.x1) * 0.5
239}
240
241fn rect_center_y(bounds: &Rect) -> f64 {
242 (bounds.y0 + bounds.y1) * 0.5
243}
244
245fn union_bounds(bounds: impl IntoIterator<Item = Rect>) -> Option<Rect> {
246 bounds
247 .into_iter()
248 .filter(|bounds| rect_is_finite(bounds))
249 .fold(None, |acc, bounds| {
250 Some(match acc {
251 Some(acc) => acc.union(bounds),
252 None => bounds,
253 })
254 })
255}
256
257fn surface_bounds(surface: &LayerSurfaceEntry) -> Rect {
258 let x0 = surface.origin_x as f64;
259 let y0 = surface.origin_y as f64;
260 Rect::new(
261 x0,
262 y0,
263 x0 + surface.surface.logical_width as f64,
264 y0 + surface.surface.logical_height as f64,
265 )
266}
267
268fn rect_contains(outer: &Rect, inner: &Rect) -> bool {
269 const EPSILON: f64 = 0.5;
270 inner.x0 >= outer.x0 - EPSILON
271 && inner.y0 >= outer.y0 - EPSILON
272 && inner.x1 <= outer.x1 + EPSILON
273 && inner.y1 <= outer.y1 + EPSILON
274}
275
276fn rect_escape_gap(outer: &Rect, inner: &Rect) -> (f64, f64) {
277 let gap_x = (outer.x0 - inner.x0)
278 .max(0.0)
279 .max((inner.x1 - outer.x1).max(0.0));
280 let gap_y = (outer.y0 - inner.y0)
281 .max(0.0)
282 .max((inner.y1 - outer.y1).max(0.0));
283 (gap_x, gap_y)
284}
285
286fn rect_is_finite(bounds: &Rect) -> bool {
287 bounds.x0.is_finite() && bounds.y0.is_finite() && bounds.x1.is_finite() && bounds.y1.is_finite()
288}
289
290#[derive(Default)]
295pub struct SurfaceReplayCoordinator {
296 targeted_replay_queues: Vec<VecDeque<Vec<usize>>>,
297 targeted_replay_bounds: Vec<HashMap<usize, Vec<Rect>>>,
298 canvas_node_coverage: Vec<HashMap<u32, Rect>>,
299}
300
301impl SurfaceReplayCoordinator {
302 pub fn targeted_replay_scope(&self, layer: usize) -> Option<Vec<usize>> {
303 self.targeted_replay_queues
304 .get(layer)
305 .and_then(|queue| queue.front().cloned())
306 }
307
308 pub fn targeted_or_all_indices(&self, layer: usize, renderer_count: usize) -> Vec<usize> {
309 self.targeted_replay_scope(layer)
310 .unwrap_or_else(|| (0..renderer_count).collect())
311 }
312
313 pub fn clear_targeted_replay_scope(&mut self, layer: usize) {
314 if let Some(queue) = self.targeted_replay_queues.get_mut(layer) {
315 queue.clear();
316 }
317 if let Some(bounds_by_surface) = self.targeted_replay_bounds.get_mut(layer) {
318 bounds_by_surface.clear();
319 }
320 }
321
322 pub fn set_targeted_replay_batches(
323 &mut self,
324 layer: usize,
325 batches: Vec<Vec<usize>>,
326 bounds_by_surface: HashMap<usize, Vec<Rect>>,
327 ) {
328 let mut queue = VecDeque::new();
329 let mut scheduled_indices = HashSet::new();
330 for mut batch in batches {
331 batch.sort_unstable();
332 batch.dedup();
333 if !batch.is_empty() {
334 scheduled_indices.extend(batch.iter().copied());
335 queue.push_back(batch);
336 }
337 }
338 if queue.is_empty() {
339 return;
340 }
341
342 let mut preserved_indices = HashSet::new();
343 if let Some(existing_queue) = self.targeted_replay_queues.get(layer) {
344 for batch in existing_queue {
345 let mut preserved_batch = Vec::new();
346 for index in batch.iter().copied() {
347 if scheduled_indices.insert(index) {
348 preserved_indices.insert(index);
349 preserved_batch.push(index);
350 }
351 }
352 if !preserved_batch.is_empty() {
353 queue.push_back(preserved_batch);
354 }
355 }
356 }
357
358 let mut bounds_by_surface = bounds_by_surface;
359 if let Some(existing_bounds) = self.targeted_replay_bounds.get(layer) {
360 for index in preserved_indices {
361 if let Some(bounds) = existing_bounds.get(&index) {
362 bounds_by_surface
363 .entry(index)
364 .or_insert_with(|| bounds.clone());
365 }
366 }
367 }
368
369 if self.targeted_replay_queues.len() <= layer {
370 self.targeted_replay_queues
371 .resize_with(layer + 1, VecDeque::new);
372 }
373 self.targeted_replay_queues[layer] = queue;
374
375 if self.targeted_replay_bounds.len() <= layer {
376 self.targeted_replay_bounds
377 .resize_with(layer + 1, HashMap::new);
378 }
379 self.targeted_replay_bounds[layer] = bounds_by_surface;
380 }
381
382 pub fn advance_targeted_replay_queue(&mut self, layer: usize) -> bool {
383 let (popped_batch, has_more) = {
384 let Some(queue) = self.targeted_replay_queues.get_mut(layer) else {
385 return false;
386 };
387 let popped_batch = queue.pop_front();
388 (popped_batch, !queue.is_empty())
389 };
390
391 if let Some(popped_batch) = popped_batch {
392 if let Some(bounds_by_surface) = self.targeted_replay_bounds.get_mut(layer) {
393 for index in popped_batch {
394 bounds_by_surface.remove(&index);
395 }
396 if !has_more {
397 bounds_by_surface.clear();
398 }
399 }
400 }
401
402 has_more
403 }
404
405 pub fn remember_canvas_node_coverage(
406 &mut self,
407 layer: usize,
408 node_id: u32,
409 coverage_bounds: Rect,
410 ) {
411 if self.canvas_node_coverage.len() <= layer {
412 self.canvas_node_coverage
413 .resize_with(layer + 1, HashMap::new);
414 }
415 self.canvas_node_coverage[layer].insert(node_id, coverage_bounds);
416 }
417
418 pub fn forget_canvas_node_coverage(&mut self, layer: usize, node_id: u32) {
419 if let Some(coverage) = self.canvas_node_coverage.get_mut(layer) {
420 coverage.remove(&node_id);
421 }
422 }
423
424 pub fn truncate_layers(&mut self, layer_count: usize) {
425 self.targeted_replay_queues.truncate(layer_count);
426 self.targeted_replay_bounds.truncate(layer_count);
427 self.canvas_node_coverage.truncate(layer_count);
428 }
429
430 pub fn targeted_replay_surface_bounds(
431 &self,
432 layer: usize,
433 mut fallback_bounds: impl FnMut(usize) -> Option<Rect>,
434 ) -> Option<Vec<Rect>> {
435 let indices = self.targeted_replay_scope(layer)?;
436 let mut bounds = Vec::with_capacity(indices.len());
437 let layer_replay_bounds = self.targeted_replay_bounds.get(layer);
438 for index in indices {
439 if let Some(surface_bounds) =
440 layer_replay_bounds.and_then(|bounds_by_surface| bounds_by_surface.get(&index))
441 {
442 bounds.extend(surface_bounds.iter().copied());
443 continue;
444 }
445 if let Some(surface_bounds) = fallback_bounds(index) {
446 bounds.push(surface_bounds);
447 }
448 }
449 (!bounds.is_empty()).then_some(bounds)
450 }
451
452 pub fn spatial_replay_node_ids(
453 &self,
454 layer: usize,
455 fallback_bounds: impl FnMut(usize) -> Option<Rect>,
456 ) -> Option<Vec<u32>> {
457 let surface_bounds = self.targeted_replay_surface_bounds(layer, fallback_bounds)?;
458 self.spatial_replay_node_ids_for_surface_bounds(layer, &surface_bounds)
459 }
460
461 pub fn spatial_replay_node_ids_for_surface_bounds(
462 &self,
463 layer: usize,
464 surface_bounds: &[Rect],
465 ) -> Option<Vec<u32>> {
466 let layer_coverage = self.canvas_node_coverage.get(layer)?;
467 if layer_coverage.is_empty() {
468 return None;
469 }
470
471 let mut node_ids: Vec<_> = layer_coverage
472 .iter()
473 .filter_map(|(node_id, bounds)| {
474 surface_bounds
475 .iter()
476 .any(|surface| {
477 surface_intersects_coverage_bounds(
478 bounds,
479 surface.x0,
480 surface.y0,
481 surface.width(),
482 surface.height(),
483 )
484 })
485 .then_some(*node_id)
486 })
487 .collect();
488 node_ids.sort_unstable();
489 if node_ids.is_empty() {
490 return None;
491 }
492 Some(node_ids)
493 }
494}
495
496#[cfg(test)]
497mod replay_priority_tests {
498 use super::{
499 replay_batches_by_directional_priority, replay_batches_by_priority, visible_surface_escape,
500 LayerSurfaceEntry, LayerSurfaceSize, ReplayPriorityEntry, SurfaceReplayCoordinator,
501 };
502 use kurbo::Rect;
503 use std::collections::HashMap;
504
505 #[test]
506 fn visible_batch_excludes_warm_rings() {
507 let batches = replay_batches_by_priority(vec![(3, 4), (0, 0), (2, 2), (1, 0)]);
508
509 assert_eq!(batches[0], vec![0, 1]);
510 assert_eq!(batches[1], vec![2]);
511 assert_eq!(batches[2], vec![3]);
512 }
513
514 #[test]
515 fn starts_with_nearest_warm_batch_when_no_visible_tiles() {
516 let batches = replay_batches_by_priority(vec![(4, 5), (2, 3), (3, 3)]);
517
518 assert_eq!(batches[0], vec![2, 3]);
519 assert_eq!(batches[1], vec![4]);
520 }
521
522 #[test]
523 fn visible_escape_detects_visible_tiles_outside_previous_warm_bounds() {
524 let previous = vec![Rect::new(0.0, 0.0, 100.0, 300.0)];
525 let current = vec![
526 test_surface(0.0, 100.0, 100.0, 100.0, 0),
527 test_surface(0.0, 300.0, 100.0, 100.0, 0),
528 test_surface(0.0, 400.0, 100.0, 100.0, 1),
529 ];
530
531 let escape = visible_surface_escape(&previous, ¤t).expect("one visible tile escaped");
532
533 assert_eq!(escape.visible_surfaces, 2);
534 assert_eq!(escape.escaped_visible_surfaces, 1);
535 assert_eq!(escape.max_gap_y, 100.0);
536 assert_eq!(escape.max_gap_x, 0.0);
537 assert_eq!(escape.visible_bounds, Rect::new(0.0, 100.0, 100.0, 400.0));
538 }
539
540 #[test]
541 fn visible_escape_ignores_covered_visible_tiles() {
542 let previous = vec![Rect::new(0.0, 0.0, 100.0, 300.0)];
543 let current = vec![
544 test_surface(0.0, 0.0, 100.0, 100.0, 0),
545 test_surface(0.0, 100.0, 100.0, 100.0, 0),
546 ];
547
548 assert!(visible_surface_escape(&previous, ¤t).is_none());
549 }
550
551 #[test]
552 fn directional_batches_start_with_leading_vertical_row() {
553 let batches = replay_batches_by_directional_priority(&[
554 ReplayPriorityEntry::new(
555 0,
556 0,
557 Rect::new(0.0, 0.0, 100.0, 100.0),
558 Rect::new(0.0, 100.0, 100.0, 200.0),
559 ),
560 ReplayPriorityEntry::new(
561 1,
562 0,
563 Rect::new(100.0, 100.0, 200.0, 200.0),
564 Rect::new(100.0, 200.0, 200.0, 300.0),
565 ),
566 ReplayPriorityEntry::new(
567 2,
568 0,
569 Rect::new(200.0, -100.0, 300.0, 0.0),
570 Rect::new(200.0, 0.0, 300.0, 100.0),
571 ),
572 ]);
573
574 assert_eq!(batches, vec![vec![1], vec![0], vec![2]]);
575 }
576
577 #[test]
578 fn directional_batches_keep_same_leading_row_together() {
579 let batches = replay_batches_by_directional_priority(&[
580 ReplayPriorityEntry::new(
581 2,
582 0,
583 Rect::new(200.0, 100.0, 300.0, 200.0),
584 Rect::new(200.0, 200.0, 300.0, 300.0),
585 ),
586 ReplayPriorityEntry::new(
587 0,
588 0,
589 Rect::new(0.0, 100.0, 100.0, 200.0),
590 Rect::new(0.0, 200.0, 100.0, 300.0),
591 ),
592 ReplayPriorityEntry::new(
593 1,
594 0,
595 Rect::new(100.0, 0.0, 200.0, 100.0),
596 Rect::new(100.0, 100.0, 200.0, 200.0),
597 ),
598 ]);
599
600 assert_eq!(batches, vec![vec![0, 2], vec![1]]);
601 }
602
603 #[test]
604 fn coordinator_advances_targeted_replay_batches() {
605 let mut coordinator = SurfaceReplayCoordinator::default();
606 coordinator.set_targeted_replay_batches(
607 0,
608 vec![vec![3, 1, 1], vec![2]],
609 HashMap::from([
610 (1, vec![Rect::new(0.0, 0.0, 10.0, 10.0)]),
611 (2, vec![Rect::new(10.0, 0.0, 20.0, 10.0)]),
612 (3, vec![Rect::new(20.0, 0.0, 30.0, 10.0)]),
613 ]),
614 );
615
616 assert_eq!(coordinator.targeted_replay_scope(0), Some(vec![1, 3]));
617 assert!(coordinator.advance_targeted_replay_queue(0));
618 assert_eq!(coordinator.targeted_replay_scope(0), Some(vec![2]));
619 let bounds = coordinator
620 .targeted_replay_surface_bounds(0, |_| None)
621 .expect("second replay bounds remain");
622 assert_eq!(bounds, vec![Rect::new(10.0, 0.0, 20.0, 10.0)]);
623 assert!(!coordinator.advance_targeted_replay_queue(0));
624 assert_eq!(coordinator.targeted_replay_scope(0), None);
625 }
626
627 #[test]
628 fn coordinator_preserves_unflushed_replay_batches_when_rescheduled() {
629 let mut coordinator = SurfaceReplayCoordinator::default();
630 coordinator.set_targeted_replay_batches(
631 0,
632 vec![vec![0], vec![1]],
633 HashMap::from([
634 (0, vec![Rect::new(0.0, 0.0, 10.0, 10.0)]),
635 (1, vec![Rect::new(10.0, 0.0, 20.0, 10.0)]),
636 ]),
637 );
638 coordinator.set_targeted_replay_batches(
639 0,
640 vec![vec![2]],
641 HashMap::from([(2, vec![Rect::new(20.0, 0.0, 30.0, 10.0)])]),
642 );
643
644 assert_eq!(coordinator.targeted_replay_scope(0), Some(vec![2]));
645 assert!(coordinator.advance_targeted_replay_queue(0));
646 assert_eq!(coordinator.targeted_replay_scope(0), Some(vec![0]));
647 let bounds = coordinator
648 .targeted_replay_surface_bounds(0, |_| None)
649 .expect("preserved replay bounds remain");
650 assert_eq!(bounds, vec![Rect::new(0.0, 0.0, 10.0, 10.0)]);
651 assert!(coordinator.advance_targeted_replay_queue(0));
652 assert_eq!(coordinator.targeted_replay_scope(0), Some(vec![1]));
653 }
654
655 #[test]
656 fn coordinator_selects_spatial_replay_nodes_by_surface_bounds() {
657 let mut coordinator = SurfaceReplayCoordinator::default();
658 coordinator.remember_canvas_node_coverage(0, 10, Rect::new(0.0, 0.0, 5.0, 5.0));
659 coordinator.remember_canvas_node_coverage(0, 20, Rect::new(50.0, 50.0, 60.0, 60.0));
660 coordinator.set_targeted_replay_batches(0, vec![vec![0]], HashMap::new());
661
662 let node_ids = coordinator
663 .spatial_replay_node_ids(0, |_| Some(Rect::new(0.0, 0.0, 10.0, 10.0)))
664 .expect("spatial replay nodes");
665
666 assert_eq!(node_ids, vec![10]);
667 }
668
669 #[test]
670 fn coordinator_falls_back_when_targeted_replay_has_no_cached_coverage() {
671 let mut coordinator = SurfaceReplayCoordinator::default();
672 coordinator.remember_canvas_node_coverage(0, 10, Rect::new(0.0, 0.0, 5.0, 5.0));
673 coordinator.set_targeted_replay_batches(0, vec![vec![0]], HashMap::new());
674
675 let node_ids =
676 coordinator.spatial_replay_node_ids(0, |_| Some(Rect::new(50.0, 50.0, 60.0, 60.0)));
677
678 assert!(node_ids.is_none());
679 }
680
681 fn test_surface(
682 origin_x: f32,
683 origin_y: f32,
684 logical_width: f32,
685 logical_height: f32,
686 replay_priority: i32,
687 ) -> LayerSurfaceEntry {
688 LayerSurfaceEntry {
689 key: String::new(),
690 host_signature: String::new(),
691 origin_x,
692 origin_y,
693 replay_priority,
694 surface: LayerSurfaceSize {
695 logical_width,
696 logical_height,
697 surface_width: logical_width as u32,
698 surface_height: logical_height as u32,
699 dpr: [1.0, 1.0],
700 },
701 }
702 }
703}