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 << 31;
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,
53
54 #[cfg(target_endian = "little")]
55 pub x: u16,
57
58 #[cfg(target_endian = "little")]
59 pub y: u16,
61}
62
63impl Tile {
64 pub const WIDTH: u16 = 4;
66
67 pub const HEIGHT: u16 = 4;
69
70 #[inline]
75 pub fn new_clamped(x: u16, y: u16, line_idx: u32, winding: bool) -> Self {
76 Self::new(
77 x.min(u16::MAX / Self::WIDTH),
80 y.min(u16::MAX / Self::HEIGHT),
81 line_idx,
82 winding,
83 )
84 }
85
86 #[inline]
87 pub(crate) const fn new(x: u16, y: u16, line_idx: u32, winding: bool) -> Self {
88 #[cfg(debug_assertions)]
89 if line_idx >= MAX_LINES_PER_PATH {
90 panic!("Max. number of lines per path exceeded.");
91 }
92 Self {
93 x,
94 y,
95 packed_winding_line_idx: ((winding as u32) << 31) | line_idx,
96 }
97 }
98
99 #[inline]
101 pub const fn same_loc(&self, other: &Self) -> bool {
102 self.same_row(other) && self.x == other.x
103 }
104
105 #[inline]
107 pub const fn prev_loc(&self, other: &Self) -> bool {
108 self.same_row(other) && self.x + 1 == other.x
109 }
110
111 #[inline]
113 pub const fn same_row(&self, other: &Self) -> bool {
114 self.y == other.y
115 }
116
117 #[inline]
119 pub const fn line_idx(&self) -> u32 {
120 self.packed_winding_line_idx & (MAX_LINES_PER_PATH - 1)
121 }
122
123 #[inline]
128 pub const fn winding(&self) -> bool {
129 (self.packed_winding_line_idx & MAX_LINES_PER_PATH) != 0
130 }
131
132 #[inline(always)]
137 const fn to_bits(self) -> u64 {
138 ((self.y as u64) << 48) | ((self.x as u64) << 32) | self.packed_winding_line_idx as u64
139 }
140}
141
142impl PartialEq for Tile {
143 #[inline(always)]
144 fn eq(&self, other: &Self) -> bool {
145 self.to_bits() == other.to_bits()
146 }
147}
148
149impl Ord for Tile {
150 #[inline(always)]
151 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
152 self.to_bits().cmp(&other.to_bits())
153 }
154}
155
156impl PartialOrd for Tile {
157 #[inline(always)]
158 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
159 Some(self.cmp(other))
160 }
161}
162
163impl Eq for Tile {}
164
165#[derive(Clone, Debug)]
167pub struct Tiles {
168 tile_buf: Vec<Tile>,
169 level: Level,
170 sorted: bool,
171}
172
173impl Tiles {
174 pub fn new(level: Level) -> Self {
176 Self {
177 tile_buf: vec![],
178 level,
179 sorted: false,
180 }
181 }
182
183 pub fn len(&self) -> u32 {
185 self.tile_buf.len() as u32
186 }
187
188 pub fn is_empty(&self) -> bool {
190 self.tile_buf.is_empty()
191 }
192
193 pub fn reset(&mut self) {
195 self.tile_buf.clear();
196 self.sorted = false;
197 }
198
199 pub fn sort_tiles(&mut self) {
201 self.sorted = true;
202 self.level.dispatch(|_| self.tile_buf.sort_unstable());
204 }
205
206 #[inline]
210 pub fn get(&self, index: u32) -> &Tile {
211 assert!(
212 self.sorted,
213 "attempted to call `get` before sorting the tile container."
214 );
215
216 &self.tile_buf[index as usize]
217 }
218
219 #[inline]
223 pub fn iter(&self) -> impl Iterator<Item = &Tile> {
224 assert!(
225 self.sorted,
226 "attempted to call `iter` before sorting the tile container."
227 );
228
229 self.tile_buf.iter()
230 }
231
232 pub fn make_tiles(&mut self, lines: &[Line], width: u16, height: u16) {
241 self.reset();
242
243 if width == 0 || height == 0 {
244 return;
245 }
246
247 debug_assert!(
248 lines.len() <= MAX_LINES_PER_PATH as usize,
249 "Max. number of lines per path exceeded. Max is {}, got {}.",
250 MAX_LINES_PER_PATH,
251 lines.len()
252 );
253
254 let tile_columns = width.div_ceil(Tile::WIDTH);
255 let tile_rows = height.div_ceil(Tile::HEIGHT);
256
257 for (line_idx, line) in lines.iter().take(MAX_LINES_PER_PATH as usize).enumerate() {
258 let line_idx = line_idx as u32;
259
260 let p0_x = line.p0.x / f32::from(Tile::WIDTH);
261 let p0_y = line.p0.y / f32::from(Tile::HEIGHT);
262 let p1_x = line.p1.x / f32::from(Tile::WIDTH);
263 let p1_y = line.p1.y / f32::from(Tile::HEIGHT);
264
265 let (line_left_x, line_right_x) = if p0_x < p1_x {
266 (p0_x, p1_x)
267 } else {
268 (p1_x, p0_x)
269 };
270 let (line_top_y, line_top_x, line_bottom_y, line_bottom_x) = if p0_y < p1_y {
271 (p0_y, p0_x, p1_y, p1_x)
272 } else {
273 (p1_y, p1_x, p0_y, p0_x)
274 };
275
276 if line_left_x == line_right_x {
278 let y_top_tiles = (line_top_y as u16).min(tile_rows);
279 let y_bottom_tiles = (line_bottom_y.ceil() as u16).min(tile_rows);
280
281 let x = (line_left_x as u16).min(tile_columns + 1);
289
290 for y_idx in y_top_tiles..y_bottom_tiles {
291 let y = f32::from(y_idx);
292
293 let tile = Tile::new_clamped(x, y_idx, line_idx, y >= line_top_y);
294 self.tile_buf.push(tile);
295 }
296 } else {
297 let x_slope = (p1_x - p0_x) / (p1_y - p0_y);
298
299 let y_top_tiles = (line_top_y as u16).min(tile_rows);
300 let y_bottom_tiles = (line_bottom_y.ceil() as u16).min(tile_rows);
301
302 for y_idx in y_top_tiles..y_bottom_tiles {
303 let y = f32::from(y_idx);
304
305 let line_row_top_y = line_top_y.max(y).min(y + 1.);
308 let line_row_bottom_y = line_bottom_y.max(y).min(y + 1.);
309
310 let line_row_top_x = p0_x + (line_row_top_y - p0_y) * x_slope;
313 let line_row_bottom_x = p0_x + (line_row_bottom_y - p0_y) * x_slope;
314
315 let line_row_left_x =
318 f32::min(line_row_top_x, line_row_bottom_x).max(line_left_x);
319 let line_row_right_x =
320 f32::max(line_row_top_x, line_row_bottom_x).min(line_right_x);
321
322 let winding_x = if line_top_x < line_bottom_x {
323 line_row_left_x as u16
324 } else {
325 line_row_right_x as u16
326 };
327
328 for x_idx in
329 line_row_left_x as u16..=(line_row_right_x as u16).min(tile_columns - 1)
330 {
331 let tile = Tile::new_clamped(
332 x_idx,
333 y_idx,
334 line_idx,
335 y >= line_top_y && x_idx == winding_x,
336 );
337 self.tile_buf.push(tile);
338 }
339 }
340 }
341 }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use crate::flatten::{FlattenCtx, Line, Point, fill};
348 use crate::kurbo::{Affine, BezPath};
349 use crate::tile::{Tile, Tiles};
350 use fearless_simd::Level;
351 use std::vec;
352
353 #[test]
354 fn cull_line_at_top() {
355 let line = Line {
356 p0: Point { x: 3.0, y: -5.0 },
357 p1: Point { x: 9.0, y: -1.0 },
358 };
359
360 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
361 tiles.make_tiles(&[line], 100, 100);
362
363 assert!(tiles.is_empty());
364 }
365
366 #[test]
367 fn cull_line_at_right() {
368 let line = Line {
369 p0: Point { x: 101.0, y: 0.0 },
370 p1: Point { x: 103.0, y: 20.0 },
371 };
372
373 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
374 tiles.make_tiles(&[line], 100, 100);
375
376 assert!(tiles.is_empty());
377 }
378
379 #[test]
380 fn cull_line_at_bottom() {
381 let line = Line {
382 p0: Point { x: 30.0, y: 101.0 },
383 p1: Point { x: 35.0, y: 105.0 },
384 };
385
386 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
387 tiles.make_tiles(&[line], 100, 100);
388
389 assert!(tiles.is_empty());
390 }
391
392 #[test]
393 fn partially_cull_line_exceeding_viewport() {
394 let line = Line {
395 p0: Point { x: -2.0, y: -3.0 },
396 p1: Point { x: 2.0, y: 1.0 },
397 };
398
399 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
400 tiles.make_tiles(&[line], 100, 100);
401
402 assert_eq!(tiles.tile_buf, [Tile::new_clamped(0, 0, 0, true)]);
403 }
404
405 #[test]
406 fn horizontal_straight_line() {
407 let line = Line {
408 p0: Point { x: 1.5, y: 1.0 },
409 p1: Point { x: 8.5, y: 1.0 },
410 };
411
412 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
413 tiles.make_tiles(&[line], 100, 100);
414 tiles.sort_tiles();
415
416 assert_eq!(
417 tiles.tile_buf,
418 [
419 Tile::new_clamped(0, 0, 0, false),
420 Tile::new_clamped(1, 0, 0, false),
421 Tile::new_clamped(2, 0, 0, false),
422 ]
423 );
424 }
425
426 #[test]
427 fn vertical_straight_line() {
428 let line = Line {
429 p0: Point { x: 1.0, y: 1.5 },
430 p1: Point { x: 1.0, y: 8.5 },
431 };
432
433 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
434 tiles.make_tiles(&[line], 100, 100);
435 tiles.sort_tiles();
436
437 assert_eq!(
438 tiles.tile_buf,
439 [
440 Tile::new_clamped(0, 0, 0, false),
441 Tile::new_clamped(0, 1, 0, true),
442 Tile::new_clamped(0, 2, 0, true),
443 ]
444 );
445 }
446
447 #[test]
448 fn top_left_to_bottom_right() {
449 let line = Line {
450 p0: Point { x: 1.0, y: 1.0 },
451 p1: Point { x: 11.0, y: 8.5 },
452 };
453
454 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
455 tiles.make_tiles(&[line], 100, 100);
456 tiles.sort_tiles();
457
458 assert_eq!(
459 tiles.tile_buf,
460 [
461 Tile::new_clamped(0, 0, 0, false),
462 Tile::new_clamped(1, 0, 0, false),
463 Tile::new_clamped(1, 1, 0, true),
464 Tile::new_clamped(2, 1, 0, false),
465 Tile::new_clamped(2, 2, 0, true),
466 ]
467 );
468 }
469
470 #[test]
471 fn bottom_right_to_top_left() {
472 let line = Line {
473 p0: Point { x: 11.0, y: 8.5 },
474 p1: Point { x: 1.0, y: 1.0 },
475 };
476
477 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
478 tiles.make_tiles(&[line], 100, 100);
479 tiles.sort_tiles();
480
481 assert_eq!(
482 tiles.tile_buf,
483 [
484 Tile::new_clamped(0, 0, 0, false),
485 Tile::new_clamped(1, 0, 0, false),
486 Tile::new_clamped(1, 1, 0, true),
487 Tile::new_clamped(2, 1, 0, false),
488 Tile::new_clamped(2, 2, 0, true),
489 ]
490 );
491 }
492
493 #[test]
494 fn bottom_left_to_top_right() {
495 let line = Line {
496 p0: Point { x: 2.0, y: 11.0 },
497 p1: Point { x: 14.0, y: 6.0 },
498 };
499
500 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
501 tiles.make_tiles(&[line], 100, 100);
502 tiles.sort_tiles();
503
504 assert_eq!(
505 tiles.tile_buf,
506 [
507 Tile::new_clamped(2, 1, 0, false),
508 Tile::new_clamped(3, 1, 0, false),
509 Tile::new_clamped(0, 2, 0, false),
510 Tile::new_clamped(1, 2, 0, false),
511 Tile::new_clamped(2, 2, 0, true),
512 ]
513 );
514 }
515
516 #[test]
517 fn top_right_to_bottom_left() {
518 let line = Line {
519 p0: Point { x: 14.0, y: 6.0 },
520 p1: Point { x: 2.0, y: 11.0 },
521 };
522
523 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
524 tiles.make_tiles(&[line], 100, 100);
525 tiles.sort_tiles();
526
527 assert_eq!(
528 tiles.tile_buf,
529 [
530 Tile::new_clamped(2, 1, 0, false),
531 Tile::new_clamped(3, 1, 0, false),
532 Tile::new_clamped(0, 2, 0, false),
533 Tile::new_clamped(1, 2, 0, false),
534 Tile::new_clamped(2, 2, 0, true),
535 ]
536 );
537 }
538
539 #[test]
540 fn two_lines_in_single_tile() {
541 let line_1 = Line {
542 p0: Point { x: 1.0, y: 3.0 },
543 p1: Point { x: 3.0, y: 3.0 },
544 };
545
546 let line_2 = Line {
547 p0: Point { x: 3.0, y: 3.0 },
548 p1: Point { x: 0.0, y: 1.0 },
549 };
550
551 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
552 tiles.make_tiles(&[line_1, line_2], 100, 100);
553
554 assert_eq!(
555 tiles.tile_buf,
556 [
557 Tile::new_clamped(0, 0, 0, false),
558 Tile::new_clamped(0, 0, 1, false),
559 ]
560 );
561 }
562
563 #[test]
564 fn infinite_loop() {
566 let line = Line {
567 p0: Point { x: 22.0, y: 552.0 },
568 p1: Point { x: 224.0, y: 388.0 },
569 };
570
571 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
572 tiles.make_tiles(&[line], 600, 600);
573 }
574
575 #[test]
576 fn vertical_path_on_the_right_of_viewport() {
577 let path = BezPath::from_svg("M261,0 L78848,0 L78848,4 L261,4 Z").unwrap();
578 let mut line_buf = vec![];
579 fill(
580 Level::try_detect().unwrap_or(Level::fallback()),
581 &path,
582 Affine::IDENTITY,
583 &mut line_buf,
584 &mut FlattenCtx::default(),
585 );
586
587 let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::fallback()));
588 tiles.make_tiles(&line_buf, 10, 10);
589 assert_eq!(tiles.tile_buf[0].x, 4);
590 assert_eq!(tiles.tile_buf[1].x, 4);
591 }
592}