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