1use crate::filter::{FilterData, FilterLayerPlacement};
36use crate::geometry::{RectU16, SizeU16};
37use crate::mask::Mask;
38use crate::peniko::BlendMode;
39use crate::strip::Strip;
40use crate::util::RectExt;
41use alloc::vec::Vec;
42use core::ops::Range;
43use smallvec::SmallVec;
44
45pub trait Drawable {
47 fn bbox(&self, strips: &[Strip]) -> Option<RectU16>;
50}
51
52#[derive(Debug)]
54pub struct Node {
55 pub draws: Range<u32>,
57 pub layer: Option<u32>,
59}
60
61impl Node {
62 pub fn draws_in<'a, D>(&self, draws: &'a [D]) -> &'a [D] {
64 &draws[self.draws.start as usize..self.draws.end as usize]
65 }
66}
67
68#[derive(Debug)]
70pub struct RecordedLayer {
71 pub props: LayerProps,
73 pub nodes: SmallVec<[Node; 2]>,
75 pub kind: RecordedLayerKind,
77 pub depth: usize,
79 pub bbox: RectU16,
97}
98
99#[derive(Debug)]
101pub struct LayerProps {
102 pub blend_mode: BlendMode,
104 pub opacity: f32,
106 pub mask: Option<Mask>,
108 pub clip_path: Option<LayerClip>,
110}
111
112#[derive(Debug, Clone)]
114pub struct LayerClip {
115 pub strip_range: Range<usize>,
117 pub thread_idx: u8,
119 pub bbox: RectU16,
121}
122
123#[derive(Debug)]
125pub enum RecordedLayerKind {
126 Regular,
128 Filter {
130 filter_data: FilterData,
132 placement: FilterLayerPlacement,
135 },
136}
137
138impl RecordedLayer {
139 fn regular(props: LayerProps, depth: usize) -> Self {
140 Self {
141 props,
142 nodes: SmallVec::new(),
143 kind: RecordedLayerKind::Regular,
144 depth,
145 bbox: RectU16::ZERO,
147 }
148 }
149
150 fn filter(props: LayerProps, filter_plan: FilterData, depth: usize) -> Self {
151 Self {
152 props,
153 nodes: SmallVec::new(),
154 kind: RecordedLayerKind::Filter {
155 filter_data: filter_plan,
156 placement: FilterLayerPlacement::EMPTY,
158 },
159 depth,
160 bbox: RectU16::ZERO,
162 }
163 }
164}
165
166#[derive(Debug)]
169pub struct CommandRecorder<D> {
170 pub scene_size: SizeU16,
172 pub nodes: Vec<Node>,
174 pub draws: Vec<D>,
176 pub layers: Vec<RecordedLayer>,
178 pub filter_layers: Vec<u32>,
180 pub root_is_blend_target: bool,
182 pub max_layer_depth: usize,
184 pub largest_layer_size: Option<SizeU16>,
186 pub largest_filter_layer_size: Option<SizeU16>,
188 pub has_non_default_blend: bool,
190 active_layer: Option<u32>,
194 layer_stack: Vec<OpenLayer>,
196}
197
198impl<D> Default for CommandRecorder<D> {
199 fn default() -> Self {
200 Self {
201 scene_size: SizeU16::ZERO,
202 nodes: Vec::new(),
203 draws: Vec::new(),
204 layers: Vec::new(),
205 filter_layers: Vec::new(),
206 root_is_blend_target: false,
207 max_layer_depth: 0,
208 largest_layer_size: None,
209 largest_filter_layer_size: None,
210 has_non_default_blend: false,
211 active_layer: None,
212 layer_stack: Vec::new(),
213 }
214 }
215}
216
217#[derive(Debug)]
218struct OpenLayer {
219 id: u32,
220 bbox: RectU16,
222 parent_layer: Option<u32>,
223}
224
225impl<D> CommandRecorder<D> {
226 pub fn new(width: u16, height: u16) -> Self {
228 Self {
229 scene_size: snapped_scene_size(width, height),
230 ..Self::default()
231 }
232 }
233
234 pub fn has_layers(&self) -> bool {
236 !self.layer_stack.is_empty()
237 }
238
239 #[inline]
241 pub fn reset(&mut self, width: u16, height: u16) {
242 self.scene_size = snapped_scene_size(width, height);
243 self.nodes.clear();
244 self.draws.clear();
245
246 self.layers.clear();
247 self.filter_layers.clear();
248 self.root_is_blend_target = false;
249 self.max_layer_depth = 0;
250 self.largest_layer_size = None;
251 self.largest_filter_layer_size = None;
252 self.has_non_default_blend = false;
253 self.active_layer = None;
254 self.layer_stack.clear();
255 }
256
257 #[inline]
259 pub fn push_layer(&mut self, props: LayerProps, filter_plan: Option<FilterData>) {
260 if let Some(filter_plan) = filter_plan {
261 self.push_filter_layer(props, filter_plan);
262 return;
263 }
264
265 self.push_regular_layer(props);
266 }
267
268 fn push_regular_layer(&mut self, props: LayerProps) {
269 let depth = self.layer_stack.len() + 1;
270 self.push_recorded_layer(RecordedLayer::regular(props, depth));
271 }
272
273 fn push_filter_layer(&mut self, props: LayerProps, filter_plan: FilterData) {
274 let depth = self.layer_stack.len() + 1;
275 let id = self.push_recorded_layer(RecordedLayer::filter(props, filter_plan, depth));
276
277 self.filter_layers.push(id);
278 }
279
280 fn push_recorded_layer(&mut self, layer: RecordedLayer) -> u32 {
281 let parent_layer = self.active_layer;
282 self.max_layer_depth = self.max_layer_depth.max(layer.depth);
283
284 if layer.props.blend_mode != BlendMode::default() {
285 self.has_non_default_blend = true;
286
287 if parent_layer.is_none() {
288 self.root_is_blend_target = true;
289 }
290 }
291
292 let id = self.push_layer_metadata(layer);
293 self.push_layer_node(id);
294 self.active_layer = Some(id);
295 self.layer_stack.push(OpenLayer {
296 id,
297 bbox: RectU16::INVERTED,
299 parent_layer,
300 });
301
302 id
303 }
304
305 pub fn pop_layer(&mut self) -> PoppedLayer {
307 let layer = self.layer_stack.pop().unwrap();
308 let id = layer.id;
309
310 let (popped_layer, bbox_in_parent) = {
311 let recorded_layer = &mut self.layers[id as usize];
312 match &mut recorded_layer.kind {
313 RecordedLayerKind::Regular => {
314 let mut bbox = layer.bbox;
315
316 if bbox.is_empty() {
318 bbox = RectU16::ZERO;
319 }
320
321 if let Some(clip_path) = &recorded_layer.props.clip_path {
322 bbox = bbox.intersect(clip_path.bbox);
323 }
324
325 recorded_layer.bbox = bbox;
326
327 let layer_size = bbox.into();
328 self.largest_layer_size = Some(
329 self.largest_layer_size
330 .map_or(layer_size, |current| current.max(layer_size)),
331 );
332
333 (PoppedLayer::Regular, bbox)
334 }
335 RecordedLayerKind::Filter {
336 filter_data: filter_plan,
337 placement,
338 } => {
339 *placement = FilterLayerPlacement::new(layer.bbox, filter_plan);
340 recorded_layer.bbox = placement.pixmap_bbox;
341
342 let filter_size = placement.pixmap_bbox.into();
343 self.largest_layer_size = Some(
344 self.largest_layer_size
345 .map_or(filter_size, |current| current.max(filter_size)),
346 );
347 self.largest_filter_layer_size = Some(
348 self.largest_filter_layer_size
349 .map_or(filter_size, |current| current.max(filter_size)),
350 );
351
352 (PoppedLayer::Filter, placement.dest_bbox)
353 }
354 }
355 };
356
357 self.active_layer = layer.parent_layer;
359 self.record_bbox(|| Some(bbox_in_parent));
360
361 popped_layer
362 }
363
364 #[inline]
365 fn active_node_mut(&mut self) -> Option<&mut Node> {
366 if let Some(id) = self.active_layer {
367 self.layers[id as usize].nodes.last_mut()
368 } else {
369 self.nodes.last_mut()
370 }
371 }
372
373 fn push_node(&mut self, node: Node) {
374 if let Some(id) = self.active_layer {
375 self.layers[id as usize].nodes.push(node);
376 } else {
377 self.nodes.push(node);
378 }
379 }
380
381 fn push_layer_node(&mut self, layer_id: u32) {
382 let draw_idx = self.draws.len() as u32;
383
384 match self.active_node_mut() {
385 Some(node) if node.layer.is_none() => {
386 node.layer = Some(layer_id);
387 }
388 _ => self.push_node(Node {
389 draws: draw_idx..draw_idx,
390 layer: Some(layer_id),
391 }),
392 }
393 }
394
395 fn push_layer_metadata(&mut self, layer: RecordedLayer) -> u32 {
396 let id = self.layers.len() as u32;
397 self.layers.push(layer);
398 id
399 }
400
401 fn record_bbox(&mut self, bbox: impl FnOnce() -> Option<RectU16>) {
402 let Some(layer) = self.layer_stack.last_mut() else {
403 return;
404 };
405
406 let Some(bbox) = bbox().and_then(|b| if b.is_empty() { None } else { Some(b) }) else {
407 return;
408 };
409
410 layer.bbox.union(bbox);
411 }
412}
413
414fn snapped_scene_size(width: u16, height: u16) -> SizeU16 {
415 RectU16::new(0, 0, width, height)
416 .snap_to_tile_coordinates()
417 .into()
418}
419
420impl<D: Drawable> CommandRecorder<D> {
421 #[inline]
423 pub fn push_draw(&mut self, draw: D, strips: &[Strip]) {
424 self.record_bbox(|| draw.bbox(strips));
425 let draw_idx = self.draws.len() as u32;
426 self.draws.push(draw);
427
428 match self.active_node_mut() {
429 Some(node) if node.layer.is_none() && node.draws.end == draw_idx => {
430 node.draws.end += 1;
431 }
432 _ => {
433 self.push_node(Node {
434 draws: draw_idx..draw_idx + 1,
435 layer: None,
436 });
437 }
438 };
439 }
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum PoppedLayer {
445 Regular,
447 Filter,
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use crate::filter_effects::{Filter, FilterPrimitive};
455 use crate::geometry::PaddingU16;
456 use crate::kurbo::Affine;
457 use crate::peniko::Mix;
458 use crate::tile::Tile;
459
460 const DEFAULT_SIZE: u16 = 10;
461
462 #[derive(Debug)]
463 struct TestDraw;
464
465 impl Drawable for TestDraw {
466 fn bbox(&self, _strips: &[Strip]) -> Option<RectU16> {
467 Some(RectU16::new(0, 0, 64, 4))
468 }
469 }
470
471 #[derive(Debug)]
472 struct EmptyDraw;
473
474 impl Drawable for EmptyDraw {
475 fn bbox(&self, _strips: &[Strip]) -> Option<RectU16> {
476 None
477 }
478 }
479
480 fn layer_props() -> LayerProps {
481 LayerProps {
482 blend_mode: BlendMode::default(),
483 opacity: 1.0,
484 mask: None,
485 clip_path: None,
486 }
487 }
488
489 fn blended_layer_props() -> LayerProps {
490 LayerProps {
491 blend_mode: Mix::Multiply.into(),
492 ..layer_props()
493 }
494 }
495
496 fn filter_data(filter_padding: PaddingU16, source_padding: PaddingU16) -> FilterData {
497 FilterData {
498 filter: Filter::from_primitive(FilterPrimitive::Offset { dx: 0.0, dy: 0.0 }),
499 transform: Affine::IDENTITY,
500 filter_padding,
501 source_padding,
502 }
503 }
504
505 fn assert_cmds(cmds: &[Node], expected: &[(Range<u32>, Option<u32>)]) {
506 assert_eq!(cmds.len(), expected.len());
507
508 for (cmd, (draws, layer)) in cmds.iter().zip(expected) {
509 assert_eq!(&cmd.draws, draws);
510 assert_eq!(cmd.layer, *layer);
511 }
512 }
513
514 fn layer_cmds(recorder: &CommandRecorder<TestDraw>, id: usize) -> &[Node] {
515 &recorder.layers[id].nodes
516 }
517
518 #[test]
519 fn scene_size_is_tile_aligned() {
520 let mut recorder = CommandRecorder::<TestDraw>::new(10, 10);
521 assert_eq!(recorder.scene_size, SizeU16::new(12));
522
523 recorder.reset(13, 7);
524 assert_eq!(recorder.scene_size, SizeU16::from_wh(16, 8));
525
526 recorder.reset(Tile::WIDTH * 5, Tile::HEIGHT * 3);
527 assert_eq!(
528 recorder.scene_size,
529 SizeU16::from_wh(Tile::WIDTH * 5, Tile::HEIGHT * 3)
530 );
531 }
532
533 #[test]
534 fn filter_placement_padding_expands_bbox() {
535 let placement = FilterLayerPlacement::new(
536 RectU16::new(8, 8, 16, 20),
537 &filter_data(PaddingU16::new(2, 4, 6, 8), PaddingU16::ZERO),
538 );
539
540 assert_eq!(placement.pixmap_bbox, RectU16::new(4, 4, 24, 28));
542 assert_eq!(placement.dest_bbox, RectU16::new(4, 4, 24, 28));
543 assert_eq!(placement.src_origin(), (0, 0));
544 }
545
546 #[test]
547 fn filter_placement_with_source_shift() {
548 let placement = FilterLayerPlacement::new(
549 RectU16::new(8, 12, 20, 24),
550 &filter_data(PaddingU16::new(6, 2, 4, 6), PaddingU16::new(10, 16, 0, 0)),
551 );
552
553 assert_eq!(placement.pixmap_bbox, RectU16::new(0, 8, 24, 32));
556 assert_eq!(placement.dest_bbox, RectU16::new(0, 0, 14, 16));
559 assert_eq!(placement.src_origin(), (10, 8));
561 }
562
563 #[test]
564 fn layer_behavior() {
565 let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
566
567 recorder.push_layer(
568 layer_props(),
569 Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
570 );
571 recorder.push_layer(
572 layer_props(),
573 Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
574 );
575 recorder.push_layer(layer_props(), None);
576
577 recorder.push_draw(TestDraw, &[]);
578
579 assert_eq!(recorder.pop_layer(), PoppedLayer::Regular);
580 assert_eq!(recorder.pop_layer(), PoppedLayer::Filter);
581
582 recorder.push_layer(layer_props(), None);
583 recorder.push_draw(TestDraw, &[]);
584 assert_eq!(recorder.pop_layer(), PoppedLayer::Regular);
585 assert_eq!(recorder.pop_layer(), PoppedLayer::Filter);
586
587 assert_cmds(&recorder.nodes, &[(0..0, Some(0))]);
588 assert_cmds(
589 layer_cmds(&recorder, 0),
590 &[(0..0, Some(1)), (1..1, Some(3))],
591 );
592 assert_cmds(layer_cmds(&recorder, 1), &[(0..0, Some(2))]);
593 assert_cmds(layer_cmds(&recorder, 2), &[(0..1, None)]);
594 assert_cmds(layer_cmds(&recorder, 3), &[(1..2, None)]);
595 assert_eq!(recorder.draws.len(), 2);
596 assert_eq!(recorder.filter_layers, [0, 1]);
597 assert_eq!(
598 recorder
599 .layers
600 .iter()
601 .map(|layer| layer.depth)
602 .collect::<Vec<_>>(),
603 [1, 2, 3, 2]
604 );
605 assert_eq!(recorder.max_layer_depth, 3);
606 assert!(!recorder.has_non_default_blend);
607 assert_eq!(recorder.largest_layer_size, Some(SizeU16::from_wh(64, 4)));
608 assert_eq!(
609 recorder.largest_filter_layer_size,
610 Some(SizeU16::from_wh(64, 4))
611 );
612 }
613
614 #[test]
615 fn draw_batches_are_split_by_layers() {
616 let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
617
618 recorder.push_draw(TestDraw, &[]);
619 recorder.push_draw(TestDraw, &[]);
620 recorder.push_layer(layer_props(), None);
621 recorder.push_draw(TestDraw, &[]);
622 recorder.pop_layer();
623 recorder.push_draw(TestDraw, &[]);
624
625 assert_cmds(&recorder.nodes, &[(0..2, Some(0)), (3..4, None)]);
626 assert_cmds(layer_cmds(&recorder, 0), &[(2..3, None)]);
627 }
628
629 #[test]
630 fn node_resolves_draw_range() {
631 let draws = [0, 1, 2, 3];
632 let node = Node {
633 draws: 1..3,
634 layer: None,
635 };
636
637 assert_eq!(node.draws_in(&draws), &[1, 2]);
638 }
639
640 #[test]
641 fn empty_draws_do_not_affect_layer_bounds() {
642 let mut recorder = CommandRecorder::<EmptyDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
643 recorder.push_layer(layer_props(), None);
644 recorder.push_draw(EmptyDraw, &[]);
645 recorder.pop_layer();
646
647 assert!(recorder.layers[0].bbox.is_empty());
648 }
649
650 #[test]
651 fn disjoint_layer_bounds_are_empty_but_not_inverted() {
652 let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
653 let mut props = layer_props();
654 props.clip_path = Some(LayerClip {
655 strip_range: 0..0,
656 thread_idx: 0,
657 bbox: RectU16::new(8, 8, 12, 12),
658 });
659
660 recorder.push_layer(props, None);
661 recorder.push_draw(TestDraw, &[]);
662 recorder.pop_layer();
663
664 assert_eq!(recorder.layers[0].bbox, RectU16::new(8, 8, 12, 8));
665 }
666
667 #[test]
668 fn blend_metadata_distinguishes_root_and_nested_targets() {
669 let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
670
671 recorder.push_layer(layer_props(), None);
672 recorder.push_layer(blended_layer_props(), None);
673
674 assert!(!recorder.root_is_blend_target);
675 assert!(recorder.has_non_default_blend);
676 assert_eq!(recorder.max_layer_depth, 2);
677
678 recorder.pop_layer();
679 recorder.pop_layer();
680 recorder.push_layer(blended_layer_props(), None);
681
682 assert!(recorder.root_is_blend_target);
683 }
684
685 #[test]
686 fn reset_clears_all_metadata() {
687 let mut recorder = CommandRecorder::<TestDraw>::new(DEFAULT_SIZE, DEFAULT_SIZE);
688
689 recorder.push_layer(blended_layer_props(), None);
690 recorder.push_layer(
691 layer_props(),
692 Some(filter_data(PaddingU16::ZERO, PaddingU16::ZERO)),
693 );
694 recorder.push_draw(TestDraw, &[]);
695 recorder.pop_layer();
696 recorder.pop_layer();
697
698 assert!(recorder.root_is_blend_target);
699 assert!(recorder.has_non_default_blend);
700 assert_eq!(recorder.max_layer_depth, 2);
701 assert!(recorder.largest_layer_size.is_some());
702 assert!(recorder.largest_filter_layer_size.is_some());
703
704 recorder.reset(13, 7);
705
706 assert_eq!(recorder.scene_size, SizeU16::from_wh(16, 8));
707 assert!(!recorder.root_is_blend_target);
708 assert!(!recorder.has_non_default_blend);
709 assert_eq!(recorder.max_layer_depth, 0);
710 assert!(recorder.largest_layer_size.is_none());
711 assert!(recorder.largest_filter_layer_size.is_none());
712 assert!(recorder.filter_layers.is_empty());
713 }
714}