1use crate::flatten::Line;
7use alloc::vec;
8use alloc::vec::Vec;
9use fearless_simd::Level;
10#[cfg(not(feature = "std"))]
11use peniko::kurbo::common::FloatFuncs as _;
12
13pub const MAX_LINES_PER_PATH: u32 = 1 << 26;
17
18#[derive(Debug, Clone, Copy)]
29#[repr(C)]
30pub struct Tile {
31 #[cfg(target_endian = "big")]
38 pub y: u16,
40
41 #[cfg(target_endian = "big")]
42 pub x: u16,
44
45 pub packed_winding_line_idx: u32,
62
63 #[cfg(target_endian = "little")]
64 pub x: u16,
66
67 #[cfg(target_endian = "little")]
68 pub y: u16,
70}
71
72impl Tile {
73 pub const WIDTH: u16 = 4;
75
76 pub const HEIGHT: u16 = 4;
78
79 #[inline]
84 pub fn new_clamped(x: u16, y: u16, line_idx: u32, intersection_mask: u32) -> Self {
85 Self::new(
86 x.min(u16::MAX / Self::WIDTH),
89 y.min(u16::MAX / Self::HEIGHT),
90 line_idx,
91 intersection_mask,
92 )
93 }
94
95 #[inline]
101 pub const fn new(x: u16, y: u16, line_idx: u32, intersection_mask: u32) -> Self {
102 #[cfg(debug_assertions)]
103 if line_idx >= MAX_LINES_PER_PATH {
104 panic!("Max. number of lines per path exceeded.");
105 }
106 Self {
109 x,
110 y,
111 packed_winding_line_idx: (line_idx << 6) | intersection_mask,
112 }
113 }
114
115 #[inline]
117 pub const fn same_loc(&self, other: &Self) -> bool {
118 self.same_row(other) && self.x == other.x
119 }
120
121 #[inline]
123 pub const fn prev_loc(&self, other: &Self) -> bool {
124 self.same_row(other) && self.x + 1 == other.x
125 }
126
127 #[inline]
129 pub const fn same_row(&self, other: &Self) -> bool {
130 self.y == other.y
131 }
132
133 #[inline]
137 pub const fn line_idx(&self) -> u32 {
138 self.packed_winding_line_idx >> 6
139 }
140
141 #[inline]
148 pub const fn winding(&self) -> bool {
149 (self.packed_winding_line_idx & (1 << 5)) != 0
150 }
151
152 #[inline]
162 pub const fn intersection_mask(&self) -> u32 {
163 self.packed_winding_line_idx & 0b111111
164 }
165
166 #[inline]
168 pub const fn intersects_top(&self) -> bool {
169 (self.intersection_mask() & 0b000001) != 0
170 }
171
172 #[inline]
174 pub const fn intersects_bottom(&self) -> bool {
175 (self.intersection_mask() & 0b000010) != 0
176 }
177
178 #[inline]
180 pub const fn intersects_left(&self) -> bool {
181 (self.intersection_mask() & 0b000100) != 0
182 }
183
184 #[inline]
186 pub const fn intersects_right(&self) -> bool {
187 (self.intersection_mask() & 0b001000) != 0
188 }
189
190 #[inline]
194 pub const fn is_perfect_corner(&self) -> bool {
195 (self.intersection_mask() & 0b010000) != 0
196 }
197
198 #[inline(always)]
203 const fn to_bits(self) -> u64 {
204 ((self.y as u64) << 48) | ((self.x as u64) << 32) | self.packed_winding_line_idx as u64
205 }
206}
207
208impl PartialEq for Tile {
209 #[inline(always)]
210 fn eq(&self, other: &Self) -> bool {
211 self.to_bits() == other.to_bits()
212 }
213}
214
215impl Ord for Tile {
216 #[inline(always)]
217 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
218 self.to_bits().cmp(&other.to_bits())
219 }
220}
221
222impl PartialOrd for Tile {
223 #[inline(always)]
224 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
225 Some(self.cmp(other))
226 }
227}
228
229impl Eq for Tile {}
230
231#[derive(Clone, Debug)]
233pub struct Tiles {
234 tile_buf: Vec<Tile>,
235 level: Level,
236 sorted: bool,
237}
238
239impl Tiles {
240 pub fn new(level: Level) -> Self {
242 Self {
243 tile_buf: vec![],
244 level,
245 sorted: false,
246 }
247 }
248
249 pub fn len(&self) -> u32 {
251 self.tile_buf.len() as u32
252 }
253
254 pub fn is_empty(&self) -> bool {
256 self.tile_buf.is_empty()
257 }
258
259 pub fn reset(&mut self) {
261 self.tile_buf.clear();
262 self.sorted = false;
263 }
264
265 pub fn sort_tiles(&mut self) {
267 self.sorted = true;
268 self.level.dispatch(|_| self.tile_buf.sort_unstable());
270 }
271
272 #[inline]
276 pub fn get(&self, index: u32) -> &Tile {
277 assert!(
278 self.sorted,
279 "attempted to call `get` before sorting the tile container."
280 );
281
282 &self.tile_buf[index as usize]
283 }
284
285 #[inline]
289 pub fn iter(&self) -> impl Iterator<Item = &Tile> {
290 assert!(
291 self.sorted,
292 "attempted to call `iter` before sorting the tile container."
293 );
294
295 self.tile_buf.iter()
296 }
297
298 pub fn make_tiles_analytic_aa(&mut self, lines: &[Line], width: u16, height: u16) {
307 self.reset();
308
309 if width == 0 || height == 0 {
310 return;
311 }
312
313 debug_assert!(
314 lines.len() <= MAX_LINES_PER_PATH as usize,
315 "Max. number of lines per path exceeded. Max is {}, got {}.",
316 MAX_LINES_PER_PATH,
317 lines.len()
318 );
319
320 let tile_columns = width.div_ceil(Tile::WIDTH);
321 let tile_rows = height.div_ceil(Tile::HEIGHT);
322
323 for (line_idx, line) in lines.iter().take(MAX_LINES_PER_PATH as usize).enumerate() {
324 let line_idx = line_idx as u32;
325
326 let p0_x = line.p0.x / f32::from(Tile::WIDTH);
327 let p0_y = line.p0.y / f32::from(Tile::HEIGHT);
328 let p1_x = line.p1.x / f32::from(Tile::WIDTH);
329 let p1_y = line.p1.y / f32::from(Tile::HEIGHT);
330
331 let (line_left_x, line_right_x) = if p0_x < p1_x {
332 (p0_x, p1_x)
333 } else {
334 (p1_x, p0_x)
335 };
336
337 if line_left_x > tile_columns as f32 {
339 continue;
340 }
341
342 let (line_top_y, line_top_x, line_bottom_y, line_bottom_x) = if p0_y < p1_y {
343 (p0_y, p0_x, p1_y, p1_x)
344 } else {
345 (p1_y, p1_x, p0_y, p0_x)
346 };
347
348 let y_top_tiles = (line_top_y as u16).min(tile_rows);
350 let line_bottom_y_ceil = line_bottom_y.ceil();
351 let y_bottom_tiles = (line_bottom_y_ceil as u16).min(tile_rows);
352
353 if y_top_tiles >= y_bottom_tiles {
357 continue;
360 }
361
362 let p0_tile_x = line_top_x.floor() as i32;
364 let p0_tile_y = line_top_y.floor() as i32;
365 let p1_tile_x = line_bottom_x.floor() as i32;
366 let p1_tile_y = line_bottom_y.floor() as i32;
367
368 let not_same_tile = p0_tile_y != p1_tile_y || p0_tile_x != p1_tile_x;
370 if not_same_tile {
371 if line_left_x == line_right_x {
373 let x = (line_left_x as u16).min(tile_columns.saturating_sub(1));
374
375 let is_start_culled = line_top_y < 0.0;
377 if !is_start_culled {
378 let winding = ((f32::from(y_top_tiles) >= line_top_y) as u32) << 5;
379 let tile = Tile::new_clamped(x, y_top_tiles, line_idx, winding);
380 self.tile_buf.push(tile);
381 }
382
383 let y_start = if is_start_culled {
386 y_top_tiles
387 } else {
388 y_top_tiles + 1
389 };
390 let line_bottom_floor = line_bottom_y.floor();
391 let y_end_idx = (line_bottom_floor as u16).min(tile_rows);
392
393 for y_idx in y_start..y_end_idx {
394 let tile = Tile::new_clamped(x, y_idx, line_idx, 0b100000);
395 self.tile_buf.push(tile);
396 }
397
398 if line_bottom_y != line_bottom_floor && y_end_idx < tile_rows {
402 let tile = Tile::new_clamped(x, y_end_idx, line_idx, 0b100000);
403 self.tile_buf.push(tile);
404 }
405 } else {
406 let dx = p1_x - p0_x;
407 let dy = p1_y - p0_y;
408 let x_slope = dx / dy;
409 let dx_dir = (line_bottom_x >= line_top_x) as u32;
410 let not_dx_dir = dx_dir ^ 1;
411
412 let w_start_base = dx_dir << 5;
413 let w_end_base = not_dx_dir << 5;
414
415 let mut push_row = |y_idx: u16,
416 row_top_y: f32,
417 row_bottom_y: f32,
418 w_start: u32,
419 w_end: u32,
420 w_single: u32| {
421 let row_top_x = p0_x + (row_top_y - p0_y) * x_slope;
422 let row_bottom_x = p0_x + (row_bottom_y - p0_y) * x_slope;
423
424 let row_left_x = f32::min(row_top_x, row_bottom_x).max(line_left_x);
425 let row_right_x = f32::max(row_top_x, row_bottom_x).min(line_right_x);
426
427 let x_start = row_left_x as u16;
428 let x_end = (row_right_x as u16).min(tile_columns.saturating_sub(1));
429
430 if x_start <= x_end {
431 let winding = if x_start == x_end { w_single } else { w_start };
432
433 self.tile_buf
434 .push(Tile::new(x_start, y_idx, line_idx, winding));
435
436 for x_idx in x_start.saturating_add(1)..x_end {
437 self.tile_buf.push(Tile::new(x_idx, y_idx, line_idx, 0));
438 }
439
440 if x_start < x_end {
441 self.tile_buf.push(Tile::new(x_end, y_idx, line_idx, w_end));
442 }
443 }
444 };
445
446 let is_start_culled = line_top_y < 0.0;
447 if !is_start_culled {
448 let y = f32::from(y_top_tiles);
449 let row_bottom_y = (y + 1.0).min(line_bottom_y);
450 let mask = ((y >= line_top_y) as u32) << 5;
451 push_row(
452 y_top_tiles,
453 line_top_y,
454 row_bottom_y,
455 w_start_base & mask,
456 w_end_base & mask,
457 0b100000 & mask,
458 );
459 }
460
461 let y_start_middle = if is_start_culled {
462 y_top_tiles
463 } else {
464 y_top_tiles + 1
465 };
466
467 let line_bottom_floor = line_bottom_y.floor();
468 let y_end_middle = (line_bottom_floor as u16).min(tile_rows);
469 for y_idx in y_start_middle..y_end_middle {
470 let y = f32::from(y_idx);
471 let row_bottom_y = (y + 1.0).min(line_bottom_y);
472 push_row(y_idx, y, row_bottom_y, w_start_base, w_end_base, 0b100000);
473 }
474
475 if line_bottom_y != line_bottom_floor
476 && y_end_middle < tile_rows
477 && (is_start_culled || y_end_middle != y_top_tiles)
478 {
479 let y_idx = y_end_middle;
480 let y = f32::from(y_idx);
481 push_row(y_idx, y, line_bottom_y, w_start_base, w_end_base, 0b100000);
482 }
483 }
484 } else {
485 let tile = Tile::new_clamped(
487 (line_left_x as u16).min(tile_columns + 1),
488 y_top_tiles,
489 line_idx,
490 ((f32::from(y_top_tiles) >= line_top_y) as u32) << 5,
491 );
492 self.tile_buf.push(tile);
493 }
494 }
495 }
496
497 pub fn make_tiles_msaa(&mut self, lines: &[Line], width: u16, height: u16) {
544 self.reset();
545
546 if width == 0 || height == 0 {
547 return;
548 }
549
550 debug_assert!(
551 lines.len() <= MAX_LINES_PER_PATH as usize,
552 "Max. number of lines per path exceeded. Max is {}, got {}.",
553 MAX_LINES_PER_PATH,
554 lines.len()
555 );
556
557 let tile_columns = width.div_ceil(Tile::WIDTH);
558 let tile_rows = height.div_ceil(Tile::HEIGHT);
559
560 for (line_idx, line) in lines.iter().take(MAX_LINES_PER_PATH as usize).enumerate() {
561 let line_idx = line_idx as u32;
562
563 let p0_x = line.p0.x / f32::from(Tile::WIDTH);
564 let p0_y = line.p0.y / f32::from(Tile::HEIGHT);
565 let p1_x = line.p1.x / f32::from(Tile::WIDTH);
566 let p1_y = line.p1.y / f32::from(Tile::HEIGHT);
567
568 let (line_left_x, line_right_x) = if p0_x < p1_x {
569 (p0_x, p1_x)
570 } else {
571 (p1_x, p0_x)
572 };
573
574 if line_left_x > tile_columns as f32 {
576 continue;
577 }
578
579 let (line_top_y, line_top_x, line_bottom_y, line_bottom_x) = if p0_y < p1_y {
580 (p0_y, p0_x, p1_y, p1_x)
581 } else {
582 (p1_y, p1_x, p0_y, p0_x)
583 };
584
585 let y_top_tiles = (line_top_y as u16).min(tile_rows);
587 let line_bottom_y_ceil = line_bottom_y.ceil();
588 let y_bottom_tiles = (line_bottom_y_ceil as u16).min(tile_rows);
589
590 if y_top_tiles >= y_bottom_tiles {
594 continue;
595 }
596
597 let p0_tile_x = line_top_x.floor() as i32;
599 let p0_tile_y = line_top_y.floor() as i32;
600 let p1_tile_x = line_bottom_x.floor() as i32;
601
602 let p1_tile_y = if line_bottom_y == line_bottom_y_ceil {
603 line_bottom_y as i32 - 1
604 } else {
605 line_bottom_y.floor() as i32
606 };
607
608 let not_same_tile = p0_tile_y != p1_tile_y || p0_tile_x != p1_tile_x;
610 if not_same_tile {
611 if line_left_x == line_right_x {
613 let x = (line_left_x as u16).min(tile_columns.saturating_sub(1));
614
615 let is_start_culled = line_top_y < 0.0;
617 if !is_start_culled {
618 let winding = ((f32::from(y_top_tiles) >= line_top_y) as u32) << 5;
619 let intersection_mask = 0b10 | winding;
620 let tile = Tile::new_clamped(x, y_top_tiles, line_idx, intersection_mask);
621 self.tile_buf.push(tile);
622 }
623
624 let y_start = if is_start_culled {
627 y_top_tiles
628 } else {
629 y_top_tiles + 1
630 };
631 let line_bottom_floor = line_bottom_y.floor();
632 let y_end_idx = (line_bottom_floor as u16).min(tile_rows);
633
634 if y_start < y_end_idx {
635 let y_last = y_end_idx - 1;
636 for y_idx in y_start..y_last {
637 let intersection_mask = 0b100011; let tile = Tile::new_clamped(x, y_idx, line_idx, intersection_mask);
639 self.tile_buf.push(tile);
640 }
641
642 {
644 let is_end_tile = ((y_last as i32) == p1_tile_y) as u32;
645 let intersection_mask = 0b100001 | ((1 ^ is_end_tile) << 1);
646 let tile = Tile::new_clamped(x, y_last, line_idx, intersection_mask);
647 self.tile_buf.push(tile);
648 }
649 }
650
651 if line_bottom_y != line_bottom_floor && y_end_idx < tile_rows {
655 let intersection_mask = 0b100001; let tile = Tile::new_clamped(x, y_end_idx, line_idx, intersection_mask);
657 self.tile_buf.push(tile);
658 }
659 } else {
660 let dx = p1_x - p0_x;
661 let dy = p1_y - p0_y;
662 let x_slope = dx / dy;
663 let slope_is_pos = x_slope >= 0.0;
664 let dx_dir = (line_bottom_x >= line_top_x) as u32;
665 let not_dx_dir = dx_dir ^ 1;
666
667 let w_start_base = dx_dir << 5;
668 let w_end_base = not_dx_dir << 5;
669
670 let min_x = p0_x.min(p1_x);
673 let max_x = p0_x.max(p1_x);
674 let needs_clamping = min_x < line_left_x || max_x >= line_right_x;
676
677 macro_rules! push_edge {
679 ($x:expr, $y:expr, $row_top_x:expr, $row_bottom_x:expr,
680 $canonical_start:expr, $canonical_end:expr, $winding_input:expr,
681 $check_s:tt, $check_e:expr) => {{
682 let x_idx = $x;
683
684 let unc_row_start = (x_idx as i32 == $canonical_start) as u32;
685 let unc_row_end = (x_idx == $canonical_end) as u32;
686
687 let canonical_row_start =
688 (dx_dir & unc_row_start) | (not_dx_dir & unc_row_end);
689 let canonical_row_end =
690 (not_dx_dir & unc_row_start) | (dx_dir & unc_row_end);
691
692 let start_tile = if $check_s {
693 ((x_idx as i32 == p0_tile_x) && ($y as i32 == p0_tile_y)) as u32
694 } else {
695 0
696 };
697
698 let end_tile = if $check_e {
699 ((x_idx as i32 == p1_tile_x) && ($y as i32 == p1_tile_y)) as u32
700 } else {
701 0
702 };
703
704 let mut mask = $winding_input;
705
706 mask |= canonical_row_start & (1 ^ start_tile);
708 mask |= (1 ^ canonical_row_start) << not_dx_dir << 2;
709 mask |= (canonical_row_end & (1 ^ end_tile)) << 1;
710 mask |= (1 ^ canonical_row_end) << dx_dir << 2;
711
712 let x_left_f = x_idx as f32;
714 let x_right_f = (x_idx + 1) as f32;
715 let trc = (($row_top_x == x_right_f) as u32) & (1 ^ start_tile);
716 let tlc = (($row_top_x == x_left_f) as u32) & (1 ^ start_tile);
717 let brc = (($row_bottom_x == x_right_f) as u32) & (1 ^ end_tile);
718 let blc = (($row_bottom_x == x_left_f) as u32) & (1 ^ end_tile);
719 let tie_break = tlc & (canonical_row_start ^ 1);
721
722 mask |= (tie_break | blc) << 2;
723 mask |= (trc | brc) << 3;
724 mask &= !(tie_break | trc);
725 mask &= !((blc | brc) << 1);
726
727 mask |= (trc | tlc | brc | blc) << 4;
729
730 self.tile_buf.push(Tile::new(x_idx, $y, line_idx, mask));
731 }};
732 }
733
734 macro_rules! process_row {
736 ($y_idx:expr, $row_top_y:expr, $row_bottom_y:expr, $w_mask:expr,
737 $check_s:tt, $check_e:tt, $clamped:tt) => {{
738 let row_top_x = p0_x + ($row_top_y - p0_y) * x_slope;
739 let row_bottom_x = p0_x + ($row_bottom_y - p0_y) * x_slope;
740
741 let (row_left_x, row_right_x, x_end) = if $clamped {
742 let lx = f32::min(row_top_x, row_bottom_x).max(line_left_x);
743 let rx = f32::max(row_top_x, row_bottom_x).min(line_right_x);
744 let xe = (rx as u16).min(tile_columns.saturating_sub(1));
745 (lx, rx, xe)
746 } else {
747 let lx = f32::min(row_top_x, row_bottom_x);
748 let rx = f32::max(row_top_x, row_bottom_x);
749 let xe = rx as u16; (lx, rx, xe)
751 };
752
753 let canonical_x_start = row_left_x.floor() as i32;
754 let canonical_x_end = row_right_x as u16;
755 let x_start = row_left_x as u16;
756
757 if x_start <= x_end {
758 let is_single = (x_start == x_end) as u32;
759 let w_left = (w_start_base | (is_single << 5)) & $w_mask;
760
761 push_edge!(
762 x_start,
763 $y_idx,
764 row_top_x,
765 row_bottom_x,
766 canonical_x_start,
767 canonical_x_end,
768 w_left,
769 $check_s,
770 $check_e
771 );
772
773 for x_idx in x_start.saturating_add(1)..x_end {
774 self.tile_buf.push(Tile::new(x_idx, $y_idx, line_idx, 12));
775 }
776
777 if x_start < x_end {
778 let w_right = w_end_base & $w_mask;
779 push_edge!(
780 x_end,
781 $y_idx,
782 row_top_x,
783 row_bottom_x,
784 canonical_x_start,
785 canonical_x_end,
786 w_right,
787 $check_s,
788 $check_e
789 );
790 }
791 }
792 }};
793 }
794
795 macro_rules! process_middle_row_incremental {
797 ($y_idx:expr, $x_curr:expr, $x_next:expr, $check_e:expr, $clamped:tt) => {{
798 let (raw_left, raw_right) = if slope_is_pos {
800 ($x_curr, $x_next)
801 } else {
802 ($x_next, $x_curr)
803 };
804
805 let (row_left_x, row_right_x, x_end) = if $clamped {
806 let lx = raw_left.max(line_left_x);
807 let rx = raw_right.min(line_right_x);
808 let xe = (rx as u16).min(tile_columns.saturating_sub(1));
809 (lx, rx, xe)
810 } else {
811 (raw_left, raw_right, raw_right as u16)
813 };
814
815 let canonical_x_start = row_left_x.floor() as i32;
816 let canonical_x_end = row_right_x as u16;
817 let x_start = row_left_x as u16;
818
819 if x_start <= x_end {
820 let is_single = (x_start == x_end) as u32;
821 let w_left = w_start_base | (is_single << 5);
822
823 push_edge!(
825 x_start,
826 $y_idx,
827 $x_curr,
828 $x_next,
829 canonical_x_start,
830 canonical_x_end,
831 w_left,
832 false,
833 $check_e
834 );
835
836 for x_idx in x_start.saturating_add(1)..x_end {
837 self.tile_buf.push(Tile::new(x_idx, $y_idx, line_idx, 12));
838 }
839
840 if x_start < x_end {
841 push_edge!(
842 x_end,
843 $y_idx,
844 $x_curr,
845 $x_next,
846 canonical_x_start,
847 canonical_x_end,
848 w_end_base,
849 false,
850 $check_e
851 );
852 }
853 }
854 }};
855 }
856
857 macro_rules! run_loops {
859 ($clamped:tt) => {{
860 let is_start_culled = line_top_y < 0.0;
862 if !is_start_culled {
863 let y = f32::from(y_top_tiles);
864 let row_bottom_y = (y + 1.0).min(line_bottom_y);
865 let mask = ((y >= line_top_y) as u32) << 5;
866 process_row!(
867 y_top_tiles,
868 line_top_y,
869 row_bottom_y,
870 mask,
871 true,
872 true,
873 $clamped
874 );
875 }
876
877 let y_start_middle = if is_start_culled {
878 y_top_tiles
879 } else {
880 y_top_tiles + 1
881 };
882 let line_bottom_floor = line_bottom_y.floor();
883 let y_end_middle = (line_bottom_floor as u16).min(tile_rows);
884
885 if y_start_middle < y_end_middle {
887 let start_y_f = f32::from(y_start_middle);
888 let mut x_curr = p0_x + (start_y_f - p0_y) * x_slope;
889
890 let y_last = y_end_middle - 1;
891 for y_idx in y_start_middle..y_last {
892 let x_next = x_curr + x_slope;
893 process_middle_row_incremental!(
894 y_idx, x_curr, x_next, false, $clamped
895 );
896 x_curr = x_next;
897 }
898
899 {
901 let x_next = x_curr + x_slope;
902 let check_end = (y_last as i32) == p1_tile_y;
903 process_middle_row_incremental!(
904 y_last, x_curr, x_next, check_end, $clamped
905 );
906 }
907 }
908
909 if line_bottom_y != line_bottom_floor
911 && y_end_middle < tile_rows
912 && (is_start_culled || y_end_middle != y_top_tiles)
913 {
914 let y_idx = y_end_middle;
915 let y = f32::from(y_idx);
916 process_row!(
917 y_idx,
918 y,
919 line_bottom_y,
920 u32::MAX,
921 false,
922 true,
923 $clamped
924 );
925 }
926 }};
927 }
928
929 if needs_clamping {
930 run_loops!(true);
931 } else {
932 run_loops!(false);
933 }
934 }
935 } else {
936 let tile = Tile::new_clamped(
938 (line_left_x as u16).min(tile_columns + 1),
939 y_top_tiles,
940 line_idx,
941 ((f32::from(y_top_tiles) >= line_top_y) as u32) << 5,
942 );
943 self.tile_buf.push(tile);
944 }
945 }
946 }
947}
948
949#[cfg(test)]
950mod tests {
951 use crate::flatten::{FlattenCtx, Line, Point, fill};
952 use crate::kurbo::{Affine, BezPath};
953 use crate::tile::{Tile, Tiles};
954 use fearless_simd::Level;
955 use std::vec;
956
957 const W: u32 = 0b100000;
958 const P: u32 = 0b010000;
959 const R: u32 = 0b001000;
960 const L: u32 = 0b000100;
961 const B: u32 = 0b000010;
962 const T: u32 = 0b000001;
963
964 const VIEW_DIM: u16 = 100;
965 const F_V_DIM: f32 = VIEW_DIM as f32;
966
967 impl Tiles {
968 fn assert_tiles_match(
969 &mut self,
970 lines: &[Line],
971 width: u16,
972 height: u16,
973 expected: &[Tile],
974 ) {
975 self.make_tiles_msaa(lines, width, height);
976 assert_eq!(self.tile_buf, expected, "MSAA: Tile buffer mismatch");
977
978 self.make_tiles_analytic_aa(lines, width, height);
979 check_analytic_aa_matches(&self.tile_buf, expected);
980 }
981 }
982
983 fn check_analytic_aa_matches(actual: &[Tile], expected: &[Tile]) {
984 assert_eq!(
985 actual.len(),
986 expected.len(),
987 "Analytic AA: Tile count mismatch."
988 );
989
990 for (i, (got, want)) in actual.iter().zip(expected.iter()).enumerate() {
991 assert_eq!(got.x, want.x, "Analytic AA: Tile[{}] X mismatch", i);
992 assert_eq!(got.y, want.y, "Analytic AA: Tile[{}] Y mismatch", i);
993 assert_eq!(
994 got.line_idx(),
995 want.line_idx(),
996 "Analytic AA: Tile[{}] Line Index mismatch",
997 i
998 );
999
1000 let got_winding = got.packed_winding_line_idx & W;
1001 let want_winding = want.packed_winding_line_idx & W;
1002 assert_eq!(
1003 got_winding, want_winding,
1004 "Analytic AA: Tile[{}] Winding mismatch",
1005 i
1006 );
1007 }
1008 }
1009
1010 #[test]
1014 fn cull_sloped_outside_lines() {
1015 let lines = [
1016 Line {
1017 p0: Point { x: 1.0, y: -7.0 },
1018 p1: Point { x: 3.0, y: -1.0 },
1019 },
1020 Line {
1021 p0: Point { x: 1.0, y: -11.0 },
1022 p1: Point { x: 3.0, y: -1.0 },
1023 },
1024 Line {
1025 p0: Point {
1026 x: F_V_DIM + 1.0,
1027 y: 50.0,
1028 },
1029 p1: Point {
1030 x: F_V_DIM + 3.0,
1031 y: 70.0,
1032 },
1033 },
1034 Line {
1035 p0: Point {
1036 x: 1.0,
1037 y: F_V_DIM + 1.0,
1038 },
1039 p1: Point {
1040 x: 3.0,
1041 y: F_V_DIM + 7.0,
1042 },
1043 },
1044 Line {
1045 p0: Point {
1046 x: 1.0,
1047 y: F_V_DIM + 1.0,
1048 },
1049 p1: Point {
1050 x: 3.0,
1051 y: F_V_DIM + 13.0,
1052 },
1053 },
1054 ];
1055
1056 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1057 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1058 }
1059
1060 #[test]
1061 fn sloped_line_crossing_top() {
1062 let lines = [
1063 Line {
1064 p0: Point { x: -2.0, y: -3.0 },
1065 p1: Point { x: 2.0, y: 1.0 },
1066 },
1067 Line {
1068 p0: Point { x: 6.0, y: -1.0 },
1069 p1: Point { x: 5.0, y: 2.0 },
1070 },
1071 Line {
1072 p0: Point { x: 9.0, y: -10.0 },
1073 p1: Point { x: 10.0, y: 3.0 },
1074 },
1075 Line {
1076 p0: Point { x: 2.0, y: 1.0 },
1077 p1: Point { x: -2.0, y: -3.0 },
1078 },
1079 ];
1080
1081 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1082 let expected = [
1083 Tile::new(0, 0, 0, W | T),
1084 Tile::new(1, 0, 1, W | T),
1085 Tile::new(2, 0, 2, W | T),
1086 Tile::new(0, 0, 3, W | T),
1087 ];
1088
1089 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1090 }
1091
1092 #[test]
1093 fn sloped_line_crossing_bot() {
1094 let lines = [
1095 Line {
1096 p0: Point {
1097 x: 5.0,
1098 y: F_V_DIM + 3.0,
1099 },
1100 p1: Point {
1101 x: 6.0,
1102 y: F_V_DIM - 2.0,
1103 },
1104 },
1105 Line {
1106 p0: Point {
1107 x: 10.0,
1108 y: F_V_DIM + 1.0,
1109 },
1110 p1: Point {
1111 x: 9.0,
1112 y: F_V_DIM - 1.0,
1113 },
1114 },
1115 Line {
1116 p0: Point {
1117 x: 2.0,
1118 y: F_V_DIM - 2.0,
1119 },
1120 p1: Point {
1121 x: 3.0,
1122 y: F_V_DIM + 3.0,
1123 },
1124 },
1125 ];
1126
1127 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1128 let expected = [
1129 Tile::new(1, 24, 0, B),
1130 Tile::new(2, 24, 1, B),
1131 Tile::new(0, 24, 2, B),
1132 ];
1133
1134 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1135 }
1136
1137 #[test]
1138 fn sloped_line_crossing_top_multi_tile() {
1139 let lines = [
1140 Line {
1141 p0: Point { x: 1.0, y: -5.0 },
1142 p1: Point { x: 6.0, y: 7.0 },
1143 },
1144 Line {
1145 p0: Point { x: 2.5, y: -10.0 },
1146 p1: Point { x: 3.5, y: 6.0 },
1147 },
1148 ];
1149
1150 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1151 let expected = [
1152 Tile::new(0, 0, 0, W | T | R),
1153 Tile::new(1, 0, 0, L | B),
1154 Tile::new(1, 1, 0, W | T),
1155 Tile::new(0, 0, 1, W | T | B),
1156 Tile::new(0, 1, 1, W | T),
1157 ];
1158
1159 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1160 }
1161
1162 #[test]
1163 fn sloped_line_crossing_bot_multi_tile() {
1164 let lines = [
1165 Line {
1166 p0: Point {
1167 x: 12.0,
1168 y: F_V_DIM + 10.0,
1169 },
1170 p1: Point { x: 2.0, y: 94.0 },
1171 },
1172 Line {
1173 p0: Point {
1174 x: 1.5,
1175 y: F_V_DIM + 5.0,
1176 },
1177 p1: Point { x: 3.5, y: 94.0 },
1178 },
1179 ];
1180
1181 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1182 let expected = [
1183 Tile::new(0, 23, 0, B),
1184 Tile::new(0, 24, 0, W | T | R),
1185 Tile::new(1, 24, 0, B | L),
1186 Tile::new(0, 23, 1, B),
1187 Tile::new(0, 24, 1, W | T | B),
1188 ];
1189
1190 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1191 }
1192
1193 #[test]
1194 fn sloped_line_crossing_right() {
1195 let lines = [
1196 Line {
1197 p0: Point { x: 97.0, y: 1.0 },
1198 p1: Point {
1199 x: F_V_DIM + 1.0,
1200 y: 2.0,
1201 },
1202 },
1203 Line {
1204 p0: Point { x: 93.0, y: 1.0 },
1205 p1: Point {
1206 x: F_V_DIM + 5.0,
1207 y: 2.0,
1208 },
1209 },
1210 ];
1211
1212 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1213 let expected = [
1214 Tile::new(24, 0, 0, R),
1215 Tile::new(23, 0, 1, R),
1216 Tile::new(24, 0, 1, R | L),
1217 ];
1218
1219 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1220 }
1221
1222 #[test]
1223 fn sloped_line_crossing_left() {
1224 let lines = [
1225 Line {
1226 p0: Point { x: -5.0, y: 1.0 },
1227 p1: Point { x: 1.0, y: 2.0 },
1228 },
1229 Line {
1230 p0: Point { x: -5.0, y: 1.0 },
1231 p1: Point { x: 5.0, y: 2.0 },
1232 },
1233 Line {
1234 p0: Point { x: -5.0, y: 1.0 },
1235 p1: Point { x: 13.0, y: 9.0 },
1236 },
1237 ];
1238
1239 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1240 let expected = [
1241 Tile::new(0, 0, 0, L),
1242 Tile::new(0, 0, 1, L | R),
1243 Tile::new(1, 0, 1, L),
1244 Tile::new(0, 0, 2, L | B),
1245 Tile::new(0, 1, 2, W | R | T),
1246 Tile::new(1, 1, 2, R | L),
1247 Tile::new(2, 1, 2, L | B),
1248 Tile::new(2, 2, 2, W | R | T),
1249 Tile::new(3, 2, 2, L),
1250 ];
1251
1252 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1253 }
1254
1255 #[test]
1256 fn horizontal_line_above_viewport() {
1257 let lines = [Line {
1258 p0: Point { x: 10.0, y: -5.0 },
1259 p1: Point { x: 90.0, y: -5.0 },
1260 }];
1261
1262 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1263 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1264 }
1265
1266 #[test]
1267 fn horizontal_line_below_viewport() {
1268 let lines = [Line {
1269 p0: Point {
1270 x: 10.0,
1271 y: F_V_DIM + 5.0,
1272 },
1273 p1: Point {
1274 x: 90.0,
1275 y: F_V_DIM + 5.0,
1276 },
1277 }];
1278
1279 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1280 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1281 }
1282
1283 #[test]
1284 fn horizontal_line_crossing_left_viewport() {
1285 let lines = [Line {
1286 p0: Point { x: -10.0, y: 10.0 },
1287 p1: Point { x: 10.0, y: 10.0 },
1288 }];
1289
1290 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1291 let expected = [
1292 Tile::new(0, 2, 0, L | R),
1293 Tile::new(1, 2, 0, L | R),
1294 Tile::new(2, 2, 0, L),
1295 ];
1296
1297 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1298 }
1299
1300 #[test]
1301 fn horizontal_line_crossing_right_viewport() {
1302 let lines = [Line {
1303 p0: Point {
1304 x: F_V_DIM - 5.0,
1305 y: 10.0,
1306 },
1307 p1: Point {
1308 x: F_V_DIM + 5.0,
1309 y: 10.0,
1310 },
1311 }];
1312
1313 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1314 let expected = [Tile::new(23, 2, 0, R), Tile::new(24, 2, 0, L | R)];
1315
1316 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1317 }
1318
1319 #[test]
1320 fn vertical_lines_outside_viewport() {
1321 let lines = [
1322 Line {
1323 p0: Point { x: 1.0, y: -5.0 },
1324 p1: Point { x: 1.0, y: -1.0 },
1325 },
1326 Line {
1327 p0: Point {
1328 x: 1.0,
1329 y: F_V_DIM + 1.0,
1330 },
1331 p1: Point {
1332 x: 1.0,
1333 y: F_V_DIM + 5.0,
1334 },
1335 },
1336 ];
1337
1338 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1339 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
1340 }
1341
1342 #[test]
1343 fn vertical_path_on_the_right_of_viewport() {
1344 let path = BezPath::from_svg("M261,0 L78848,0 L78848,4 L261,4 Z").unwrap();
1345 let mut line_buf = vec![];
1346 fill(
1347 Level::try_detect().unwrap_or(Level::fallback()),
1348 &path,
1349 Affine::IDENTITY,
1350 &mut line_buf,
1351 &mut FlattenCtx::default(),
1352 );
1353
1354 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1355 tiles.assert_tiles_match(&line_buf, 10, 10, &[]);
1356 }
1357
1358 #[test]
1359 fn vertical_line_crossing_top_viewport() {
1360 let lines = [
1361 Line {
1362 p0: Point { x: 1.0, y: -7.0 },
1363 p1: Point { x: 1.0, y: 3.0 },
1364 },
1365 Line {
1366 p0: Point { x: 1.0, y: -7.0 },
1367 p1: Point { x: 1.0, y: 7.0 },
1368 },
1369 Line {
1370 p0: Point { x: 1.0, y: -7.0 },
1371 p1: Point { x: 1.0, y: 8.0 },
1372 },
1373 ];
1374
1375 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1376 let expected = [
1377 Tile::new(0, 0, 0, W | T),
1378 Tile::new(0, 0, 1, W | B | T),
1379 Tile::new(0, 1, 1, W | T),
1380 Tile::new(0, 0, 2, W | B | T),
1381 Tile::new(0, 1, 2, W | T),
1382 ];
1383
1384 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1385 }
1386
1387 #[test]
1388 fn vertical_line_crossing_bot_viewport() {
1389 let lines = [
1390 Line {
1391 p0: Point {
1392 x: 1.0,
1393 y: F_V_DIM - 1.0,
1394 },
1395 p1: Point {
1396 x: 1.0,
1397 y: F_V_DIM + 5.0,
1398 },
1399 },
1400 Line {
1401 p0: Point {
1402 x: 1.0,
1403 y: F_V_DIM - 5.0,
1404 },
1405 p1: Point {
1406 x: 1.0,
1407 y: F_V_DIM + 5.0,
1408 },
1409 },
1410 ];
1411
1412 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1413 let expected = [
1414 Tile::new(0, 24, 0, B),
1415 Tile::new(0, 23, 1, B),
1416 Tile::new(0, 24, 1, W | T | B),
1417 ];
1418
1419 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1420 }
1421
1422 #[test]
1423 fn clip_top_left_corner() {
1424 let lines = [Line {
1425 p0: Point { x: -1.0, y: 2.0 },
1426 p1: Point { x: 2.0, y: -1.0 },
1427 }];
1428
1429 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1430 let expected = [Tile::new(0, 0, 0, W | L | T)];
1431
1432 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1433 }
1434
1435 #[test]
1436 fn clip_bottom_right_corner() {
1437 let lines = [Line {
1438 p0: Point {
1439 x: F_V_DIM + 1.0,
1440 y: F_V_DIM - 2.0,
1441 },
1442 p1: Point {
1443 x: F_V_DIM - 2.0,
1444 y: F_V_DIM + 1.0,
1445 },
1446 }];
1447
1448 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1449 let expected = [Tile::new(24, 24, 0, R | B)];
1450
1451 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1452 }
1453
1454 #[test]
1458 fn horizontal_line_left_to_right_three_tile() {
1459 let lines = [Line {
1460 p0: Point { x: 1.5, y: 1.0 },
1461 p1: Point { x: 8.5, y: 1.0 },
1462 }];
1463
1464 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1465 let expected = [
1466 Tile::new(0, 0, 0, R),
1467 Tile::new(1, 0, 0, R | L),
1468 Tile::new(2, 0, 0, L),
1469 ];
1470
1471 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1472 }
1473
1474 #[test]
1475 fn horizontal_line_right_to_left_three_tile() {
1476 let lines = [Line {
1477 p0: Point { x: 8.5, y: 1.0 },
1478 p1: Point { x: 1.5, y: 1.0 },
1479 }];
1480
1481 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1482 let expected = [
1483 Tile::new(0, 0, 0, R),
1484 Tile::new(1, 0, 0, R | L),
1485 Tile::new(2, 0, 0, L),
1486 ];
1487
1488 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1489 }
1490
1491 #[test]
1492 fn horizontal_line_multi_tile() {
1493 let lines = [Line {
1494 p0: Point { x: 1.5, y: 1.0 },
1495 p1: Point { x: 12.5, y: 1.0 },
1496 }];
1497
1498 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1499 let expected = [
1500 Tile::new(0, 0, 0, R),
1501 Tile::new(1, 0, 0, R | L),
1502 Tile::new(2, 0, 0, R | L),
1503 Tile::new(3, 0, 0, L),
1504 ];
1505
1506 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1507 }
1508
1509 #[test]
1510 fn vertical_line_down_three_tile() {
1511 let lines = [Line {
1512 p0: Point { x: 1.0, y: 1.5 },
1513 p1: Point { x: 1.0, y: 8.5 },
1514 }];
1515
1516 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1517 let expected = [
1518 Tile::new(0, 0, 0, B),
1519 Tile::new(0, 1, 0, W | T | B),
1520 Tile::new(0, 2, 0, W | T),
1521 ];
1522
1523 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1524 }
1525
1526 #[test]
1527 fn vertical_line_down_multi_tile() {
1528 let lines = [Line {
1529 p0: Point { x: 1.0, y: 1.0 },
1530 p1: Point { x: 1.0, y: 13.0 },
1531 }];
1532
1533 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1534 let expected = [
1535 Tile::new(0, 0, 0, B),
1536 Tile::new(0, 1, 0, W | T | B),
1537 Tile::new(0, 2, 0, W | T | B),
1538 Tile::new(0, 3, 0, W | T),
1539 ];
1540
1541 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1542 }
1543
1544 #[test]
1545 fn vertical_line_up_three_tile() {
1546 let lines = [Line {
1547 p0: Point { x: 1.0, y: 13.0 },
1548 p1: Point { x: 1.0, y: 1.0 },
1549 }];
1550
1551 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1552 let expected = [
1553 Tile::new(0, 0, 0, B),
1554 Tile::new(0, 1, 0, W | T | B),
1555 Tile::new(0, 2, 0, W | T | B),
1556 Tile::new(0, 3, 0, W | T),
1557 ];
1558
1559 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1560 }
1561
1562 #[test]
1563 fn vertical_line_up_multi_tile() {
1564 let lines = [Line {
1565 p0: Point { x: 1.0, y: 8.5 },
1566 p1: Point { x: 1.0, y: 1.5 },
1567 }];
1568
1569 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1570 let expected = [
1571 Tile::new(0, 0, 0, B),
1572 Tile::new(0, 1, 0, W | T | B),
1573 Tile::new(0, 2, 0, W | T),
1574 ];
1575
1576 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1577 }
1578
1579 #[test]
1581 fn vertical_line_touching_bot() {
1582 let lines = [Line {
1583 p0: Point { x: 1.0, y: 1.0 },
1584 p1: Point { x: 1.0, y: 8.0 },
1585 }];
1586
1587 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1588 let expected = [Tile::new(0, 0, 0, B), Tile::new(0, 1, 0, W | T)];
1589
1590 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1591 }
1592
1593 #[test]
1594 fn vertical_line_touching_top() {
1595 let lines = [Line {
1596 p0: Point { x: 1.0, y: 0.0 },
1597 p1: Point { x: 1.0, y: 7.0 },
1598 }];
1599
1600 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1601 let expected = [Tile::new(0, 0, 0, W | B), Tile::new(0, 1, 0, W | T)];
1602
1603 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1604 }
1605
1606 #[test]
1610 fn top_left_to_bottom_right() {
1611 let lines = [Line {
1612 p0: Point { x: 1.0, y: 1.0 },
1613 p1: Point { x: 11.0, y: 9.0 },
1614 }];
1615
1616 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1617 let expected = [
1618 Tile::new(0, 0, 0, R),
1619 Tile::new(1, 0, 0, L | B),
1620 Tile::new(1, 1, 0, W | R | T),
1621 Tile::new(2, 1, 0, L | B),
1622 Tile::new(2, 2, 0, W | T),
1623 ];
1624
1625 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1626 }
1627
1628 #[test]
1629 fn bottom_right_to_top_left() {
1630 let lines = [Line {
1631 p0: Point { x: 11.0, y: 9.0 },
1632 p1: Point { x: 1.0, y: 1.0 },
1633 }];
1634
1635 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1636 let expected = [
1637 Tile::new(0, 0, 0, R),
1638 Tile::new(1, 0, 0, L | B),
1639 Tile::new(1, 1, 0, W | R | T),
1640 Tile::new(2, 1, 0, L | B),
1641 Tile::new(2, 2, 0, W | T),
1642 ];
1643
1644 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1645 }
1646
1647 #[test]
1648 fn bottom_left_to_top_right() {
1649 let lines = [Line {
1650 p0: Point { x: 2.0, y: 11.0 },
1651 p1: Point { x: 14.0, y: 6.0 },
1652 }];
1653
1654 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1655 let expected = [
1656 Tile::new(2, 1, 0, R | B),
1657 Tile::new(3, 1, 0, L),
1658 Tile::new(0, 2, 0, R),
1659 Tile::new(1, 2, 0, R | L),
1660 Tile::new(2, 2, 0, W | L | T),
1661 ];
1662
1663 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1664 }
1665
1666 #[test]
1667 fn top_right_to_bottom_left() {
1668 let lines = [Line {
1669 p0: Point { x: 14.0, y: 6.0 },
1670 p1: Point { x: 2.0, y: 11.0 },
1671 }];
1672
1673 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1674 let expected = [
1675 Tile::new(2, 1, 0, R | B),
1676 Tile::new(3, 1, 0, L),
1677 Tile::new(0, 2, 0, R),
1678 Tile::new(1, 2, 0, R | L),
1679 Tile::new(2, 2, 0, W | L | T),
1680 ];
1681
1682 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1683 }
1684
1685 #[test]
1686 fn two_lines_in_single_tile() {
1687 let lines = [
1688 Line {
1689 p0: Point { x: 1.0, y: 3.0 },
1690 p1: Point { x: 3.0, y: 3.0 },
1691 },
1692 Line {
1693 p0: Point { x: 3.0, y: 3.0 },
1694 p1: Point { x: 0.0, y: 1.0 },
1695 },
1696 ];
1697
1698 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1699 let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
1700
1701 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1702 }
1703
1704 #[test]
1705 fn diagonal_cross_corner() {
1706 let lines = [Line {
1707 p0: Point { x: 3.0, y: 5.0 },
1708 p1: Point { x: 5.0, y: 3.0 },
1709 }];
1710
1711 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1712 let expected = [
1713 Tile::new(1, 0, 0, P | L),
1714 Tile::new(0, 1, 0, P | R),
1715 Tile::new(1, 1, 0, W | P | L | T),
1716 ];
1717
1718 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1719 }
1720
1721 #[test]
1722 fn diagonal_cross_corner_two() {
1723 let lines = [Line {
1724 p0: Point { x: 7.9, y: 7.9 },
1725 p1: Point { x: 0.1, y: 0.1 },
1726 }];
1727
1728 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1729 let expected = [
1730 Tile::new(0, 0, 0, P | R),
1731 Tile::new(1, 0, 0, P | L),
1732 Tile::new(1, 1, 0, W | P | T),
1733 ];
1734
1735 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1736 }
1737
1738 #[test]
1739 fn diagonal_down_slope_tiles() {
1740 let lines = [Line {
1741 p0: Point { x: 5.0, y: 5.0 },
1742 p1: Point { x: 9.0, y: 9.0 },
1743 }];
1744
1745 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1746 let expected = [
1747 Tile::new(1, 1, 0, P | R),
1748 Tile::new(2, 1, 0, P | L),
1749 Tile::new(2, 2, 0, W | P | T),
1750 ];
1751
1752 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1753 }
1754
1755 #[test]
1756 fn diagonal_up_slope_tiles() {
1757 let lines = [Line {
1758 p0: Point { x: 5.0, y: 9.0 },
1759 p1: Point { x: 9.0, y: 5.0 },
1760 }];
1761
1762 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1763 let expected = [
1764 Tile::new(1, 1, 0, R | B),
1765 Tile::new(2, 1, 0, L),
1766 Tile::new(1, 2, 0, W | T),
1767 ];
1768
1769 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1770 }
1771
1772 #[test]
1773 fn diagonal_down_one_tile() {
1774 let lines = [Line {
1775 p0: Point { x: 0.0, y: 0.0 },
1776 p1: Point { x: 4.0, y: 4.0 },
1777 }];
1778
1779 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1780 let expected = [Tile::new(0, 0, 0, W | P | R), Tile::new(1, 0, 0, L)];
1781
1782 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1783 }
1784
1785 #[test]
1786 fn diagonal_up_one_tile() {
1787 let lines = [Line {
1788 p0: Point { x: 0.0, y: 4.0 },
1789 p1: Point { x: 4.0, y: 0.0 },
1790 }];
1791
1792 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1793 let expected = [Tile::new(0, 0, 0, P | R), Tile::new(1, 0, 0, W | L)];
1794
1795 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1796 }
1797
1798 #[test]
1799 fn diagonal_down_two_tile() {
1800 let lines = [Line {
1801 p0: Point { x: 0.0, y: 0.0 },
1802 p1: Point { x: 8.0, y: 8.0 },
1803 }];
1804
1805 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1806 let expected = [
1807 Tile::new(0, 0, 0, W | P | R),
1808 Tile::new(1, 0, 0, P | L),
1809 Tile::new(1, 1, 0, W | P | R | T),
1810 Tile::new(2, 1, 0, L),
1811 ];
1812
1813 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1814 }
1815
1816 #[test]
1817 fn diagonal_up_two_tile() {
1818 let lines = [Line {
1819 p0: Point { x: 0.0, y: 8.0 },
1820 p1: Point { x: 8.0, y: 0.0 },
1821 }];
1822
1823 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1824 let expected = [
1825 Tile::new(1, 0, 0, P | R | L),
1826 Tile::new(2, 0, 0, W | L),
1827 Tile::new(0, 1, 0, P | R),
1828 Tile::new(1, 1, 0, W | P | L | T),
1829 ];
1830
1831 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1832 }
1833
1834 #[test]
1835 fn sloped_ending_right() {
1836 let lines = [Line {
1837 p0: Point { x: 1.0, y: 1.0 },
1838 p1: Point { x: 8.0, y: 2.0 },
1839 }];
1840
1841 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1842 let expected = [
1843 Tile::new(0, 0, 0, R),
1844 Tile::new(1, 0, 0, R | L),
1845 Tile::new(2, 0, 0, L),
1846 ];
1847
1848 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1849 }
1850
1851 #[test]
1852 fn sloped_touching_top() {
1853 let lines = [Line {
1854 p0: Point { x: 0.0, y: 8.0 },
1855 p1: Point { x: 4.0, y: 0.0 },
1856 }];
1857
1858 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1859 let expected = [
1860 Tile::new(0, 0, 0, P | R | B),
1861 Tile::new(1, 0, 0, W | L),
1862 Tile::new(0, 1, 0, W | T),
1863 ];
1864
1865 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1866 }
1867
1868 #[test]
1869 fn sloped_touching_bot() {
1870 let lines = [Line {
1871 p0: Point { x: 0.0, y: 0.0 },
1872 p1: Point { x: 4.0, y: 8.0 },
1873 }];
1874
1875 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1876 let expected = [
1877 Tile::new(0, 0, 0, W | B),
1878 Tile::new(0, 1, 0, W | P | R | T),
1879 Tile::new(1, 1, 0, L),
1880 ];
1881
1882 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1883 }
1884
1885 #[test]
1889 fn same_tile() {
1890 let lines = [Line {
1891 p0: Point { x: 1.0, y: 1.0 },
1892 p1: Point { x: 3.0, y: 3.0 },
1893 }];
1894
1895 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1896 let expected = [Tile::new(0, 0, 0, 0)];
1897
1898 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1899 }
1900
1901 #[test]
1902 fn same_tile_left() {
1903 let lines = [Line {
1904 p0: Point { x: 0.0, y: 1.0 },
1905 p1: Point { x: 3.0, y: 1.0 },
1906 }];
1907
1908 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1909 let expected = [Tile::new(0, 0, 0, 0)];
1910
1911 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1912 }
1913
1914 #[test]
1915 fn same_tile_top() {
1916 let lines = [Line {
1917 p0: Point { x: 1.0, y: 0.0 },
1918 p1: Point { x: 1.0, y: 3.0 },
1919 }];
1920
1921 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1922 let expected = [Tile::new(0, 0, 0, W)];
1923
1924 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1925 }
1926
1927 #[test]
1928 fn same_tile_right() {
1929 let lines = [Line {
1930 p0: Point { x: 1.0, y: 1.0 },
1931 p1: Point { x: 4.0, y: 1.0 },
1932 }];
1933
1934 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1935 let expected = [Tile::new(0, 0, 0, R), Tile::new(1, 0, 0, L)];
1936
1937 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1938 }
1939
1940 #[test]
1941 fn same_tile_bottom() {
1942 let lines = [
1943 Line {
1944 p0: Point { x: 1.0, y: 1.0 },
1945 p1: Point { x: 1.0, y: 4.0 },
1946 },
1947 Line {
1948 p0: Point { x: 1.0, y: 1.0 },
1949 p1: Point { x: 2.0, y: 4.0 },
1950 },
1951 ];
1952
1953 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1954 let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
1955
1956 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1957 }
1958
1959 #[test]
1960 fn same_tile_top_left() {
1961 let lines = [
1962 Line {
1963 p0: Point { x: 0.0, y: 1.0 },
1964 p1: Point { x: 1.0, y: 0.0 },
1965 },
1966 Line {
1967 p0: Point { x: 0.0, y: 0.0001 },
1968 p1: Point { x: 0.0001, y: 0.0 },
1969 },
1970 ];
1971
1972 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1973 let expected = [Tile::new(0, 0, 0, W), Tile::new(0, 0, 1, W)];
1974
1975 tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
1976 }
1977
1978 #[test]
1982 fn infinite_loop() {
1984 let line = Line {
1985 p0: Point { x: 22.0, y: 552.0 },
1986 p1: Point { x: 224.0, y: 388.0 },
1987 };
1988
1989 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
1990 tiles.make_tiles_msaa(&[line], 600, 600);
1991 tiles.make_tiles_analytic_aa(&[line], 600, 600);
1992 }
1993
1994 #[test]
1995 fn overflow() {
1997 let line = Line {
1998 p0: Point {
1999 x: 59.60001,
2000 y: 40.78,
2001 },
2002 p1: Point {
2003 x: 520599.6,
2004 y: 100.18,
2005 },
2006 };
2007
2008 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
2009 tiles.make_tiles_analytic_aa(&[line], 200, 100);
2010 tiles.make_tiles_msaa(&[line], 200, 100);
2011 }
2012
2013 #[test]
2014 fn sort_test() {
2015 let mut lines = vec![];
2016 let mut tiles = Tiles::new(Level::fallback());
2017
2018 let step = 4.0;
2019 let mut y = F_V_DIM - 10.0;
2020 while y > 10.0 {
2021 lines.push(Line {
2022 p0: Point {
2023 x: F_V_DIM - 10.0,
2024 y,
2025 },
2026 p1: Point { x: 10.0, y },
2027 });
2028
2029 lines.push(Line {
2030 p0: Point {
2031 x: F_V_DIM - 12.0,
2032 y,
2033 },
2034 p1: Point { x: 12.0, y },
2035 });
2036
2037 y -= step;
2038 }
2039
2040 tiles.make_tiles_msaa(&lines, VIEW_DIM, VIEW_DIM);
2041 assert!(tiles.tile_buf.first().unwrap().y > tiles.tile_buf.last().unwrap().y);
2042 tiles.sort_tiles();
2043 check_sorted(&tiles.tile_buf);
2044
2045 tiles.make_tiles_analytic_aa(&lines, VIEW_DIM, VIEW_DIM);
2046 assert!(tiles.tile_buf.first().unwrap().y > tiles.tile_buf.last().unwrap().y);
2047 tiles.sort_tiles();
2048 check_sorted(&tiles.tile_buf);
2049 }
2050
2051 fn check_sorted(buf: &[Tile]) {
2052 for i in 0..buf.len() - 1 {
2053 let current = buf[i];
2054 let next = buf[i + 1];
2055
2056 if current.y > next.y {
2057 panic!(
2058 "Sort Failure [Y]: Tile[{}] (y={}) > Tile[{}] (y={})",
2059 i,
2060 current.y,
2061 i + 1,
2062 next.y
2063 );
2064 }
2065
2066 if current.y == next.y {
2067 if current.x > next.x {
2068 panic!(
2069 "Sort Failure [X]: at Row y={}, Tile[{}] (x={}) > Tile[{}] (x={})",
2070 current.y,
2071 i,
2072 current.x,
2073 i + 1,
2074 next.x
2075 );
2076 }
2077
2078 if current.x == next.x
2079 && current.packed_winding_line_idx > next.packed_winding_line_idx
2080 {
2081 panic!(
2082 "Sort Failure [Payload]: at {}x{}, Tile[{}] (val={}) > Tile[{}] (val={})",
2083 current.x,
2084 current.y,
2085 i,
2086 current.packed_winding_line_idx,
2087 i + 1,
2088 next.packed_winding_line_idx
2089 );
2090 }
2091 }
2092 }
2093 }
2094}