1use super::cmd::{DepthFill, LayerFill, LayerFillAttrs, PaintFill, PaintFillAttrs, RenderCmd};
5use super::depth::{DepthSegment, DepthState};
6use crate::coarse::depth;
7use crate::filter::context::FilterContext;
8use crate::kurbo::{Affine, Vec2};
9use crate::peniko::{BlendMode, Extend, ImageQuality, ImageSampler};
10use crate::record::RecordedFill;
11use crate::util::Span;
12use alloc::sync::Arc;
13use alloc::vec;
14use alloc::vec::Vec;
15use vello_common::encode::{EncodedImage, EncodedPaint};
16use vello_common::filter::FilterLayerPlacement;
17use vello_common::geometry::RectU16;
18use vello_common::mask::Mask;
19use vello_common::paint::{ImageSource, IndexedPaint, Paint};
20use vello_common::pixmap::Pixmap;
21use vello_common::record::{LayerClip, LayerProps, Node, RecordedLayer, RecordedLayerKind};
22use vello_common::strip::{Strip, visit_strip_fill_segments};
23use vello_common::tile::Tile;
24use vello_common::util::{Clear, RectExt, RetainVec, VecPool};
25
26#[derive(Debug, Default)]
28pub(crate) struct RowState {
29 pub(crate) render_cmds: Vec<RenderCmd>,
31 pub(crate) depth_cmds: Vec<DepthFill>,
33 depth: DepthState,
35 pub(super) layer_depth: usize,
37 layer_stack: Vec<RowLayerState>,
38}
39
40#[derive(Debug)]
52struct RowLayerState {
53 push_cmd_idx: usize,
56 span: Option<Span>,
58}
59
60impl RowState {
61 pub(super) fn new() -> Self {
62 Self::default()
63 }
64
65 fn clear(&mut self) {
66 self.render_cmds.clear();
67 self.depth_cmds.clear();
68 self.depth.reset();
69 self.layer_depth = 0;
70 self.layer_stack.clear();
71 }
72
73 #[inline]
74 pub(super) fn push_cmd(&mut self, cmd: RenderCmd) {
75 match cmd {
76 RenderCmd::PaintFill(cmd) => self.include_current_span(cmd.span),
77 RenderCmd::LayerFill(cmd) => self.include_current_span(cmd.span),
78 RenderCmd::PushBuf(_) | RenderCmd::PopBuf => {}
79 }
80
81 self.render_cmds.push(cmd);
82 }
83
84 #[inline]
85 pub(super) fn push_buf(&mut self) {
86 let push_cmd_idx = self.render_cmds.len();
87 self.render_cmds.push(RenderCmd::PushBuf(None));
88 self.layer_stack.push(RowLayerState {
89 push_cmd_idx,
90 span: None,
91 });
92 self.layer_depth += 1;
93 }
94
95 #[inline]
96 pub(super) fn pop_buf(&mut self) {
97 let layer = self.layer_stack.pop().unwrap();
98 match &mut self.render_cmds[layer.push_cmd_idx] {
99 RenderCmd::PushBuf(push_span) => *push_span = layer.span,
100 _ => unreachable!("layer stack must point to a PushBuf command"),
101 }
102
103 self.render_cmds.push(RenderCmd::PopBuf);
104
105 self.layer_depth -= 1;
106 }
107
108 #[inline]
109 pub(super) fn push_depth_fill(&mut self, cmd: DepthFill, draw_id: u32) {
110 let span = cmd.span();
111 self.depth.include_span(span, draw_id);
112 self.depth_cmds.push(cmd);
113 }
114
115 #[inline]
116 pub(crate) fn can_skip_depth(&self, span: Span, draw_id: u32) -> bool {
117 self.depth.can_skip(span, draw_id)
118 }
119
120 #[inline]
121 fn include_current_span(&mut self, span: Span) {
122 if let Some(layer) = self.layer_stack.last_mut() {
123 match &mut layer.span {
124 Some(layer_span) => layer_span.extend(span),
125 None => layer.span = Some(span),
126 }
127 }
128 }
129}
130
131impl Clear for RowState {
132 fn clear(&mut self) {
133 Self::clear(self);
134 }
135}
136
137fn debug_assert_tile_aligned(point: (u16, u16), description: &str) {
138 debug_assert_eq!(
139 point.0 % Tile::WIDTH,
140 0,
141 "{description} must be tile-width aligned",
142 );
143 debug_assert_eq!(
144 point.1 % Tile::HEIGHT,
145 0,
146 "{description} must be tile-height aligned",
147 );
148}
149
150#[derive(Debug)]
152pub(crate) struct CommandBucketer {
153 viewport: RectU16,
155 pub(super) clip_bboxes: Vec<RectU16>,
168 pub(super) rows: RetainVec<RowState>,
173 pub(crate) paint_fill_attrs: Vec<PaintFillAttrs>,
174 pub(crate) layer_fill_attrs: Vec<LayerFillAttrs>,
175 pub(crate) filter_paints: Vec<EncodedPaint>,
176 pub(super) active_layers: Vec<ActiveLayer>,
178 occupied_rows_pool: VecPool<usize>,
180 occupied_rows_bool_scratch: Vec<bool>,
182 pub(super) next_draw_id: u32,
184}
185
186impl CommandBucketer {
187 pub(crate) fn from_wh(width: u16, height: u16) -> Self {
188 Self::new(RectU16::new(0, 0, width, height))
189 }
190
191 pub(crate) fn new(mut viewport: RectU16) -> Self {
192 viewport = viewport.snap_to_tile_coordinates();
196 let clip_bbox = RectU16::new(0, 0, viewport.width(), viewport.height());
197 let num_rows = usize::from(clip_bbox.height() / Tile::HEIGHT);
200
201 Self {
202 viewport,
203 clip_bboxes: vec![clip_bbox],
204 rows: RetainVec::with_len(num_rows, RowState::new),
205 paint_fill_attrs: Vec::new(),
206 layer_fill_attrs: Vec::new(),
207 filter_paints: Vec::new(),
208 active_layers: Vec::new(),
209 occupied_rows_pool: VecPool::default(),
210 occupied_rows_bool_scratch: vec![false; num_rows],
211 next_draw_id: 1,
213 }
214 }
215
216 fn bbox_span(bbox: RectU16) -> Span {
217 if bbox.is_empty() {
221 Span::new(bbox.x0, 0)
222 } else {
223 Span::new(bbox.x0, bbox.x1 - bbox.x0)
224 }
225 }
226
227 pub(crate) fn rows(&self) -> &[RowState] {
228 self.rows.as_slice()
229 }
230
231 pub(crate) fn width(&self) -> u16 {
232 self.clip_bboxes[0].width()
233 }
234
235 fn viewport_origin(&self) -> (u16, u16) {
236 (self.viewport.x0, self.viewport.y0)
237 }
238
239 pub(crate) fn reset(&mut self, mut viewport: RectU16) {
240 viewport = viewport.snap_to_tile_coordinates();
242 let clip_bbox = RectU16::new(0, 0, viewport.width(), viewport.height());
243 let num_rows = usize::from(viewport.height() / Tile::HEIGHT);
244
245 self.rows.clear();
246 self.rows.resize_with(num_rows, RowState::new);
247 self.paint_fill_attrs.clear();
248 self.layer_fill_attrs.clear();
249 self.filter_paints.clear();
250 for layer in self.active_layers.drain(..) {
251 self.occupied_rows_pool.submit(layer.occupied_rows);
252 }
253 self.occupied_rows_bool_scratch.clear();
254 self.occupied_rows_bool_scratch.resize(num_rows, false);
255 self.next_draw_id = 1;
256 self.viewport = viewport;
257 self.clip_bboxes.truncate(1);
258 self.clip_bboxes[0] = clip_bbox;
259 }
260
261 fn next_draw_id(&mut self) -> u32 {
262 let draw_id = self.next_draw_id;
263 self.next_draw_id += 1;
264
265 draw_id
266 }
267
268 #[inline(always)]
269 pub(super) fn ensure_row_layers(&mut self, row_idx: usize) {
270 let layer_depth = self.rows[row_idx].layer_depth;
271 if layer_depth == self.active_layers.len() {
272 return;
273 }
274
275 for layer_idx in layer_depth..self.active_layers.len() {
276 self.rows[row_idx].push_buf();
277 self.active_layers[layer_idx].occupied_rows.push(row_idx);
278 }
279 }
280
281 pub(crate) fn bucket_commands(
282 &mut self,
283 nodes: &[Node],
284 draws: &[RecordedFill],
285 layers: &[RecordedLayer],
286 strips: &[Strip],
287 encoded_paints: &[EncodedPaint],
288 filter_ctx: &FilterContext,
289 ) {
290 debug_assert_tile_aligned(self.viewport_origin(), "viewport origin");
299
300 for node in nodes {
301 for RecordedFill {
302 thread_idx,
303 strip_range,
304 paint,
305 blend_mode,
306 mask,
307 } in node.draws_in(draws)
308 {
309 let draw_id = self.next_draw_id();
310 let attrs = PaintFillAttrs {
311 paint: paint.clone(),
312 blend_mode: *blend_mode,
313 mask: mask.clone(),
314 draw_id,
315 thread_idx: *thread_idx,
316 origin: self.viewport_origin(),
317 };
318 self.generate_fill(&strips[strip_range.clone()], &attrs, encoded_paints);
319 }
320
321 if let Some(id) = node.layer {
322 let layer = &layers[id as usize];
323 let props = &layer.props;
324
325 match &layer.kind {
326 RecordedLayerKind::Regular => {
327 self.push_layer(props);
329 self.bucket_commands(
332 &layer.nodes,
333 draws,
334 layers,
335 strips,
336 encoded_paints,
337 filter_ctx,
338 );
339 self.pop_layer(strips);
340 }
341 RecordedLayerKind::Filter { placement, .. } => {
342 let needs_layer = props.blend_mode != BlendMode::default()
343 || props.opacity != 1.0
344 || props.mask.is_some()
345 || props.clip_path.is_some();
346
347 if needs_layer {
348 self.push_layer(props);
349 }
350
351 if let Some(pixmap) = filter_ctx.filter_layer(id as usize) {
354 self.generate_filter_layer_fill(
355 pixmap,
356 *placement,
357 encoded_paints.len(),
358 );
359 }
360
361 if needs_layer {
362 self.pop_layer(strips);
363 }
364 }
365 }
366 }
367 }
368 }
369
370 pub(crate) fn push_layer(&mut self, props: &LayerProps) {
371 let parent_bbox = *self.clip_bboxes.last().unwrap();
372 let bbox = props
373 .clip_path
374 .as_ref()
375 .map(|clip| {
376 let clip_bbox = clip.bbox.relative_to_origin(self.viewport_origin());
379 clip_bbox.intersect(parent_bbox)
380 })
381 .unwrap_or(parent_bbox);
382
383 if props.clip_path.is_some() {
384 self.clip_bboxes.push(bbox);
385 }
386
387 self.active_layers.push(ActiveLayer {
388 mask: props.mask.clone(),
392 blend_mode: props.blend_mode,
393 opacity: props.opacity,
394 clip: props.clip_path.clone(),
395 span: Self::bbox_span(bbox),
396 occupied_rows: self.occupied_rows_pool.take(),
397 });
398
399 if props.blend_mode.is_destructive() && !bbox.is_empty() {
403 let row_start = usize::from(bbox.y0 / Tile::HEIGHT);
404 let row_end = usize::from(bbox.y1.div_ceil(Tile::HEIGHT)).min(self.rows.len());
405 for row_idx in row_start..row_end {
406 self.ensure_row_layers(row_idx);
407 }
408 }
409 }
410
411 pub(crate) fn pop_layer(&mut self, strips: &[Strip]) {
412 let mut layer = self.active_layers.pop().unwrap();
413 let opacity = layer.opacity;
414 let blend_mode = layer.blend_mode;
415
416 if let Some(clip) = layer.clip {
427 let attrs_idx = self.layer_fill_attrs.len() as u32;
428 let draw_id = self.next_draw_id();
429
430 self.layer_fill_attrs.push(LayerFillAttrs {
431 blend_mode,
432 opacity,
433 mask: layer.mask.clone(),
434 draw_id,
435 thread_idx: clip.thread_idx,
436 });
437 self.clip_bboxes.pop();
438
439 let clip_strips = &strips[clip.strip_range];
442
443 for &row_idx in &layer.occupied_rows {
455 self.occupied_rows_bool_scratch[row_idx] = true;
456 }
457
458 self.generate(
461 clip_strips,
462 |bucketer, fill| {
463 if bucketer.occupied_rows_bool_scratch[fill.row_idx] {
464 bucketer.rows[fill.row_idx].push_cmd(RenderCmd::LayerFill(LayerFill::new(
465 fill.span, None, attrs_idx,
466 )));
467 }
468 },
469 |bucketer, fill| {
470 if bucketer.occupied_rows_bool_scratch[fill.row_idx] {
471 bucketer.rows[fill.row_idx].push_cmd(RenderCmd::LayerFill(LayerFill::new(
472 fill.span,
473 Some(fill.alpha_idx),
474 attrs_idx,
475 )));
476 }
477 },
478 );
479
480 for row_idx in layer.occupied_rows.drain(..) {
481 self.rows[row_idx].pop_buf();
482 self.occupied_rows_bool_scratch[row_idx] = false;
484 }
485
486 self.occupied_rows_pool.submit(layer.occupied_rows);
487 } else {
488 let attrs_idx = self.layer_fill_attrs.len() as u32;
489 let draw_id = self.next_draw_id();
490
491 self.layer_fill_attrs.push(LayerFillAttrs {
492 blend_mode,
493 opacity,
494 mask: layer.mask.clone(),
495 draw_id,
496 thread_idx: 0,
497 });
498
499 for row_idx in layer.occupied_rows.drain(..) {
500 let row = &mut self.rows[row_idx];
501
502 row.push_cmd(RenderCmd::LayerFill(LayerFill::new(
505 layer.span, None, attrs_idx,
506 )));
507 row.pop_buf();
508 }
509
510 self.occupied_rows_pool.submit(layer.occupied_rows);
511 }
512 }
513
514 pub(crate) fn generate_filter_layer_fill(
515 &mut self,
516 pixmap: Arc<Pixmap>,
517 placement: FilterLayerPlacement,
518 static_paint_count: usize,
519 ) {
520 let origin = self.viewport_origin();
521 let dest_bbox = placement.dest_bbox;
522 let src_sample_shift = placement.src_origin();
523
524 let src_offset = (
532 i32::from(src_sample_shift.0) - i32::from(dest_bbox.x0),
533 i32::from(src_sample_shift.1) - i32::from(dest_bbox.y0),
534 );
535
536 let dest_bbox = dest_bbox.relative_to_origin(origin);
542 let clip_bbox = *self.clip_bboxes.last().unwrap();
543 let clipped_dest_bbox = dest_bbox.intersect(clip_bbox);
548 if clipped_dest_bbox.is_empty() {
549 return;
550 }
551
552 let draw_id = self.next_draw_id();
553 let span = Self::bbox_span(clipped_dest_bbox);
554 let paint_idx = static_paint_count + self.filter_paints.len();
555 self.filter_paints.push(EncodedPaint::Image(EncodedImage {
556 source: ImageSource::Pixmap(pixmap),
557 sampler: ImageSampler {
558 x_extend: Extend::Pad,
559 y_extend: Extend::Pad,
560 quality: ImageQuality::Low,
561 alpha: 1.0,
562 },
563 may_have_transparency: true,
564 transform: Affine::translate((f64::from(src_offset.0), f64::from(src_offset.1))),
565 x_advance: Vec2::new(1.0, 0.0),
566 y_advance: Vec2::new(0.0, 1.0),
567 tint: None,
568 }));
569 let attrs_idx = self.paint_fill_attrs.len() as u32;
570 self.paint_fill_attrs.push(PaintFillAttrs {
571 paint: Paint::Indexed(IndexedPaint::new(paint_idx)),
572 blend_mode: BlendMode::default(),
573 mask: None,
574 draw_id,
575 thread_idx: 0,
576 origin,
577 });
578 let row_start = usize::from(clipped_dest_bbox.y0 / Tile::HEIGHT);
579 let row_end = usize::from(clipped_dest_bbox.y1.div_ceil(Tile::HEIGHT));
580 for row_idx in row_start..row_end {
581 self.push_fill(GeneratedFill { row_idx, span }, attrs_idx, None);
582 }
583 }
584
585 pub(crate) fn generate_fill(
586 &mut self,
587 strip_buf: &[Strip],
588 attrs: &PaintFillAttrs,
589 encoded_paints: &[EncodedPaint],
590 ) {
591 if strip_buf.is_empty() {
592 return;
593 }
594
595 debug_assert_ne!(attrs.draw_id, 0, "fill draw IDs should start at 1");
596
597 let attrs_idx = self.paint_fill_attrs.len() as u32;
598 self.paint_fill_attrs.push(attrs.clone());
599
600 let draw_id =
601 (self.active_layers.is_empty()
605 && attrs.blend_mode == BlendMode::default()
606 && attrs.mask.is_none()
607 && !attrs.paint.may_have_transparency(encoded_paints))
608 .then_some(attrs.draw_id);
609
610 self.generate(
611 strip_buf,
612 |bucketer, fill| {
613 bucketer.push_fill(fill, attrs_idx, draw_id);
615 },
616 |bucketer, fill| {
617 let row_idx = fill.row_idx;
618 bucketer.ensure_row_layers(row_idx);
619 bucketer.rows[row_idx].push_cmd(RenderCmd::PaintFill(PaintFill::new(
620 fill.span,
621 Some(fill.alpha_idx),
622 attrs_idx,
623 )));
624 },
625 );
626 }
627
628 pub(crate) fn generate<F, A>(
629 &mut self,
630 strip_buf: &[Strip],
631 mut fill_cmd: F,
632 mut alpha_fill_cmd: A,
633 ) where
634 F: FnMut(&mut Self, GeneratedFill),
635 A: FnMut(&mut Self, GeneratedAlphaFill),
636 {
637 if strip_buf.is_empty() {
638 return;
639 }
640
641 let clip_bbox = *self.clip_bboxes.last().unwrap();
642
643 if clip_bbox.is_empty() {
646 return;
647 }
648
649 let clip_x0 = clip_bbox.x0;
651 let clip_x1 = clip_bbox.x1;
652
653 debug_assert_tile_aligned((clip_x0, clip_bbox.y0), "clip start");
654 debug_assert_tile_aligned((clip_x1, clip_bbox.y1), "clip end");
655
656 let origin = self.viewport_origin();
657 debug_assert_tile_aligned(origin, "viewport origin");
658
659 let origin_tile_x = origin.0 / Tile::WIDTH;
668 let origin_tile_y = origin.1 / Tile::HEIGHT;
669 let clip_scene_y0 = origin.1.saturating_add(clip_bbox.y0);
670 let clip_scene_y1 = origin.1.saturating_add(clip_bbox.y1);
671 let clip_scene_x0 = origin.0.saturating_add(clip_x0);
673 let clip_scene_x1 = origin.0.saturating_add(clip_x1);
674
675 let tile_bounds = RectU16::new(
677 clip_scene_x0 / Tile::WIDTH,
678 clip_scene_y0 / Tile::HEIGHT,
679 clip_scene_x1 / Tile::WIDTH,
680 clip_scene_y1 / Tile::HEIGHT,
681 );
682
683 visit_strip_fill_segments(
684 strip_buf,
685 tile_bounds,
686 self,
687 |bucketer, segment| {
688 let row_idx = usize::from(segment.tile_y - origin_tile_y);
689 let x0 = (segment.tile_x0 - origin_tile_x) * Tile::WIDTH;
690 let x1 = (segment.tile_x1 - origin_tile_x) * Tile::WIDTH;
691 alpha_fill_cmd(
692 bucketer,
693 GeneratedAlphaFill {
694 row_idx,
695 span: Span::new(x0, x1 - x0),
696 alpha_idx: segment.alpha_idx,
697 },
698 );
699 },
700 |bucketer, segment| {
701 let row_idx = usize::from(segment.tile_y - origin_tile_y);
702 let x0 = (segment.tile_x0 - origin_tile_x) * Tile::WIDTH;
703 let x1 = (segment.tile_x1 - origin_tile_x) * Tile::WIDTH;
704 fill_cmd(
705 bucketer,
706 GeneratedFill {
707 row_idx,
708 span: Span::new(x0, x1 - x0),
709 },
710 );
711 },
712 );
713 }
714
715 fn push_fill(&mut self, fill: GeneratedFill, attrs_idx: u32, draw_id: Option<u32>) {
717 self.ensure_row_layers(fill.row_idx);
718 let row = &mut self.rows[fill.row_idx];
719 let draw_id = draw_id.filter(|_| row.layer_depth == 0);
720
721 let Some(draw_id) = draw_id else {
722 row.push_cmd(RenderCmd::PaintFill(PaintFill::new(
724 fill.span, None, attrs_idx,
725 )));
726
727 return;
728 };
729
730 depth::split_opaque_span(fill.span, |segment| match segment {
731 DepthSegment::Regular(span) => {
732 row.push_cmd(RenderCmd::PaintFill(PaintFill::new(span, None, attrs_idx)));
733 }
734 DepthSegment::Opaque(bucket_range) => {
735 row.push_depth_fill(DepthFill::new(bucket_range, attrs_idx), draw_id);
736 }
737 });
738 }
739}
740
741#[derive(Debug, Clone)]
743pub(crate) struct ActiveLayer {
744 pub(crate) mask: Option<Mask>,
745 pub(crate) blend_mode: BlendMode,
746 pub(crate) opacity: f32,
747 pub(crate) clip: Option<LayerClip>,
748 pub(crate) span: Span,
749 pub(crate) occupied_rows: Vec<usize>,
751}
752
753#[derive(Debug, Clone, Copy)]
755pub(crate) struct GeneratedFill {
756 pub(crate) row_idx: usize,
757 pub(crate) span: Span,
758}
759
760#[derive(Debug, Clone, Copy)]
762pub(crate) struct GeneratedAlphaFill {
763 pub(crate) row_idx: usize,
764 pub(crate) span: Span,
765 pub(crate) alpha_idx: u32,
766}
767
768#[cfg(test)]
769mod tests {
770 use super::LayerClip;
771 use crate::coarse::CommandBucketer;
772 use crate::coarse::cmd::{PaintFillAttrs, RenderCmd};
773 use crate::coarse::depth::{BucketRange, DEPTH_BUCKET_WIDTH};
774 use vello_common::color::palette::css::{BLUE, RED};
775 use vello_common::color::{AlphaColor, Srgb};
776 use vello_common::geometry::RectU16;
777 use vello_common::paint::{Paint, PremulColor};
778 use vello_common::peniko::{BlendMode, Compose, Mix};
779 use vello_common::record::LayerProps;
780 use vello_common::strip::Strip;
781 use vello_common::tile::Tile;
782
783 fn color(alpha: AlphaColor<Srgb>) -> PremulColor {
784 PremulColor::from_alpha_color(alpha)
785 }
786
787 fn fill_attrs(paint: Paint) -> PaintFillAttrs {
788 PaintFillAttrs {
789 paint,
790 blend_mode: BlendMode::default(),
791 mask: None,
792 draw_id: 1,
793 thread_idx: 0,
794 origin: (0, 0),
795 }
796 }
797
798 fn layer_props() -> LayerProps {
799 LayerProps {
800 blend_mode: BlendMode::default(),
801 opacity: 1.0,
802 mask: None,
803 clip_path: None,
804 }
805 }
806
807 fn clipped_layer_props(bbox: RectU16) -> LayerProps {
808 LayerProps {
809 blend_mode: BlendMode::default(),
810 opacity: 1.0,
811 mask: None,
812 clip_path: Some(LayerClip {
813 strip_range: 0..0,
814 thread_idx: 0,
815 bbox,
816 }),
817 }
818 }
819
820 fn destructive_clipped_layer_props(bbox: RectU16) -> LayerProps {
821 LayerProps {
822 blend_mode: BlendMode::new(Mix::Normal, Compose::Clear),
823 ..clipped_layer_props(bbox)
824 }
825 }
826
827 fn clipped_layer_props_with_strips(
828 bbox: RectU16,
829 strip_range: core::ops::Range<usize>,
830 ) -> LayerProps {
831 LayerProps {
832 blend_mode: BlendMode::default(),
833 opacity: 1.0,
834 mask: None,
835 clip_path: Some(LayerClip {
836 strip_range,
837 thread_idx: 0,
838 bbox,
839 }),
840 }
841 }
842
843 fn count_layer_fills(cmds: &[RenderCmd]) -> usize {
844 cmds.iter()
845 .filter(|cmd| matches!(cmd, RenderCmd::LayerFill(_)))
846 .count()
847 }
848
849 #[test]
850 fn opaque_fill_inside_layer_does_not_use_depth_write() {
851 let mut bucketer = CommandBucketer::from_wh(DEPTH_BUCKET_WIDTH, 4);
852 let strips = [
853 Strip::new(0, 0, 0, false),
854 Strip::new(DEPTH_BUCKET_WIDTH, 0, 0, true),
855 ];
856
857 bucketer.push_layer(&layer_props());
858 bucketer.generate_fill(&strips, &fill_attrs(Paint::Solid(color(RED))), &[]);
859
860 let row = &bucketer.rows()[0];
861 assert_eq!(row.depth_cmds.len(), 0);
862 assert_eq!(row.render_cmds.len(), 2);
863 assert!(matches!(row.render_cmds[0], RenderCmd::PushBuf(_)));
864 assert!(
865 matches!(row.render_cmds[1], RenderCmd::PaintFill(cmd) if cmd.span.pixel_x() == 0 && cmd.span.pixel_width() == DEPTH_BUCKET_WIDTH)
866 );
867 }
868
869 #[test]
870 fn alpha_fill_is_clipped_to_active_layer_bbox() {
871 let mut bucketer = CommandBucketer::from_wh(8, 4);
872 let strips = [Strip::new(0, 0, 0, false), Strip::new(12, 0, 48, false)];
873
874 bucketer.push_layer(&clipped_layer_props(RectU16::new(4, 0, 8, 4)));
875 bucketer.generate_fill(&strips, &fill_attrs(Paint::Solid(color(RED))), &[]);
876
877 let row = &bucketer.rows()[0];
878 assert_eq!(row.render_cmds.len(), 2);
879 assert!(matches!(row.render_cmds[0], RenderCmd::PushBuf(_)));
880 assert!(matches!(
881 row.render_cmds[1],
882 RenderCmd::PaintFill(cmd)
883 if cmd.span.pixel_x() == 4
884 && cmd.span.pixel_width() == 4
885 && cmd.alpha_idx() == Some(u32::from(4 * Tile::HEIGHT))
886 ));
887 }
888
889 #[test]
890 fn disjoint_nested_clip_bounds_do_not_emit_commands() {
891 let mut bucketer = CommandBucketer::from_wh(16, 4);
892 let strips = [Strip::new(0, 0, 0, false), Strip::new(16, 0, 0, true)];
893
894 bucketer.push_layer(&clipped_layer_props(RectU16::new(0, 0, 4, 4)));
895 bucketer.push_layer(&clipped_layer_props(RectU16::new(8, 0, 12, 4)));
896 bucketer.generate_fill(&strips, &fill_attrs(Paint::Solid(color(RED))), &[]);
897
898 assert!(bucketer.rows().iter().all(|row| row.render_cmds.is_empty()));
899 }
900
901 #[test]
902 fn empty_destructive_clip_does_not_push_rows() {
903 let mut bucketer = CommandBucketer::from_wh(16, 4);
904
905 bucketer.push_layer(&clipped_layer_props(RectU16::new(0, 0, 4, 4)));
906 bucketer.push_layer(&destructive_clipped_layer_props(RectU16::new(8, 0, 12, 4)));
907
908 assert!(bucketer.rows().iter().all(|row| row.render_cmds.is_empty()));
909 }
910
911 #[test]
912 fn opaque_fill_uses_depth_write_when_possible() {
913 let end = DEPTH_BUCKET_WIDTH * 2 + 4;
914 let mut bucketer = CommandBucketer::from_wh(end, 4);
915 let strips = [Strip::new(4, 0, 0, false), Strip::new(end, 0, 0, true)];
916
917 bucketer.generate_fill(&strips, &fill_attrs(Paint::Solid(color(RED))), &[]);
918
919 let row = &bucketer.rows()[0];
920 assert_eq!(row.depth_cmds.len(), 1);
921 assert_eq!(row.depth_cmds[0].bucket_range(), BucketRange::new(1, 2));
922 assert_eq!(row.render_cmds.len(), 2);
923 assert!(
924 matches!(row.render_cmds[0], RenderCmd::PaintFill(cmd) if cmd.span.pixel_x() == 4 && cmd.span.pixel_width() == DEPTH_BUCKET_WIDTH - 4)
925 );
926 assert!(
927 matches!(row.render_cmds[1], RenderCmd::PaintFill(cmd) if cmd.span.pixel_x() == DEPTH_BUCKET_WIDTH * 2 && cmd.span.pixel_width() == 4)
928 );
929 }
930
931 #[test]
932 fn non_opaque_fill_uses_regular_commands() {
933 let mut bucketer = CommandBucketer::from_wh(DEPTH_BUCKET_WIDTH, 4);
934 let strips = [
935 Strip::new(0, 0, 0, false),
936 Strip::new(DEPTH_BUCKET_WIDTH, 0, 0, true),
937 ];
938
939 bucketer.generate_fill(
940 &strips,
941 &fill_attrs(Paint::Solid(color(BLUE.with_alpha(0.5)))),
942 &[],
943 );
944
945 let row = &bucketer.rows()[0];
946 assert_eq!(row.depth_cmds.len(), 0);
947 assert_eq!(row.render_cmds.len(), 1);
948 assert!(
949 matches!(row.render_cmds[0], RenderCmd::PaintFill(cmd) if cmd.span.pixel_x() == 0 && cmd.span.pixel_width() == DEPTH_BUCKET_WIDTH)
950 );
951 }
952
953 #[test]
954 fn clips_fills_correctly_inside_nonzero_origin_viewport() {
955 let mut bucketer = CommandBucketer::new(RectU16::new(32, 32, 96, 96));
957 bucketer.push_layer(&clipped_layer_props(RectU16::new(40, 32, 72, 96)));
959
960 let strips = [
962 Strip::new(40, 32, 0, false),
963 Strip::new(72, 32, 32 * u32::from(Tile::HEIGHT), false),
964 ];
965 bucketer.generate_fill(&strips, &fill_attrs(Paint::Solid(color(RED))), &[]);
966
967 let row = &bucketer.rows()[0];
968
969 assert!(matches!(row.render_cmds[0], RenderCmd::PushBuf(_)));
970 assert!(matches!(
972 row.render_cmds[1],
973 RenderCmd::PaintFill(cmd)
974 if cmd.span.pixel_x() == 8
975 && cmd.span.pixel_width() == 32
976 && cmd.alpha_idx() == Some(0)
977 ));
978 }
979
980 #[test]
981 fn culls_clip_strips_above_viewport_origin() {
982 let mut bucketer = CommandBucketer::new(RectU16::new(0, 32, 64, 96));
984
985 let alpha = u32::from(Tile::HEIGHT);
986 let strips = [
987 Strip::new(0, 32, 0, false),
989 Strip::new(16, 32, 16 * alpha, false),
990 Strip::new(0, 0, 16 * alpha, false),
992 Strip::new(16, 0, 32 * alpha, false),
993 Strip::new(0, 32, 32 * alpha, false),
995 Strip::new(16, 32, 48 * alpha, false),
996 ];
997
998 bucketer.push_layer(&clipped_layer_props_with_strips(
1000 RectU16::new(0, 0, 16, 40),
1001 2..6,
1002 ));
1003 bucketer.generate_fill(&strips[0..2], &fill_attrs(Paint::Solid(color(RED))), &[]);
1004 bucketer.pop_layer(&strips);
1005
1006 let row = &bucketer.rows()[0];
1007 assert_eq!(
1008 count_layer_fills(&row.render_cmds),
1009 1,
1010 "row 0 must be composited exactly once, got {:?}",
1011 row.render_cmds
1012 );
1013 }
1014}