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