1use crate::flatten::Line;
7use alloc::vec;
8use alloc::vec::Vec;
9use fearless_simd::*;
10#[cfg(not(feature = "std"))]
11use peniko::kurbo::common::FloatFuncs as _;
12
13const T: u32 = 0b00001;
15const B: u32 = 0b00010;
17const L: u32 = 0b00100;
19const R: u32 = 0b01000;
21const W: u32 = 0b10000;
23
24const BOT_SHIFT: u32 = B.trailing_zeros();
26const LEFT_SHIFT: u32 = L.trailing_zeros();
28const RIGHT_SHIFT: u32 = R.trailing_zeros();
30const WINDING_SHIFT: u32 = W.trailing_zeros();
32
33const INTERSECTION_MASK: u32 = W | R | L | B | T;
35const INT_MASK_SHIFT: u32 = INTERSECTION_MASK.count_ones();
37
38pub const MAX_LINES_PER_PATH: u32 = 1 << (32 - INT_MASK_SHIFT);
42
43#[derive(Debug, Clone, Default)]
45pub struct CulledWindings {
46 pub partial: Vec<[f32; Tile::HEIGHT as usize]>,
48 pub coarse: Vec<i16>,
52 pub active: Vec<u32>,
54 pub culled: bool,
56 height: u16,
57}
58
59impl CulledWindings {
60 const WORD_BITS: usize = 32;
62 const WORD_SHIFT: usize = 5;
64 const WORD_MASK: usize = 31;
66
67 pub fn new(height: u16) -> Self {
69 let (num_rows, num_bits) = Self::sizes(height);
70
71 Self {
72 partial: vec![[0.0; Tile::HEIGHT as usize]; num_rows],
73 coarse: vec![0; num_rows],
74 active: vec![0; num_bits],
75 culled: false,
76 height,
77 }
78 }
79
80 fn sizes(height: u16) -> (usize, usize) {
81 let num_rows = usize::from(height).div_ceil(Tile::HEIGHT as usize);
82 let num_bits = num_rows.div_ceil(Self::WORD_BITS);
83
84 (num_rows, num_bits)
85 }
86
87 pub fn reset(&mut self, height: u16) {
89 if self.height != height {
90 let (num_rows, num_bits) = Self::sizes(height);
91 self.partial.resize(num_rows, [0.0; Tile::HEIGHT as usize]);
92 self.coarse.resize(num_rows, 0);
93 self.active.resize(num_bits, 0);
94 self.height = height;
95 }
96
97 if self.culled {
100 self.partial.fill([0.0; Tile::HEIGHT as usize]);
101 self.coarse.fill(0);
102 self.active.fill(0);
103 self.culled = false;
104 }
105 }
106
107 #[inline(always)]
109 pub fn mark_row_active(&mut self, row_idx: usize) {
110 self.active[row_idx >> Self::WORD_SHIFT] |= 1 << (row_idx & Self::WORD_MASK);
111 }
112
113 #[inline(always)]
115 pub fn mark_row_range_active(&mut self, start_row: usize, end_row: usize) {
116 if start_row >= end_row {
117 return;
118 }
119
120 let start_word = start_row >> Self::WORD_SHIFT;
121 let end_word = (end_row - 1) >> Self::WORD_SHIFT;
122
123 if start_word == end_word {
124 let shift = start_row & Self::WORD_MASK;
126 let count = end_row - start_row;
127 let mask = if count == Self::WORD_BITS {
128 u32::MAX
129 } else {
130 ((1_u32 << count) - 1) << shift
131 };
132 self.active[start_word] |= mask;
133 } else {
134 self.active[start_word] |= u32::MAX << (start_row & Self::WORD_MASK);
136
137 self.active[(start_word + 1)..end_word].fill(u32::MAX);
138
139 let end_shift = ((end_row - 1) & Self::WORD_MASK) + 1;
140 let mask = if end_shift == Self::WORD_BITS {
141 u32::MAX
142 } else {
143 (1_u32 << end_shift) - 1
144 };
145 self.active[end_word] |= mask;
146 }
147 }
148
149 #[inline(always)]
151 pub fn for_active_rows_in_range<F>(&self, start: usize, end: usize, mut f: F)
152 where
153 F: FnMut(usize),
154 {
155 if start >= end {
156 return;
157 }
158
159 let start_word = start >> Self::WORD_SHIFT;
160 let end_word = (end - 1) >> Self::WORD_SHIFT;
161
162 let mut process_word = |mut word: u32, word_idx: usize| {
163 while word != 0 {
164 let bit = word.trailing_zeros();
165 word &= !(1_u32 << bit);
166 f((word_idx << Self::WORD_SHIFT) + bit as usize);
167 }
168 };
169
170 let end_limit = ((end - 1) & Self::WORD_MASK) + 1;
171 let end_mask = if end_limit == Self::WORD_BITS {
172 u32::MAX
173 } else {
174 (1_u32 << end_limit) - 1
175 };
176
177 {
179 let mut word = self.active[start_word];
180 let start_bit = start & Self::WORD_MASK;
181 word &= !((1_u32 << start_bit) - 1);
182
183 if start_word == end_word {
184 word &= end_mask;
185 }
186
187 process_word(word, start_word);
188 }
189
190 if start_word < end_word {
192 for (word_idx, &word_val) in self
193 .active
194 .iter()
195 .enumerate()
196 .take(end_word)
197 .skip(start_word + 1)
198 {
199 process_word(word_val, word_idx);
200 }
201
202 process_word(self.active[end_word] & end_mask, end_word);
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy)]
219#[repr(C)]
220pub struct Tile {
221 #[cfg(target_endian = "big")]
228 pub y: u16,
230
231 #[cfg(target_endian = "big")]
232 pub x: u16,
234
235 pub packed_winding_line_idx: u32,
251
252 #[cfg(target_endian = "little")]
253 pub x: u16,
255
256 #[cfg(target_endian = "little")]
257 pub y: u16,
259}
260
261impl Tile {
262 pub const WIDTH: u16 = 4;
264
265 pub const HEIGHT: u16 = 4;
267
268 pub const SENTINEL: Self = Self::new(u16::MAX, u16::MAX, 0, 0);
270
271 #[inline]
276 pub fn new_clamped(x: u16, y: u16, line_idx: u32, intersection_mask: u32) -> Self {
277 Self::new(
278 x.min(u16::MAX / Self::WIDTH),
281 y.min(u16::MAX / Self::HEIGHT),
282 line_idx,
283 intersection_mask,
284 )
285 }
286
287 #[inline]
293 pub const fn new(x: u16, y: u16, line_idx: u32, intersection_mask: u32) -> Self {
294 #[cfg(debug_assertions)]
295 if line_idx >= MAX_LINES_PER_PATH {
296 panic!("Max. number of lines per path exceeded.");
297 }
298 Self {
301 x,
302 y,
303 packed_winding_line_idx: (line_idx << INT_MASK_SHIFT) | intersection_mask,
304 }
305 }
306
307 #[inline]
309 pub const fn same_loc(&self, other: &Self) -> bool {
310 self.same_row(other) && self.x == other.x
311 }
312
313 #[inline]
315 pub const fn prev_loc(&self, other: &Self) -> bool {
316 self.same_row(other) && self.x + 1 == other.x
317 }
318
319 #[inline]
321 pub const fn same_row(&self, other: &Self) -> bool {
322 self.y == other.y
323 }
324
325 #[inline]
329 pub const fn line_idx(&self) -> u32 {
330 self.packed_winding_line_idx >> INT_MASK_SHIFT
331 }
332
333 #[inline]
340 pub const fn winding(&self) -> bool {
341 (self.packed_winding_line_idx & W) != 0
342 }
343
344 #[inline]
346 pub const fn intersection_mask(&self) -> u32 {
347 self.packed_winding_line_idx & INTERSECTION_MASK
348 }
349
350 #[inline]
352 pub const fn intersects_top(&self) -> bool {
353 (self.intersection_mask() & T) != 0
354 }
355
356 #[inline]
358 pub const fn intersects_bottom(&self) -> bool {
359 (self.intersection_mask() & B) != 0
360 }
361
362 #[inline]
364 pub const fn intersects_left(&self) -> bool {
365 (self.intersection_mask() & L) != 0
366 }
367
368 #[inline]
370 pub const fn intersects_right(&self) -> bool {
371 (self.intersection_mask() & R) != 0
372 }
373
374 #[inline(always)]
379 const fn to_bits(self) -> u64 {
380 ((self.y as u64) << 48) | ((self.x as u64) << 32) | self.packed_winding_line_idx as u64
388 }
389
390 #[inline(always)]
395 pub const fn is_sentinel(&self) -> bool {
396 self.x == u16::MAX
397 }
398}
399
400impl PartialEq for Tile {
401 #[inline(always)]
402 fn eq(&self, other: &Self) -> bool {
403 self.to_bits() == other.to_bits()
404 }
405}
406
407impl Ord for Tile {
408 #[inline(always)]
409 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
410 self.to_bits().cmp(&other.to_bits())
411 }
412}
413
414impl PartialOrd for Tile {
415 #[inline(always)]
416 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
417 Some(self.cmp(other))
418 }
419}
420
421impl Eq for Tile {}
422
423#[derive(Clone, Debug)]
425pub struct Tiles {
426 tile_buf: Vec<Tile>,
427 level: Level,
428 sorted: bool,
429 width: u16,
430 height: u16,
431 pub windings: CulledWindings,
433}
434
435impl Tiles {
436 pub fn new(level: Level, width: u16, height: u16) -> Self {
438 Self {
439 tile_buf: vec![],
440 level,
441 sorted: false,
442 width,
443 height,
444 windings: CulledWindings::new(height),
445 }
446 }
447
448 pub fn len(&self) -> u32 {
450 self.tile_buf.len() as u32
451 }
452
453 pub fn is_empty(&self) -> bool {
455 self.tile_buf.is_empty()
456 }
457
458 pub(crate) fn width(&self) -> u16 {
460 self.width
461 }
462
463 pub fn has_culled_tiles(&self) -> bool {
465 self.windings.culled
466 }
467
468 pub fn reset(&mut self, width: u16, height: u16) {
470 self.windings.reset(height);
471 self.width = width;
472 self.height = height;
473 self.tile_buf.clear();
474 self.sorted = false;
475 }
476
477 pub fn sort_tiles(&mut self) {
479 self.sorted = true;
480 self.level.dispatch(|_| self.tile_buf.sort_unstable());
482 }
483
484 #[inline]
488 pub fn get(&self, index: u32) -> &Tile {
489 assert!(
490 self.sorted,
491 "attempted to call `get` before sorting the tile container."
492 );
493
494 &self.tile_buf[index as usize]
495 }
496
497 #[inline]
501 pub fn iter(&self) -> impl Iterator<Item = &Tile> {
502 assert!(
503 self.sorted,
504 "attempted to call `iter` before sorting the tile container."
505 );
506
507 self.tile_buf.iter()
508 }
509
510 pub fn make_tiles_analytic_aa(
515 &mut self,
516 level: Level,
517 lines: &[Line],
518 width: u16,
519 height: u16,
520 ) -> bool {
521 dispatch!(level, simd => self.make_tiles_analytic_aa_impl::<_>(
522 simd,
523 lines,
524 width,
525 height,
526 ))
527 }
528
529 #[inline(always)]
530 fn make_tiles_analytic_aa_impl<S: Simd>(
531 &mut self,
532 s: S,
533 lines: &[Line],
534 width: u16,
535 height: u16,
536 ) -> bool {
537 self.reset(width, height);
538
539 if width == 0 || height == 0 {
540 return self.windings.culled;
541 }
542
543 debug_assert!(
544 lines.len() <= MAX_LINES_PER_PATH as usize,
545 "Max. number of lines per path exceeded. Max is {}, got {}.",
546 MAX_LINES_PER_PATH,
547 lines.len()
548 );
549
550 let tile_columns = width.div_ceil(Tile::WIDTH);
551 let tile_rows = height.div_ceil(Tile::HEIGHT);
552
553 let px_top = f32x4::from_slice(s, &[0.0, 1.0, 2.0, 3.0]);
554 let px_bottom = px_top + f32x4::splat(s, 1.0);
555 let simd_zero = f32x4::splat(s, 0.0);
556 let tile_height_f32 = Tile::HEIGHT as f32;
557
558 for (line_idx, line) in lines.iter().take(MAX_LINES_PER_PATH as usize).enumerate() {
559 let line_idx = line_idx as u32;
560
561 let p0_x = line.p0.x / f32::from(Tile::WIDTH);
562 let p0_y = line.p0.y / f32::from(Tile::HEIGHT);
563 let p1_x = line.p1.x / f32::from(Tile::WIDTH);
564 let p1_y = line.p1.y / f32::from(Tile::HEIGHT);
565
566 let (line_left_x, line_right_x) = if p0_x < p1_x {
567 (p0_x, p1_x)
568 } else {
569 (p1_x, p0_x)
570 };
571
572 if line_left_x > tile_columns as f32 {
574 continue;
575 }
576
577 let (line_top_y, line_top_x, line_bottom_y, line_bottom_x) = if p0_y < p1_y {
578 (p0_y, p0_x, p1_y, p1_x)
579 } else {
580 (p1_y, p1_x, p0_y, p0_x)
581 };
582
583 let y_top_tiles = (line_top_y as u16).min(tile_rows);
585 let line_bottom_y_ceil = line_bottom_y.ceil();
586 let y_bottom_tiles = (line_bottom_y_ceil as u16).min(tile_rows);
587
588 if y_top_tiles >= y_bottom_tiles {
592 continue;
595 }
596
597 let dir = if p0_y >= p1_y { 1 } else { -1 };
598 let f_dir = dir as f32;
599 let f_dir_v = f32x4::splat(s, f_dir);
600
601 macro_rules! calc_fractional_coverage {
602 ($y_idx:expr, $segment_top_y:expr, $segment_bottom_y:expr) => {{
603 let y_idx_f32 = f32::from($y_idx);
604 let local_y_start = ($segment_top_y - y_idx_f32) * tile_height_f32;
605 let local_y_end = ($segment_bottom_y - y_idx_f32) * tile_height_f32;
606
607 let start_v = f32x4::splat(s, local_y_start);
608 let end_v = f32x4::splat(s, local_y_end);
609
610 (px_bottom.min(end_v) - px_top.max(start_v)).max(simd_zero)
611 }};
612 }
613
614 if line_right_x < 0.0 {
617 let is_start_culled = line_top_y < 0.0;
618
619 if !is_start_culled {
623 self.windings.mark_row_active(y_top_tiles as usize);
624
625 let at_top_of_tile = line_top_y <= f32::from(y_top_tiles);
629 if at_top_of_tile {
630 self.windings.coarse[y_top_tiles as usize] += dir;
631 }
632
633 let fractional_coverage =
634 calc_fractional_coverage!(y_top_tiles, line_top_y, line_bottom_y);
635 let target_row = &mut self.windings.partial[y_top_tiles as usize];
636 let current = f32x4::from_slice(s, target_row);
637
638 let double_count = if at_top_of_tile {
640 f_dir_v
641 } else {
642 f32x4::splat(s, 0.0)
643 };
644 let next = fractional_coverage.mul_add(f_dir_v, current - double_count);
645 next.store_slice(target_row);
646 }
647
648 let y_start_middle = if is_start_culled {
649 y_top_tiles
650 } else {
651 y_top_tiles + 1
652 };
653 let line_bottom_floor = line_bottom_y.floor();
654 let y_end_middle = (line_bottom_floor as u16).min(tile_rows);
655
656 for y_idx in y_start_middle..y_end_middle {
657 self.windings.coarse[y_idx as usize] += dir;
658 }
659 self.windings
660 .mark_row_range_active(y_start_middle as usize, y_end_middle as usize);
661
662 if line_bottom_y != line_bottom_floor
663 && y_end_middle < tile_rows
664 && (is_start_culled || y_end_middle != y_top_tiles)
667 {
668 self.windings.mark_row_active(y_end_middle as usize);
669 self.windings.coarse[y_end_middle as usize] += dir;
671 let fractional_coverage =
672 calc_fractional_coverage!(y_end_middle, line_top_y, line_bottom_y);
673 let target_row = &mut self.windings.partial[y_end_middle as usize];
674 let current = f32x4::from_slice(s, target_row);
675 let next = fractional_coverage.mul_add(f_dir_v, current - f_dir_v);
677 next.store_slice(target_row);
678 }
679
680 self.windings.culled = true;
681 continue;
682 }
683
684 let p0_tile_x = line_top_x.floor() as i32;
686 let p0_tile_y = line_top_y.floor() as i32;
687 let p1_tile_x = line_bottom_x.floor() as i32;
688 let p1_tile_y = line_bottom_y.floor() as i32;
689
690 let not_same_tile = p0_tile_y != p1_tile_y || p0_tile_x != p1_tile_x;
692 if not_same_tile {
693 if line_left_x == line_right_x {
696 let x = (line_left_x as u16).min(tile_columns.saturating_sub(1));
697
698 let is_start_culled = line_top_y < 0.0;
700 if !is_start_culled {
701 let winding =
702 ((f32::from(y_top_tiles) >= line_top_y) as u32) << WINDING_SHIFT;
703 let tile = Tile::new_clamped(x, y_top_tiles, line_idx, winding);
704 self.tile_buf.push(tile);
705 }
706
707 let y_start = if is_start_culled {
710 y_top_tiles
711 } else {
712 y_top_tiles + 1
713 };
714
715 for y_idx in y_start..y_bottom_tiles {
716 let tile = Tile::new_clamped(x, y_idx, line_idx, W);
717 self.tile_buf.push(tile);
718 }
719 } else {
720 let dx = p1_x - p0_x;
722 let dy = p1_y - p0_y;
723 let x_slope = dx / dy;
724 let dx_dir = (line_bottom_x >= line_top_x) as u32;
725 let not_dx_dir = dx_dir ^ 1;
726
727 let w_start_base = dx_dir << WINDING_SHIFT;
728 let w_end_base = not_dx_dir << WINDING_SHIFT;
729
730 let push_row_extents = {
731 #[inline(always)]
732 |tile_buf: &mut Vec<Tile>,
733 y_idx: u16,
734 row_left_x: f32,
735 row_right_x: f32,
736 w_start: u32,
737 w_end: u32,
738 w_single: u32| {
739 let x_start = row_left_x as u16;
740 let x_end = (row_right_x as u16).min(tile_columns - 1);
741
742 if x_start <= x_end {
743 let winding = if x_start == x_end { w_single } else { w_start };
744
745 tile_buf.push(Tile::new(x_start, y_idx, line_idx, winding));
746
747 for x_idx in x_start.saturating_add(1)..x_end {
748 tile_buf.push(Tile::new(x_idx, y_idx, line_idx, 0));
749 }
750
751 if x_start < x_end {
752 tile_buf.push(Tile::new(x_end, y_idx, line_idx, w_end));
753 }
754 }
755 }
756 };
757
758 let mut push_row = {
759 #[inline(always)]
760 |y_idx: u16,
761 row_top_y: f32,
762 row_bottom_y: f32,
763 w_start: u32,
764 w_end: u32,
765 w_single: u32| {
766 let row_top_x = p0_x + (row_top_y - p0_y) * x_slope;
767 let row_bottom_x = p0_x + (row_bottom_y - p0_y) * x_slope;
768
769 let row_left_x = f32::min(row_top_x, row_bottom_x).max(line_left_x);
771 let row_right_x = f32::max(row_top_x, row_bottom_x).min(line_right_x);
772
773 if row_left_x < 0.0 {
774 self.windings.culled = true;
775
776 if row_right_x < 0.0 {
777 self.windings.mark_row_active(y_idx as usize);
781
782 let crosses_top = (w_single & W) != 0;
783 if crosses_top {
784 self.windings.coarse[y_idx as usize] += dir;
785 }
786
787 let fractional_coverage =
788 calc_fractional_coverage!(y_idx, row_top_y, row_bottom_y);
789 let target_row = &mut self.windings.partial[y_idx as usize];
790 let current = f32x4::from_slice(s, target_row);
791
792 let double_count = if crosses_top {
793 f_dir_v
794 } else {
795 f32x4::splat(s, 0.0)
796 };
797 let next = fractional_coverage
798 .mul_add(f_dir_v, current - double_count);
799 next.store_slice(target_row);
800
801 return;
802 } else {
803 let y_slope = dy / dx;
807 let y_intersect = row_top_y - (row_top_x * y_slope);
808
809 let (off_screen_top_y, off_screen_bottom_y) = if row_top_x < 0.0
810 {
811 (row_top_y, f32::min(row_bottom_y, y_intersect))
812 } else {
813 (f32::max(row_top_y, y_intersect), row_bottom_y)
814 };
815
816 if off_screen_top_y < off_screen_bottom_y {
817 self.windings.mark_row_active(y_idx as usize);
818 let fractional_coverage = calc_fractional_coverage!(
819 y_idx,
820 off_screen_top_y,
821 off_screen_bottom_y
822 );
823 let target_row = &mut self.windings.partial[y_idx as usize];
824 let current = f32x4::from_slice(s, target_row);
825 let next = fractional_coverage.mul_add(f_dir_v, current);
826 next.store_slice(target_row);
827 }
828 }
829 }
830
831 push_row_extents(
832 &mut self.tile_buf,
833 y_idx,
834 row_left_x,
835 row_right_x,
836 w_start,
837 w_end,
838 w_single,
839 );
840 }
841 };
842
843 let is_start_culled = line_top_y < 0.0;
844 if line_left_x >= 0.0 && line_right_x < tile_columns as f32 {
848 if !is_start_culled {
849 let y = f32::from(y_top_tiles);
850 let row_bottom_y = (y + 1.0).min(line_bottom_y);
851 let row_bottom_x = if row_bottom_y == line_bottom_y {
852 line_bottom_x
853 } else {
854 p0_x + (row_bottom_y - p0_y) * x_slope
855 };
856 let mask = ((y >= line_top_y) as u32) << WINDING_SHIFT;
857 push_row_extents(
858 &mut self.tile_buf,
859 y_top_tiles,
860 f32::min(line_top_x, row_bottom_x),
861 f32::max(line_top_x, row_bottom_x),
862 w_start_base & mask,
863 w_end_base & mask,
864 W & mask,
865 );
866 }
867
868 let y_start = if is_start_culled {
869 y_top_tiles
870 } else {
871 y_top_tiles + 1
872 };
873
874 if y_start < y_bottom_tiles {
875 let mut row_top_x = p0_x + (f32::from(y_start) - p0_y) * x_slope;
876 for y_idx in y_start..y_bottom_tiles {
877 let y = f32::from(y_idx);
878 let row_bottom_x = if line_bottom_y < y + 1.0 {
882 line_bottom_x
883 } else {
884 p0_x + (y + 1.0 - p0_y) * x_slope
885 };
886 push_row_extents(
887 &mut self.tile_buf,
888 y_idx,
889 f32::min(row_top_x, row_bottom_x),
890 f32::max(row_top_x, row_bottom_x),
891 w_start_base,
892 w_end_base,
893 W,
894 );
895 row_top_x = row_bottom_x;
896 }
897 }
898 } else {
899 if !is_start_culled {
900 let y = f32::from(y_top_tiles);
901 let row_bottom_y = (y + 1.0).min(line_bottom_y);
902 let mask = ((y >= line_top_y) as u32) << WINDING_SHIFT;
903 push_row(
904 y_top_tiles,
905 line_top_y,
906 row_bottom_y,
907 w_start_base & mask,
908 w_end_base & mask,
909 W & mask,
910 );
911 }
912
913 let y_start = if is_start_culled {
914 y_top_tiles
915 } else {
916 y_top_tiles + 1
917 };
918
919 for y_idx in y_start..y_bottom_tiles {
920 let y = f32::from(y_idx);
921 let row_bottom_y = (y + 1.0).min(line_bottom_y);
922 push_row(y_idx, y, row_bottom_y, w_start_base, w_end_base, W);
923 }
924 }
925 }
926 } else {
927 let tile = Tile::new_clamped(
929 (line_left_x as u16).min(tile_columns + 1),
930 y_top_tiles,
931 line_idx,
932 ((f32::from(y_top_tiles) >= line_top_y) as u32) << WINDING_SHIFT,
933 );
934 self.tile_buf.push(tile);
935 }
936 }
937
938 self.windings.culled
939 }
940
941 pub fn make_tiles_msaa(&mut self, lines: &[Line], width: u16, height: u16) {
965 self.reset(width, height);
966
967 if width == 0 || height == 0 {
968 return;
969 }
970
971 debug_assert!(
972 lines.len() <= MAX_LINES_PER_PATH as usize,
973 "Max. number of lines per path exceeded. Max is {}, got {}.",
974 MAX_LINES_PER_PATH,
975 lines.len()
976 );
977
978 let tile_columns = width.div_ceil(Tile::WIDTH);
979 let tile_rows = height.div_ceil(Tile::HEIGHT);
980
981 for (line_idx, line) in lines.iter().take(MAX_LINES_PER_PATH as usize).enumerate() {
982 let line_idx = line_idx as u32;
983
984 let p0_x = line.p0.x / f32::from(Tile::WIDTH);
985 let p0_y = line.p0.y / f32::from(Tile::HEIGHT);
986 let p1_x = line.p1.x / f32::from(Tile::WIDTH);
987 let p1_y = line.p1.y / f32::from(Tile::HEIGHT);
988
989 let (line_left_x, line_right_x) = if p0_x < p1_x {
990 (p0_x, p1_x)
991 } else {
992 (p1_x, p0_x)
993 };
994
995 if line_left_x > tile_columns as f32 {
997 continue;
998 }
999
1000 let (line_top_y, line_top_x, line_bottom_y, line_bottom_x) = if p0_y < p1_y {
1001 (p0_y, p0_x, p1_y, p1_x)
1002 } else {
1003 (p1_y, p1_x, p0_y, p0_x)
1004 };
1005
1006 let y_top_tiles = (line_top_y as u16).min(tile_rows);
1008 let line_bottom_y_ceil = line_bottom_y.ceil();
1009 let y_bottom_tiles = (line_bottom_y_ceil as u16).min(tile_rows);
1010
1011 if y_top_tiles >= y_bottom_tiles {
1015 continue;
1016 }
1017
1018 let p0_tile_x = line_top_x.floor() as i32;
1020 let p0_tile_y = line_top_y.floor() as i32;
1021 let p1_tile_x = line_bottom_x.floor() as i32;
1022
1023 let p1_tile_y = if line_bottom_y == line_bottom_y_ceil {
1024 line_bottom_y as i32 - 1
1025 } else {
1026 line_bottom_y.floor() as i32
1027 };
1028
1029 let not_same_tile = p0_tile_y != p1_tile_y || p0_tile_x != p1_tile_x;
1031 if not_same_tile {
1032 if line_left_x == line_right_x {
1034 let x = (line_left_x as u16).min(tile_columns.saturating_sub(1));
1035
1036 let is_start_culled = line_top_y < 0.0;
1038 if !is_start_culled {
1039 let winding =
1040 ((f32::from(y_top_tiles) >= line_top_y) as u32) << WINDING_SHIFT;
1041 let intersection_mask = B | winding;
1042 let tile = Tile::new_clamped(x, y_top_tiles, line_idx, intersection_mask);
1043 self.tile_buf.push(tile);
1044 }
1045
1046 let y_start = if is_start_culled {
1049 y_top_tiles
1050 } else {
1051 y_top_tiles + 1
1052 };
1053 let line_bottom_floor = line_bottom_y.floor();
1054 let y_end_idx = (line_bottom_floor as u16).min(tile_rows);
1055
1056 if y_start < y_end_idx {
1057 let y_last = y_end_idx - 1;
1058 for y_idx in y_start..y_last {
1059 let intersection_mask = W | B | T;
1060 let tile = Tile::new_clamped(x, y_idx, line_idx, intersection_mask);
1061 self.tile_buf.push(tile);
1062 }
1063
1064 {
1066 let is_end_tile = ((y_last as i32) == p1_tile_y) as u32;
1067 let intersection_mask = W | T | ((1 ^ is_end_tile) << BOT_SHIFT);
1068 let tile = Tile::new_clamped(x, y_last, line_idx, intersection_mask);
1069 self.tile_buf.push(tile);
1070 }
1071 }
1072
1073 if line_bottom_y != line_bottom_floor && y_end_idx < tile_rows {
1077 let intersection_mask = W | T;
1078 let tile = Tile::new_clamped(x, y_end_idx, line_idx, intersection_mask);
1079 self.tile_buf.push(tile);
1080 }
1081 } else {
1082 let dx = p1_x - p0_x;
1083 let dy = p1_y - p0_y;
1084 let x_slope = dx / dy;
1085 let dx_dir = (line_bottom_x >= line_top_x) as u32;
1086 let not_dx_dir = dx_dir ^ 1;
1087
1088 let w_start_base = dx_dir << WINDING_SHIFT;
1089 let w_end_base = not_dx_dir << WINDING_SHIFT;
1090
1091 let min_x = p0_x.min(p1_x);
1094 let max_x = p0_x.max(p1_x);
1095 let needs_clamping = min_x < line_left_x || max_x >= line_right_x;
1097
1098 macro_rules! push_edge {
1100 ($x:expr, $y:expr, $row_top_x:expr, $row_bottom_x:expr,
1101 $canonical_start:expr, $canonical_end:expr, $winding_input:expr,
1102 $check_s:tt, $check_e:expr) => {{
1103 let x_idx = $x;
1104
1105 let unc_row_start = (x_idx as i32 == $canonical_start) as u32;
1106 let unc_row_end = (x_idx == $canonical_end) as u32;
1107
1108 let canonical_row_start =
1109 (dx_dir & unc_row_start) | (not_dx_dir & unc_row_end);
1110 let canonical_row_end =
1111 (not_dx_dir & unc_row_start) | (dx_dir & unc_row_end);
1112
1113 let start_tile = if $check_s {
1114 ((x_idx as i32 == p0_tile_x) && ($y as i32 == p0_tile_y)) as u32
1115 } else {
1116 0
1117 };
1118
1119 let end_tile = if $check_e {
1120 ((x_idx as i32 == p1_tile_x) && ($y as i32 == p1_tile_y)) as u32
1121 } else {
1122 0
1123 };
1124
1125 let mut mask = $winding_input;
1126
1127 mask |= canonical_row_start & (1 ^ start_tile);
1129 mask |= (1 ^ canonical_row_start) << not_dx_dir << LEFT_SHIFT;
1130 mask |= (canonical_row_end & (1 ^ end_tile)) << BOT_SHIFT;
1131 mask |= (1 ^ canonical_row_end) << dx_dir << LEFT_SHIFT;
1132
1133 let x_left_f = x_idx as f32;
1135 let x_right_f = (x_idx + 1) as f32;
1136 let trc = (($row_top_x == x_right_f) as u32) & (1 ^ start_tile);
1137 let tlc = (($row_top_x == x_left_f) as u32) & (1 ^ start_tile);
1138 let brc = (($row_bottom_x == x_right_f) as u32) & (1 ^ end_tile);
1139 let blc = (($row_bottom_x == x_left_f) as u32) & (1 ^ end_tile);
1140 let tie_break = tlc & (canonical_row_start ^ 1);
1142
1143 mask |= (tie_break | blc) << LEFT_SHIFT;
1144 mask |= (trc | brc) << RIGHT_SHIFT;
1145 mask &= !(tie_break | trc);
1146 mask &= !((blc | brc) << BOT_SHIFT);
1147
1148 self.tile_buf.push(Tile::new(x_idx, $y, line_idx, mask));
1149 }};
1150 }
1151
1152 macro_rules! process_row {
1154 ($y_idx:expr, $row_top_y:expr, $row_bottom_y:expr, $w_mask:expr,
1155 $check_s:tt, $check_e:tt, $clamped:tt) => {{
1156 let row_top_x = p0_x + ($row_top_y - p0_y) * x_slope;
1157 let row_bottom_x = p0_x + ($row_bottom_y - p0_y) * x_slope;
1158
1159 let (row_left_x, row_right_x, x_end) = if $clamped {
1160 let lx = f32::min(row_top_x, row_bottom_x).max(line_left_x);
1161 let rx = f32::max(row_top_x, row_bottom_x).min(line_right_x);
1162 let xe = (rx as u16).min(tile_columns.saturating_sub(1));
1163 (lx, rx, xe)
1164 } else {
1165 let lx = f32::min(row_top_x, row_bottom_x);
1166 let rx = f32::max(row_top_x, row_bottom_x);
1167 let xe = rx as u16; (lx, rx, xe)
1169 };
1170
1171 let canonical_x_start = row_left_x.floor() as i32;
1172 let canonical_x_end = row_right_x as u16;
1173 let x_start = row_left_x as u16;
1174
1175 if x_start <= x_end {
1176 let is_single = (x_start == x_end) as u32;
1177 let w_left = (w_start_base | (is_single << 4)) & $w_mask;
1178
1179 push_edge!(
1180 x_start,
1181 $y_idx,
1182 row_top_x,
1183 row_bottom_x,
1184 canonical_x_start,
1185 canonical_x_end,
1186 w_left,
1187 $check_s,
1188 $check_e
1189 );
1190
1191 for x_idx in x_start.saturating_add(1)..x_end {
1192 self.tile_buf
1193 .push(Tile::new(x_idx, $y_idx, line_idx, R | L));
1194 }
1195
1196 if x_start < x_end {
1197 let w_right = w_end_base & $w_mask;
1198 push_edge!(
1199 x_end,
1200 $y_idx,
1201 row_top_x,
1202 row_bottom_x,
1203 canonical_x_start,
1204 canonical_x_end,
1205 w_right,
1206 $check_s,
1207 $check_e
1208 );
1209 }
1210 }
1211 }};
1212 }
1213
1214 macro_rules! run_loops {
1216 ($clamped:tt) => {{
1217 let is_start_culled = line_top_y < 0.0;
1219 if !is_start_culled {
1220 let y = f32::from(y_top_tiles);
1221 let row_bottom_y = (y + 1.0).min(line_bottom_y);
1222 let mask = ((y >= line_top_y) as u32) << WINDING_SHIFT;
1223 process_row!(
1224 y_top_tiles,
1225 line_top_y,
1226 row_bottom_y,
1227 mask,
1228 true,
1229 true,
1230 $clamped
1231 );
1232 }
1233
1234 let y_start_middle = if is_start_culled {
1235 y_top_tiles
1236 } else {
1237 y_top_tiles + 1
1238 };
1239 let line_bottom_floor = line_bottom_y.floor();
1240 let y_end_middle = (line_bottom_floor as u16).min(tile_rows);
1241 let has_separate_bottom_row = line_bottom_y != line_bottom_floor
1242 && y_end_middle < tile_rows
1243 && (is_start_culled || y_end_middle != y_top_tiles);
1244
1245 if y_start_middle < y_end_middle {
1246 for y_idx in y_start_middle..y_end_middle {
1247 let y = f32::from(y_idx);
1248 let row_bottom_y = (y + 1.0).min(line_bottom_y);
1249 let is_last_middle = y_idx == y_end_middle - 1;
1250 let check_end = is_last_middle && !has_separate_bottom_row;
1251
1252 process_row!(
1253 y_idx,
1254 y,
1255 row_bottom_y,
1256 u32::MAX,
1257 false,
1258 check_end,
1259 $clamped
1260 );
1261 }
1262 }
1263
1264 if has_separate_bottom_row {
1266 let y_idx = y_end_middle;
1267 let y = f32::from(y_idx);
1268 process_row!(
1269 y_idx,
1270 y,
1271 line_bottom_y,
1272 u32::MAX,
1273 false,
1274 true,
1275 $clamped
1276 );
1277 }
1278 }};
1279 }
1280
1281 if needs_clamping {
1282 run_loops!(true);
1283 } else {
1284 run_loops!(false);
1285 }
1286 }
1287 } else {
1288 let tile = Tile::new_clamped(
1290 (line_left_x as u16).min(tile_columns + 1),
1291 y_top_tiles,
1292 line_idx,
1293 ((f32::from(y_top_tiles) >= line_top_y) as u32) << WINDING_SHIFT,
1294 );
1295 self.tile_buf.push(tile);
1296 }
1297 }
1298 }
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use crate::flatten::{FlattenCtx, Line, Point, fill};
1304 use crate::geometry::RectU16;
1305 use crate::kurbo::{Affine, BezPath};
1306 use crate::tile::CulledWindings;
1307 use crate::tile::{B, L, R, T, Tile, Tiles, W};
1308 use fearless_simd::Level;
1309 use std::vec::Vec;
1310
1311 const VIEW_DIM: u16 = 100;
1312 const F_V_DIM: f32 = VIEW_DIM as f32;
1313
1314 fn new_tiles() -> Tiles {
1315 Tiles::new(
1316 Level::try_detect().unwrap_or(Level::baseline()),
1317 VIEW_DIM,
1318 VIEW_DIM,
1319 )
1320 }
1321
1322 impl Tiles {
1323 fn assert_tiles_match(
1324 &mut self,
1325 lines: &[Line],
1326 width: u16,
1327 height: u16,
1328 expected: &[Tile],
1329 ) {
1330 self.make_tiles_msaa(lines, width, height);
1331 assert_eq!(self.tile_buf, expected, "MSAA: Tile buffer mismatch");
1332
1333 self.make_tiles_analytic_aa(Level::baseline(), lines, width, height);
1334 check_analytic_aa_matches(&self.tile_buf, expected);
1335 }
1336 }
1337
1338 fn check_analytic_aa_matches(actual: &[Tile], expected: &[Tile]) {
1339 assert_eq!(
1340 actual.len(),
1341 expected.len(),
1342 "Analytic AA: Tile count mismatch."
1343 );
1344
1345 for (i, (got, want)) in actual.iter().zip(expected.iter()).enumerate() {
1346 assert_eq!(got.x, want.x, "Analytic AA: Tile[{}] X mismatch", i);
1347 assert_eq!(got.y, want.y, "Analytic AA: Tile[{}] Y mismatch", i);
1348 assert_eq!(
1349 got.line_idx(),
1350 want.line_idx(),
1351 "Analytic AA: Tile[{}] Line Index mismatch",
1352 i
1353 );
1354
1355 let got_winding = got.packed_winding_line_idx & W;
1356 let want_winding = want.packed_winding_line_idx & W;
1357 assert_eq!(
1358 got_winding, want_winding,
1359 "Analytic AA: Tile[{}] Winding mismatch",
1360 i
1361 );
1362 }
1363 }
1364
1365 #[test]
1369 fn cull_sloped_outside_lines() {
1370 let lines = [
1371 Line {
1372 p0: Point { x: 1.0, y: -7.0 },
1373 p1: Point { x: 3.0, y: -1.0 },
1374 },
1375 Line {
1376 p0: Point { x: 1.0, y: -11.0 },
1377 p1: Point { x: 3.0, y: -1.0 },
1378 },
1379 Line {
1380 p0: Point {
1381 x: F_V_DIM + 1.0,
1382 y: 50.0,
1383 },
1384 p1: Point {
1385 x: F_V_DIM + 3.0,
1386 y: 70.0,
1387 },
1388 },
1389 Line {
1390 p0: Point {
1391 x: 1.0,
1392 y: F_V_DIM + 1.0,
1393 },
1394 p1: Point {
1395 x: 3.0,
1396 y: F_V_DIM + 7.0,
1397 },
1398 },
1399 Line {
1400 p0: Point {
1401 x: 1.0,
1402 y: F_V_DIM + 1.0,
1403 },
1404 p1: Point {
1405 x: 3.0,
1406 y: F_V_DIM + 13.0,
1407 },
1408 },
1409 ];
1410
1411 let mut tiles = new_tiles();
1412 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1413 }
1414
1415 #[test]
1416 fn sloped_line_crossing_top() {
1417 let lines = [
1418 Line {
1419 p0: Point { x: -2.0, y: -3.0 },
1420 p1: Point { x: 2.0, y: 1.0 },
1421 },
1422 Line {
1423 p0: Point { x: 6.0, y: -1.0 },
1424 p1: Point { x: 5.0, y: 2.0 },
1425 },
1426 Line {
1427 p0: Point { x: 9.0, y: -10.0 },
1428 p1: Point { x: 10.0, y: 3.0 },
1429 },
1430 Line {
1431 p0: Point { x: 2.0, y: 1.0 },
1432 p1: Point { x: -2.0, y: -3.0 },
1433 },
1434 ];
1435
1436 let mut tiles = new_tiles();
1437 let expected = [
1438 Tile::new(0, 0, 0, W | T),
1439 Tile::new(1, 0, 1, W | T),
1440 Tile::new(2, 0, 2, W | T),
1441 Tile::new(0, 0, 3, W | T),
1442 ];
1443
1444 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1445 }
1446
1447 #[test]
1448 fn sloped_line_crossing_bot() {
1449 let lines = [
1450 Line {
1451 p0: Point {
1452 x: 5.0,
1453 y: F_V_DIM + 3.0,
1454 },
1455 p1: Point {
1456 x: 6.0,
1457 y: F_V_DIM - 2.0,
1458 },
1459 },
1460 Line {
1461 p0: Point {
1462 x: 10.0,
1463 y: F_V_DIM + 1.0,
1464 },
1465 p1: Point {
1466 x: 9.0,
1467 y: F_V_DIM - 1.0,
1468 },
1469 },
1470 Line {
1471 p0: Point {
1472 x: 2.0,
1473 y: F_V_DIM - 2.0,
1474 },
1475 p1: Point {
1476 x: 3.0,
1477 y: F_V_DIM + 3.0,
1478 },
1479 },
1480 ];
1481
1482 let mut tiles = new_tiles();
1483 let expected = [
1484 Tile::new(1, 24, 0, B),
1485 Tile::new(2, 24, 1, B),
1486 Tile::new(0, 24, 2, B),
1487 ];
1488
1489 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1490 }
1491
1492 #[test]
1493 fn sloped_line_crossing_top_multi_tile() {
1494 let lines = [
1495 Line {
1496 p0: Point { x: 1.0, y: -5.0 },
1497 p1: Point { x: 6.0, y: 7.0 },
1498 },
1499 Line {
1500 p0: Point { x: 2.5, y: -10.0 },
1501 p1: Point { x: 3.5, y: 6.0 },
1502 },
1503 ];
1504
1505 let mut tiles = new_tiles();
1506 let expected = [
1507 Tile::new(0, 0, 0, W | T | R),
1508 Tile::new(1, 0, 0, L | B),
1509 Tile::new(1, 1, 0, W | T),
1510 Tile::new(0, 0, 1, W | T | B),
1511 Tile::new(0, 1, 1, W | T),
1512 ];
1513
1514 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1515 }
1516
1517 #[test]
1518 fn sloped_line_crossing_bot_multi_tile() {
1519 let lines = [
1520 Line {
1521 p0: Point {
1522 x: 12.0,
1523 y: F_V_DIM + 10.0,
1524 },
1525 p1: Point { x: 2.0, y: 94.0 },
1526 },
1527 Line {
1528 p0: Point {
1529 x: 1.5,
1530 y: F_V_DIM + 5.0,
1531 },
1532 p1: Point { x: 3.5, y: 94.0 },
1533 },
1534 ];
1535
1536 let mut tiles = new_tiles();
1537 let expected = [
1538 Tile::new(0, 23, 0, B),
1539 Tile::new(0, 24, 0, W | T | R),
1540 Tile::new(1, 24, 0, B | L),
1541 Tile::new(0, 23, 1, B),
1542 Tile::new(0, 24, 1, W | T | B),
1543 ];
1544
1545 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1546 }
1547
1548 #[test]
1549 fn sloped_line_crossing_right() {
1550 let lines = [
1551 Line {
1552 p0: Point { x: 97.0, y: 1.0 },
1553 p1: Point {
1554 x: F_V_DIM + 1.0,
1555 y: 2.0,
1556 },
1557 },
1558 Line {
1559 p0: Point { x: 93.0, y: 1.0 },
1560 p1: Point {
1561 x: F_V_DIM + 5.0,
1562 y: 2.0,
1563 },
1564 },
1565 ];
1566
1567 let mut tiles = new_tiles();
1568 let expected = [
1569 Tile::new(24, 0, 0, R),
1570 Tile::new(23, 0, 1, R),
1571 Tile::new(24, 0, 1, R | L),
1572 ];
1573
1574 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1575 }
1576
1577 #[test]
1578 fn sloped_line_crossing_left() {
1579 let lines = [
1580 Line {
1581 p0: Point { x: -5.0, y: 1.0 },
1582 p1: Point { x: 1.0, y: 2.0 },
1583 },
1584 Line {
1585 p0: Point { x: -5.0, y: 1.0 },
1586 p1: Point { x: 5.0, y: 2.0 },
1587 },
1588 Line {
1589 p0: Point { x: -5.0, y: 1.0 },
1590 p1: Point { x: 13.0, y: 9.0 },
1591 },
1592 ];
1593
1594 let mut tiles = new_tiles();
1595 let expected = [
1596 Tile::new(0, 0, 0, L),
1597 Tile::new(0, 0, 1, L | R),
1598 Tile::new(1, 0, 1, L),
1599 Tile::new(0, 0, 2, L | B),
1600 Tile::new(0, 1, 2, W | R | T),
1601 Tile::new(1, 1, 2, R | L),
1602 Tile::new(2, 1, 2, L | B),
1603 Tile::new(2, 2, 2, W | R | T),
1604 Tile::new(3, 2, 2, L),
1605 ];
1606
1607 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1608 }
1609
1610 #[test]
1611 fn horizontal_line_above_viewport() {
1612 let lines = [Line {
1613 p0: Point { x: 10.0, y: -5.0 },
1614 p1: Point { x: 90.0, y: -5.0 },
1615 }];
1616
1617 let mut tiles = new_tiles();
1618 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1619 }
1620
1621 #[test]
1622 fn horizontal_line_below_viewport() {
1623 let lines = [Line {
1624 p0: Point {
1625 x: 10.0,
1626 y: F_V_DIM + 5.0,
1627 },
1628 p1: Point {
1629 x: 90.0,
1630 y: F_V_DIM + 5.0,
1631 },
1632 }];
1633
1634 let mut tiles = new_tiles();
1635 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1636 }
1637
1638 #[test]
1639 fn horizontal_line_crossing_left_viewport() {
1640 let lines = [Line {
1641 p0: Point { x: -10.0, y: 10.0 },
1642 p1: Point { x: 10.0, y: 10.0 },
1643 }];
1644
1645 let mut tiles = new_tiles();
1646 let expected = [
1647 Tile::new(0, 2, 0, L | R),
1648 Tile::new(1, 2, 0, L | R),
1649 Tile::new(2, 2, 0, L),
1650 ];
1651
1652 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1653 }
1654
1655 #[test]
1656 fn horizontal_line_crossing_right_viewport() {
1657 let lines = [Line {
1658 p0: Point {
1659 x: F_V_DIM - 5.0,
1660 y: 10.0,
1661 },
1662 p1: Point {
1663 x: F_V_DIM + 5.0,
1664 y: 10.0,
1665 },
1666 }];
1667
1668 let mut tiles = new_tiles();
1669 let expected = [Tile::new(23, 2, 0, R), Tile::new(24, 2, 0, L | R)];
1670
1671 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1672 }
1673
1674 #[test]
1675 fn vertical_lines_outside_viewport() {
1676 let lines = [
1677 Line {
1678 p0: Point { x: 1.0, y: -5.0 },
1679 p1: Point { x: 1.0, y: -1.0 },
1680 },
1681 Line {
1682 p0: Point {
1683 x: 1.0,
1684 y: F_V_DIM + 1.0,
1685 },
1686 p1: Point {
1687 x: 1.0,
1688 y: F_V_DIM + 5.0,
1689 },
1690 },
1691 ];
1692
1693 let mut tiles = new_tiles();
1694 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1695 }
1696
1697 #[test]
1698 fn vertical_path_on_the_right_of_viewport() {
1699 const VIEWPORT_WIDTH: u16 = 10;
1700 const VIEWPORT_HEIGHT: u16 = 10;
1701
1702 let path = BezPath::from_svg("M261,0 L78848,0 L78848,4 L261,4 Z").unwrap();
1703 let mut line_buf: Vec<Line> = Vec::new();
1704 fill(
1705 Level::try_detect().unwrap_or(Level::baseline()),
1706 &path,
1707 Affine::IDENTITY,
1708 &mut line_buf,
1709 &mut FlattenCtx::default(),
1710 RectU16::new(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT),
1711 );
1712
1713 let mut tiles = new_tiles();
1714 tiles.assert_tiles_match(&line_buf, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, &[]);
1715 }
1716
1717 #[test]
1718 fn vertical_line_crossing_top_viewport() {
1719 let lines = [
1720 Line {
1721 p0: Point { x: 1.0, y: -7.0 },
1722 p1: Point { x: 1.0, y: 3.0 },
1723 },
1724 Line {
1725 p0: Point { x: 1.0, y: -7.0 },
1726 p1: Point { x: 1.0, y: 7.0 },
1727 },
1728 Line {
1729 p0: Point { x: 1.0, y: -7.0 },
1730 p1: Point { x: 1.0, y: 8.0 },
1731 },
1732 ];
1733
1734 let mut tiles = new_tiles();
1735 let expected = [
1736 Tile::new(0, 0, 0, W | T),
1737 Tile::new(0, 0, 1, W | B | T),
1738 Tile::new(0, 1, 1, W | T),
1739 Tile::new(0, 0, 2, W | B | T),
1740 Tile::new(0, 1, 2, W | T),
1741 ];
1742
1743 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1744 }
1745
1746 #[test]
1747 fn vertical_line_crossing_bot_viewport() {
1748 let lines = [
1749 Line {
1750 p0: Point {
1751 x: 1.0,
1752 y: F_V_DIM - 1.0,
1753 },
1754 p1: Point {
1755 x: 1.0,
1756 y: F_V_DIM + 5.0,
1757 },
1758 },
1759 Line {
1760 p0: Point {
1761 x: 1.0,
1762 y: F_V_DIM - 5.0,
1763 },
1764 p1: Point {
1765 x: 1.0,
1766 y: F_V_DIM + 5.0,
1767 },
1768 },
1769 ];
1770
1771 let mut tiles = new_tiles();
1772 let expected = [
1773 Tile::new(0, 24, 0, B),
1774 Tile::new(0, 23, 1, B),
1775 Tile::new(0, 24, 1, W | T | B),
1776 ];
1777
1778 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1779 }
1780
1781 #[test]
1782 fn clip_top_left_corner() {
1783 let lines = [Line {
1784 p0: Point { x: -1.0, y: 2.0 },
1785 p1: Point { x: 2.0, y: -1.0 },
1786 }];
1787
1788 let mut tiles = new_tiles();
1789 let expected = [Tile::new(0, 0, 0, W | L | T)];
1790
1791 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1792 }
1793
1794 #[test]
1795 fn clip_bottom_right_corner() {
1796 let lines = [Line {
1797 p0: Point {
1798 x: F_V_DIM + 1.0,
1799 y: F_V_DIM - 2.0,
1800 },
1801 p1: Point {
1802 x: F_V_DIM - 2.0,
1803 y: F_V_DIM + 1.0,
1804 },
1805 }];
1806
1807 let mut tiles = new_tiles();
1808 let expected = [Tile::new(24, 24, 0, R | B)];
1809
1810 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1811 }
1812
1813 #[test]
1817 fn horizontal_line_left_to_right_three_tile() {
1818 let lines = [Line {
1819 p0: Point { x: 1.5, y: 1.0 },
1820 p1: Point { x: 8.5, y: 1.0 },
1821 }];
1822
1823 let mut tiles = new_tiles();
1824 let expected = [
1825 Tile::new(0, 0, 0, R),
1826 Tile::new(1, 0, 0, R | L),
1827 Tile::new(2, 0, 0, L),
1828 ];
1829
1830 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1831 }
1832
1833 #[test]
1834 fn resize_works_correctly() {
1835 let lines = [
1836 Line {
1837 p0: Point { x: 1.5, y: 1.0 },
1838 p1: Point { x: 8.5, y: 1.0 },
1839 },
1840 Line {
1841 p0: Point { x: 1.5, y: 13.0 },
1842 p1: Point { x: 8.5, y: 13.0 },
1843 },
1844 ];
1845 let small_expected = [
1846 Tile::new(0, 0, 0, R),
1847 Tile::new(1, 0, 0, R | L),
1848 Tile::new(2, 0, 0, L),
1849 ];
1850 let large_expected = [
1851 Tile::new(0, 0, 0, R),
1852 Tile::new(1, 0, 0, R | L),
1853 Tile::new(2, 0, 0, L),
1854 Tile::new(0, 3, 1, R),
1855 Tile::new(1, 3, 1, R | L),
1856 Tile::new(2, 3, 1, L),
1857 ];
1858
1859 let mut tiles = Tiles::new(Level::baseline(), 12, 8);
1860 tiles.assert_tiles_match(&lines, 12, 8, &small_expected);
1861 tiles.assert_tiles_match(&lines, 12, 16, &large_expected);
1862 tiles.assert_tiles_match(&lines, 12, 8, &small_expected);
1863 }
1864
1865 #[test]
1866 fn horizontal_line_right_to_left_three_tile() {
1867 let lines = [Line {
1868 p0: Point { x: 8.5, y: 1.0 },
1869 p1: Point { x: 1.5, y: 1.0 },
1870 }];
1871
1872 let mut tiles = new_tiles();
1873 let expected = [
1874 Tile::new(0, 0, 0, R),
1875 Tile::new(1, 0, 0, R | L),
1876 Tile::new(2, 0, 0, L),
1877 ];
1878
1879 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1880 }
1881
1882 #[test]
1883 fn horizontal_line_multi_tile() {
1884 let lines = [Line {
1885 p0: Point { x: 1.5, y: 1.0 },
1886 p1: Point { x: 12.5, y: 1.0 },
1887 }];
1888
1889 let mut tiles = new_tiles();
1890 let expected = [
1891 Tile::new(0, 0, 0, R),
1892 Tile::new(1, 0, 0, R | L),
1893 Tile::new(2, 0, 0, R | L),
1894 Tile::new(3, 0, 0, L),
1895 ];
1896
1897 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1898 }
1899
1900 #[test]
1901 fn vertical_line_down_three_tile() {
1902 let lines = [Line {
1903 p0: Point { x: 1.0, y: 1.5 },
1904 p1: Point { x: 1.0, y: 8.5 },
1905 }];
1906
1907 let mut tiles = new_tiles();
1908 let expected = [
1909 Tile::new(0, 0, 0, B),
1910 Tile::new(0, 1, 0, W | T | B),
1911 Tile::new(0, 2, 0, W | T),
1912 ];
1913
1914 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1915 }
1916
1917 #[test]
1918 fn vertical_line_down_multi_tile() {
1919 let lines = [Line {
1920 p0: Point { x: 1.0, y: 1.0 },
1921 p1: Point { x: 1.0, y: 13.0 },
1922 }];
1923
1924 let mut tiles = new_tiles();
1925 let expected = [
1926 Tile::new(0, 0, 0, B),
1927 Tile::new(0, 1, 0, W | T | B),
1928 Tile::new(0, 2, 0, W | T | B),
1929 Tile::new(0, 3, 0, W | T),
1930 ];
1931
1932 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1933 }
1934
1935 #[test]
1936 fn vertical_line_up_three_tile() {
1937 let lines = [Line {
1938 p0: Point { x: 1.0, y: 13.0 },
1939 p1: Point { x: 1.0, y: 1.0 },
1940 }];
1941
1942 let mut tiles = new_tiles();
1943 let expected = [
1944 Tile::new(0, 0, 0, B),
1945 Tile::new(0, 1, 0, W | T | B),
1946 Tile::new(0, 2, 0, W | T | B),
1947 Tile::new(0, 3, 0, W | T),
1948 ];
1949
1950 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1951 }
1952
1953 #[test]
1954 fn vertical_line_up_multi_tile() {
1955 let lines = [Line {
1956 p0: Point { x: 1.0, y: 8.5 },
1957 p1: Point { x: 1.0, y: 1.5 },
1958 }];
1959
1960 let mut tiles = new_tiles();
1961 let expected = [
1962 Tile::new(0, 0, 0, B),
1963 Tile::new(0, 1, 0, W | T | B),
1964 Tile::new(0, 2, 0, W | T),
1965 ];
1966
1967 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1968 }
1969
1970 #[test]
1972 fn vertical_line_touching_bot() {
1973 let lines = [Line {
1974 p0: Point { x: 1.0, y: 1.0 },
1975 p1: Point { x: 1.0, y: 8.0 },
1976 }];
1977
1978 let mut tiles = new_tiles();
1979 let expected = [Tile::new(0, 0, 0, B), Tile::new(0, 1, 0, W | T)];
1980
1981 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1982 }
1983
1984 #[test]
1985 fn vertical_line_touching_top() {
1986 let lines = [Line {
1987 p0: Point { x: 1.0, y: 0.0 },
1988 p1: Point { x: 1.0, y: 7.0 },
1989 }];
1990
1991 let mut tiles = new_tiles();
1992 let expected = [Tile::new(0, 0, 0, W | B), Tile::new(0, 1, 0, W | T)];
1993
1994 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1995 }
1996
1997 #[test]
2001 fn top_left_to_bottom_right() {
2002 let lines = [Line {
2003 p0: Point { x: 1.0, y: 1.0 },
2004 p1: Point { x: 11.0, y: 9.0 },
2005 }];
2006
2007 let mut tiles = new_tiles();
2008 let expected = [
2009 Tile::new(0, 0, 0, R),
2010 Tile::new(1, 0, 0, L | B),
2011 Tile::new(1, 1, 0, W | R | T),
2012 Tile::new(2, 1, 0, L | B),
2013 Tile::new(2, 2, 0, W | T),
2014 ];
2015
2016 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2017 }
2018
2019 #[test]
2020 fn bottom_right_to_top_left() {
2021 let lines = [Line {
2022 p0: Point { x: 11.0, y: 9.0 },
2023 p1: Point { x: 1.0, y: 1.0 },
2024 }];
2025
2026 let mut tiles = new_tiles();
2027 let expected = [
2028 Tile::new(0, 0, 0, R),
2029 Tile::new(1, 0, 0, L | B),
2030 Tile::new(1, 1, 0, W | R | T),
2031 Tile::new(2, 1, 0, L | B),
2032 Tile::new(2, 2, 0, W | T),
2033 ];
2034
2035 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2036 }
2037
2038 #[test]
2039 fn bottom_left_to_top_right() {
2040 let lines = [Line {
2041 p0: Point { x: 2.0, y: 11.0 },
2042 p1: Point { x: 14.0, y: 6.0 },
2043 }];
2044
2045 let mut tiles = new_tiles();
2046 let expected = [
2047 Tile::new(2, 1, 0, R | B),
2048 Tile::new(3, 1, 0, L),
2049 Tile::new(0, 2, 0, R),
2050 Tile::new(1, 2, 0, R | L),
2051 Tile::new(2, 2, 0, W | L | T),
2052 ];
2053
2054 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2055 }
2056
2057 #[test]
2058 fn top_right_to_bottom_left() {
2059 let lines = [Line {
2060 p0: Point { x: 14.0, y: 6.0 },
2061 p1: Point { x: 2.0, y: 11.0 },
2062 }];
2063
2064 let mut tiles = new_tiles();
2065 let expected = [
2066 Tile::new(2, 1, 0, R | B),
2067 Tile::new(3, 1, 0, L),
2068 Tile::new(0, 2, 0, R),
2069 Tile::new(1, 2, 0, R | L),
2070 Tile::new(2, 2, 0, W | L | T),
2071 ];
2072
2073 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2074 }
2075
2076 #[test]
2077 fn two_lines_in_single_tile() {
2078 let lines = [
2079 Line {
2080 p0: Point { x: 1.0, y: 3.0 },
2081 p1: Point { x: 3.0, y: 3.0 },
2082 },
2083 Line {
2084 p0: Point { x: 3.0, y: 3.0 },
2085 p1: Point { x: 0.0, y: 1.0 },
2086 },
2087 ];
2088
2089 let mut tiles = new_tiles();
2090 let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
2091
2092 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2093 }
2094
2095 #[test]
2096 fn diagonal_cross_corner() {
2097 let lines = [Line {
2098 p0: Point { x: 3.0, y: 5.0 },
2099 p1: Point { x: 5.0, y: 3.0 },
2100 }];
2101
2102 let mut tiles = new_tiles();
2103 let expected = [
2104 Tile::new(1, 0, 0, L),
2105 Tile::new(0, 1, 0, R),
2106 Tile::new(1, 1, 0, W | L | T),
2107 ];
2108
2109 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2110 }
2111
2112 #[test]
2113 fn diagonal_cross_corner_two() {
2114 let lines = [Line {
2115 p0: Point { x: 7.9, y: 7.9 },
2116 p1: Point { x: 0.1, y: 0.1 },
2117 }];
2118
2119 let mut tiles = new_tiles();
2120 let expected = [
2121 Tile::new(0, 0, 0, R),
2122 Tile::new(1, 0, 0, L),
2123 Tile::new(1, 1, 0, W | T),
2124 ];
2125
2126 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2127 }
2128
2129 #[test]
2130 fn diagonal_down_slope_tiles() {
2131 let lines = [Line {
2132 p0: Point { x: 5.0, y: 5.0 },
2133 p1: Point { x: 9.0, y: 9.0 },
2134 }];
2135
2136 let mut tiles = new_tiles();
2137 let expected = [
2138 Tile::new(1, 1, 0, R),
2139 Tile::new(2, 1, 0, L),
2140 Tile::new(2, 2, 0, W | T),
2141 ];
2142
2143 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2144 }
2145
2146 #[test]
2147 fn diagonal_up_slope_tiles() {
2148 let lines = [Line {
2149 p0: Point { x: 5.0, y: 9.0 },
2150 p1: Point { x: 9.0, y: 5.0 },
2151 }];
2152
2153 let mut tiles = new_tiles();
2154 let expected = [
2155 Tile::new(1, 1, 0, R | B),
2156 Tile::new(2, 1, 0, L),
2157 Tile::new(1, 2, 0, W | T),
2158 ];
2159
2160 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2161 }
2162
2163 #[test]
2164 fn diagonal_down_one_tile() {
2165 let lines = [Line {
2166 p0: Point { x: 0.0, y: 0.0 },
2167 p1: Point { x: 4.0, y: 4.0 },
2168 }];
2169
2170 let mut tiles = new_tiles();
2171 let expected = [Tile::new(0, 0, 0, W | R), Tile::new(1, 0, 0, L)];
2172
2173 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2174 }
2175
2176 #[test]
2177 fn diagonal_up_one_tile() {
2178 let lines = [Line {
2179 p0: Point { x: 0.0, y: 4.0 },
2180 p1: Point { x: 4.0, y: 0.0 },
2181 }];
2182
2183 let mut tiles = new_tiles();
2184 let expected = [Tile::new(0, 0, 0, R), Tile::new(1, 0, 0, W | L)];
2185
2186 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2187 }
2188
2189 #[test]
2190 fn diagonal_down_two_tile() {
2191 let lines = [Line {
2192 p0: Point { x: 0.0, y: 0.0 },
2193 p1: Point { x: 8.0, y: 8.0 },
2194 }];
2195
2196 let mut tiles = new_tiles();
2197 let expected = [
2198 Tile::new(0, 0, 0, W | R),
2199 Tile::new(1, 0, 0, L),
2200 Tile::new(1, 1, 0, W | R | T),
2201 Tile::new(2, 1, 0, L),
2202 ];
2203
2204 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2205 }
2206
2207 #[test]
2208 fn diagonal_up_two_tile() {
2209 let lines = [Line {
2210 p0: Point { x: 0.0, y: 8.0 },
2211 p1: Point { x: 8.0, y: 0.0 },
2212 }];
2213
2214 let mut tiles = new_tiles();
2215 let expected = [
2216 Tile::new(1, 0, 0, R | L),
2217 Tile::new(2, 0, 0, W | L),
2218 Tile::new(0, 1, 0, R),
2219 Tile::new(1, 1, 0, W | L | T),
2220 ];
2221
2222 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2223 }
2224
2225 #[test]
2226 fn sloped_ending_right() {
2227 let lines = [Line {
2228 p0: Point { x: 1.0, y: 1.0 },
2229 p1: Point { x: 8.0, y: 2.0 },
2230 }];
2231
2232 let mut tiles = new_tiles();
2233 let expected = [
2234 Tile::new(0, 0, 0, R),
2235 Tile::new(1, 0, 0, R | L),
2236 Tile::new(2, 0, 0, L),
2237 ];
2238
2239 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2240 }
2241
2242 #[test]
2246 fn issue_early_winding_emission() {
2247 const WIDTH: u16 = Tile::WIDTH * 35;
2248 const HEIGHT: u16 = Tile::HEIGHT * 7;
2249
2250 let tile_width = f32::from(Tile::WIDTH);
2251 let tile_height = f32::from(Tile::HEIGHT);
2252 let lines = [Line {
2253 p0: Point {
2254 x: 32.89 * tile_width,
2255 y: 0.9 * tile_height,
2256 },
2257 p1: Point {
2258 x: 33.5 * tile_width,
2259 y: 7.0 * tile_height,
2260 },
2261 }];
2262
2263 let mut tiles = Tiles::new(Level::baseline(), HEIGHT, HEIGHT);
2264 tiles.make_tiles_analytic_aa(Level::baseline(), &lines, WIDTH, HEIGHT);
2265
2266 let row_tiles: Vec<Tile> = tiles
2267 .tile_buf
2268 .iter()
2269 .copied()
2270 .filter(|tile| tile.y == 2)
2271 .collect();
2272
2273 assert_eq!(row_tiles, [Tile::new(33, 2, 0, W)]);
2276 }
2277
2278 #[test]
2279 fn sloped_touching_top() {
2280 let lines = [Line {
2281 p0: Point { x: 0.0, y: 8.0 },
2282 p1: Point { x: 4.0, y: 0.0 },
2283 }];
2284
2285 let mut tiles = new_tiles();
2286 let expected = [
2287 Tile::new(0, 0, 0, R | B),
2288 Tile::new(1, 0, 0, W | L),
2289 Tile::new(0, 1, 0, W | T),
2290 ];
2291
2292 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2293 }
2294
2295 #[test]
2296 fn sloped_touching_bot() {
2297 let lines = [Line {
2298 p0: Point { x: 0.0, y: 0.0 },
2299 p1: Point { x: 4.0, y: 8.0 },
2300 }];
2301
2302 let mut tiles = new_tiles();
2303 let expected = [
2304 Tile::new(0, 0, 0, W | B),
2305 Tile::new(0, 1, 0, W | R | T),
2306 Tile::new(1, 1, 0, L),
2307 ];
2308
2309 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2310 }
2311
2312 #[test]
2316 fn same_tile() {
2317 let lines = [Line {
2318 p0: Point { x: 1.0, y: 1.0 },
2319 p1: Point { x: 3.0, y: 3.0 },
2320 }];
2321
2322 let mut tiles = new_tiles();
2323 let expected = [Tile::new(0, 0, 0, 0)];
2324
2325 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2326 }
2327
2328 #[test]
2329 fn same_tile_left() {
2330 let lines = [Line {
2331 p0: Point { x: 0.0, y: 1.0 },
2332 p1: Point { x: 3.0, y: 1.0 },
2333 }];
2334
2335 let mut tiles = new_tiles();
2336 let expected = [Tile::new(0, 0, 0, 0)];
2337
2338 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2339 }
2340
2341 #[test]
2342 fn same_tile_top() {
2343 let lines = [Line {
2344 p0: Point { x: 1.0, y: 0.0 },
2345 p1: Point { x: 1.0, y: 3.0 },
2346 }];
2347
2348 let mut tiles = new_tiles();
2349 let expected = [Tile::new(0, 0, 0, W)];
2350
2351 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2352 }
2353
2354 #[test]
2355 fn same_tile_right() {
2356 let lines = [Line {
2357 p0: Point { x: 1.0, y: 1.0 },
2358 p1: Point { x: 4.0, y: 1.0 },
2359 }];
2360
2361 let mut tiles = new_tiles();
2362 let expected = [Tile::new(0, 0, 0, R), Tile::new(1, 0, 0, L)];
2363
2364 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2365 }
2366
2367 #[test]
2368 fn same_tile_bottom() {
2369 let lines = [
2370 Line {
2371 p0: Point { x: 1.0, y: 1.0 },
2372 p1: Point { x: 1.0, y: 4.0 },
2373 },
2374 Line {
2375 p0: Point { x: 1.0, y: 1.0 },
2376 p1: Point { x: 2.0, y: 4.0 },
2377 },
2378 ];
2379
2380 let mut tiles = new_tiles();
2381 let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
2382
2383 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2384 }
2385
2386 #[test]
2387 fn same_tile_top_left() {
2388 let lines = [
2389 Line {
2390 p0: Point { x: 0.0, y: 1.0 },
2391 p1: Point { x: 1.0, y: 0.0 },
2392 },
2393 Line {
2394 p0: Point { x: 0.0, y: 0.0001 },
2395 p1: Point { x: 0.0001, y: 0.0 },
2396 },
2397 ];
2398
2399 let mut tiles = new_tiles();
2400 let expected = [Tile::new(0, 0, 0, W), Tile::new(0, 0, 1, W)];
2401
2402 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
2403 }
2404
2405 #[test]
2409 fn test_culled_windings_new_and_reset() {
2410 let mut windings = CulledWindings::new(8);
2411 assert_eq!(windings.partial.len(), 2);
2412 assert_eq!(windings.coarse.len(), 2);
2413 assert_eq!(windings.active.len(), 1);
2414
2415 windings.coarse[0] = 1;
2416 windings.active[0] = 0xFF;
2417 windings.culled = true;
2418
2419 windings.reset(8);
2420 assert_eq!(windings.coarse[0], 0);
2421 assert_eq!(windings.active[0], 0);
2422
2423 windings.coarse[0] = 1;
2424 windings.active[0] = 0xFF;
2425 windings.culled = false;
2426
2427 windings.reset(8);
2428 assert_eq!(windings.coarse[0], 1);
2429 assert_eq!(windings.active[0], 0xFF);
2430
2431 windings.culled = true;
2432 windings.reset(12);
2433 assert_eq!(windings.coarse[0], 0);
2434 assert_eq!(windings.active[0], 0);
2435 }
2436
2437 #[test]
2438 fn test_mark_row_active() {
2439 let mut windings = CulledWindings::new(200);
2440 windings.mark_row_active(0);
2441 windings.mark_row_active(5);
2442 windings.mark_row_active(31);
2443 windings.mark_row_active(32);
2444 assert_eq!(windings.active[0], (1 << 0) | (1 << 5) | (1 << 31));
2445 assert_eq!(windings.active[1], 1 << 0);
2446 }
2447
2448 #[test]
2449 fn test_mark_row_range_single_word() {
2450 let mut windings = CulledWindings::new(200);
2451 windings.mark_row_range_active(5, 10);
2452 let expected_mask = ((1_u32 << 5) - 1) << 5;
2453 assert_eq!(windings.active[0], expected_mask);
2454 assert_eq!(windings.active[1], 0);
2455 }
2456
2457 #[test]
2458 fn test_mark_row_range_full_word() {
2459 let mut windings = CulledWindings::new(200);
2460 windings.mark_row_range_active(0, 32);
2461 assert_eq!(windings.active[0], u32::MAX);
2462 assert_eq!(windings.active[1], 0);
2463 }
2464
2465 #[test]
2466 fn test_mark_row_range_spanning_two_words() {
2467 let mut windings = CulledWindings::new(200);
2468 windings.mark_row_range_active(30, 35);
2469 assert_eq!(windings.active[0], (1 << 30) | (1 << 31));
2470 assert_eq!(windings.active[1], (1 << 0) | (1 << 1) | (1 << 2));
2471 }
2472
2473 #[test]
2474 fn test_mark_row_range_spanning_multiple_words() {
2475 let mut windings = CulledWindings::new(500);
2476 windings.mark_row_range_active(10, 80);
2477 assert_eq!(windings.active[0], u32::MAX << 10);
2478 assert_eq!(windings.active[1], u32::MAX);
2479 assert_eq!(windings.active[2], (1 << 16) - 1);
2480 assert_eq!(windings.active[3], 0);
2481 }
2482
2483 #[test]
2484 fn test_mark_row_range_empty_or_invalid() {
2485 let mut windings = CulledWindings::new(200);
2486 windings.mark_row_range_active(10, 10);
2487 windings.mark_row_range_active(15, 10);
2488 assert_eq!(windings.active[0], 0);
2489 assert_eq!(windings.active[1], 0);
2490 }
2491
2492 #[test]
2496 fn infinite_loop() {
2498 let line = Line {
2499 p0: Point { x: 22.0, y: 552.0 },
2500 p1: Point { x: 224.0, y: 388.0 },
2501 };
2502
2503 let mut tiles = new_tiles();
2504 tiles.make_tiles_msaa(&[line], 600, 600);
2505 tiles.make_tiles_analytic_aa(Level::baseline(), &[line], 600, 600);
2506 }
2507
2508 #[test]
2509 fn overflow() {
2511 let line = Line {
2512 p0: Point {
2513 x: 59.60001,
2514 y: 40.78,
2515 },
2516 p1: Point {
2517 x: 520599.6,
2518 y: 100.18,
2519 },
2520 };
2521
2522 let mut tiles = new_tiles();
2523 tiles.make_tiles_analytic_aa(Level::baseline(), &[line], 200, 100);
2524 tiles.make_tiles_msaa(&[line], 200, 100);
2525 }
2526
2527 #[test]
2528 fn sort_test() {
2529 let mut lines: Vec<Line> = Vec::new();
2530 let mut tiles = Tiles::new(Level::baseline(), VIEW_DIM, VIEW_DIM);
2531
2532 let step = 4.0;
2533 let mut y = F_V_DIM - 10.0;
2534 while y > 10.0 {
2535 lines.push(Line {
2536 p0: Point {
2537 x: F_V_DIM - 10.0,
2538 y,
2539 },
2540 p1: Point { x: 10.0, y },
2541 });
2542
2543 lines.push(Line {
2544 p0: Point {
2545 x: F_V_DIM - 12.0,
2546 y,
2547 },
2548 p1: Point { x: 12.0, y },
2549 });
2550
2551 y -= step;
2552 }
2553
2554 tiles.make_tiles_msaa(&lines, VIEW_DIM, VIEW_DIM);
2555 assert!(tiles.tile_buf.first().unwrap().y > tiles.tile_buf.last().unwrap().y);
2556 tiles.sort_tiles();
2557 check_sorted(&tiles.tile_buf);
2558
2559 tiles.make_tiles_analytic_aa(Level::baseline(), &lines, VIEW_DIM, VIEW_DIM);
2560 assert!(tiles.tile_buf.first().unwrap().y > tiles.tile_buf.last().unwrap().y);
2561 tiles.sort_tiles();
2562 check_sorted(&tiles.tile_buf);
2563 }
2564
2565 fn check_sorted(buf: &[Tile]) {
2566 for i in 0..buf.len() - 1 {
2567 let current = buf[i];
2568 let next = buf[i + 1];
2569
2570 if current.y > next.y {
2571 panic!(
2572 "Sort Failure [Y]: Tile[{}] (y={}) > Tile[{}] (y={})",
2573 i,
2574 current.y,
2575 i + 1,
2576 next.y
2577 );
2578 }
2579
2580 if current.y == next.y {
2581 if current.x > next.x {
2582 panic!(
2583 "Sort Failure [X]: at Row y={}, Tile[{}] (x={}) > Tile[{}] (x={})",
2584 current.y,
2585 i,
2586 current.x,
2587 i + 1,
2588 next.x
2589 );
2590 }
2591
2592 if current.x == next.x
2593 && current.packed_winding_line_idx > next.packed_winding_line_idx
2594 {
2595 panic!(
2596 "Sort Failure [Payload]: at {}x{}, Tile[{}] (val={}) > Tile[{}] (val={})",
2597 current.x,
2598 current.y,
2599 i,
2600 current.packed_winding_line_idx,
2601 i + 1,
2602 next.packed_winding_line_idx
2603 );
2604 }
2605 }
2606 }
2607 }
2608}