use crate::geometry::RectU16;
use crate::kurbo::{Affine, BezPath, PathEl, Rect};
use crate::strip::Strip;
use crate::strip_generator::{GenerationMode, StripGenerator, StripStorage};
use crate::tile::Tile;
use crate::util::normalized_mul_u8x16;
use alloc::vec;
use alloc::vec::Vec;
use fearless_simd::{Level, Simd, SimdBase, dispatch, u8x16};
use peniko::Fill;
#[cfg(not(feature = "std"))]
use peniko::kurbo::common::FloatFuncs as _;
#[derive(Debug)]
struct ClipData {
alpha_start: u32,
strip_start: u32,
bbox: RectU16,
}
impl ClipData {
fn to_path_data_ref<'a>(&self, storage: &'a StripStorage) -> PathDataRef<'a> {
PathDataRef {
strips: storage
.strips
.get(self.strip_start as usize..)
.unwrap_or(&[]),
alphas: storage
.alphas
.get(self.alpha_start as usize..)
.unwrap_or(&[]),
bbox: self.bbox,
}
}
}
#[derive(Debug)]
pub struct ClipContext {
storage: StripStorage,
temp_storage: StripStorage,
clip_stack: Vec<ClipData>,
}
impl Default for ClipContext {
fn default() -> Self {
Self::new()
}
}
impl ClipContext {
#[inline]
pub fn new() -> Self {
let mut main_storage = StripStorage::default();
main_storage.set_generation_mode(GenerationMode::Append);
Self {
storage: main_storage,
temp_storage: StripStorage::default(),
clip_stack: vec![],
}
}
#[inline]
pub fn reset(&mut self) {
self.clip_stack.clear();
self.storage.clear();
self.temp_storage.clear();
}
#[inline]
pub fn get(&self) -> Option<PathDataRef<'_>> {
self.clip_stack
.last()
.map(|c| c.to_path_data_ref(&self.storage))
}
#[inline]
pub fn push_clip(
&mut self,
clip_path: &BezPath,
strip_generator: &mut StripGenerator,
fill_rule: Fill,
transform: Affine,
aliasing_threshold: Option<u8>,
) {
self.temp_storage.clear();
let alpha_start = self.storage.alphas.len() as u32;
let strip_start = self.storage.strips.len() as u32;
let path_bbox = control_point_bbox(clip_path, transform);
let mut bbox = RectU16::new(
path_bbox.x0 as u16,
path_bbox.y0 as u16,
path_bbox.x1.ceil() as u16,
path_bbox.y1.ceil() as u16,
);
if let Some(existing) = self.clip_stack.last() {
bbox = bbox.intersect(existing.bbox);
} else {
bbox.x1 = bbox.x1.min(strip_generator.width());
bbox.y1 = bbox.y1.min(strip_generator.height());
}
let clip_data = ClipData {
alpha_start,
strip_start,
bbox,
};
let existing_clip = self
.clip_stack
.last()
.map(|c| c.to_path_data_ref(&self.storage));
strip_generator.generate_filled_path(
clip_path,
fill_rule,
transform,
aliasing_threshold,
&mut self.temp_storage,
existing_clip,
);
self.storage.extend(&self.temp_storage);
self.clip_stack.push(clip_data);
}
#[inline]
pub fn pop_clip(&mut self) {
let data = self.clip_stack.pop().expect("clip stack underflowed");
self.storage.strips.truncate(data.strip_start as usize);
self.storage.alphas.truncate(data.alpha_start as usize);
}
}
fn control_point_bbox(path: &BezPath, transform: Affine) -> Rect {
let mut bbox = Rect::new(
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
);
for el in path.iter() {
match el {
PathEl::MoveTo(p) | PathEl::LineTo(p) => {
bbox = bbox.union_pt(transform * p);
}
PathEl::QuadTo(p1, p2) => {
bbox = bbox.union_pt(transform * p1);
bbox = bbox.union_pt(transform * p2);
}
PathEl::CurveTo(p1, p2, p3) => {
bbox = bbox.union_pt(transform * p1);
bbox = bbox.union_pt(transform * p2);
bbox = bbox.union_pt(transform * p3);
}
PathEl::ClosePath => {}
}
}
bbox
}
#[derive(Clone, Copy, Debug)]
pub struct PathDataRef<'a> {
pub strips: &'a [Strip],
pub alphas: &'a [u8],
pub bbox: RectU16,
}
pub fn intersect(
level: Level,
path_1: PathDataRef<'_>,
path_2: PathDataRef<'_>,
target: &mut StripStorage,
) {
dispatch!(level, simd => intersect_impl(simd, path_1, path_2, target));
}
fn intersect_impl<S: Simd>(
simd: S,
path_1: PathDataRef<'_>,
path_2: PathDataRef<'_>,
target: &mut StripStorage,
) {
if path_1.strips.is_empty() || path_2.strips.is_empty() {
return;
}
let mut cur_y = path_1.strips[0].strip_y().min(path_2.strips[0].strip_y());
let end_y = path_1.strips[path_1.strips.len() - 1]
.strip_y()
.min(path_2.strips[path_2.strips.len() - 1].strip_y());
let mut path_1_idx = 0;
let mut path_2_idx = 0;
let mut strip_state = None;
while cur_y <= end_y {
let mut p1_iter = RowIterator::new(path_1, &mut path_1_idx, cur_y);
let mut p2_iter = RowIterator::new(path_2, &mut path_2_idx, cur_y);
let mut p1_region = p1_iter.next();
let mut p2_region = p2_iter.next();
while let (Some(region_1), Some(region_2)) = (p1_region, p2_region) {
match region_1.overlap_relationship(®ion_2) {
OverlapRelationship::Advance(advance) => {
match advance {
Advance::Left => p1_region = p1_iter.next(),
Advance::Right => p2_region = p2_iter.next(),
};
continue;
}
OverlapRelationship::Overlap(overlap) => {
match (region_1, region_2) {
(Region::Fill(_), Region::Fill(_)) => {
flush_strip(&mut strip_state, &mut target.strips, cur_y);
start_strip(&mut strip_state, &target.alphas, overlap.end, true);
}
(Region::Strip(s), Region::Fill(_))
| (Region::Fill(_), Region::Strip(s)) => {
if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
{
flush_strip(&mut strip_state, &mut target.strips, cur_y);
start_strip(&mut strip_state, &target.alphas, overlap.start, false);
}
let s_alphas = &s.alphas[(overlap.start - s.start) as usize * 4..]
[..overlap.width() as usize * 4];
target.alphas.extend_from_slice(s_alphas);
}
(Region::Strip(s_region_1), Region::Strip(s_region_2)) => {
if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
{
flush_strip(&mut strip_state, &mut target.strips, cur_y);
start_strip(&mut strip_state, &target.alphas, overlap.start, false);
}
let num_blocks = overlap.width() / Tile::HEIGHT;
let s1_alphas = s_region_1.alphas
[(overlap.start - s_region_1.start) as usize * 4..]
.chunks_exact(16)
.take(num_blocks as usize);
let s2_alphas = s_region_2.alphas
[(overlap.start - s_region_2.start) as usize * 4..]
.chunks_exact(16)
.take(num_blocks as usize);
for (s1_alpha, s2_alpha) in s1_alphas.zip(s2_alphas) {
let s1 = u8x16::from_slice(simd, s1_alpha);
let s2 = u8x16::from_slice(simd, s2_alpha);
let res = simd.narrow_u16x16(normalized_mul_u8x16(s1, s2));
target.alphas.extend(res.as_slice());
}
}
}
match overlap.advance {
Advance::Left => p1_region = p1_iter.next(),
Advance::Right => p2_region = p2_iter.next(),
};
}
}
}
flush_strip(&mut strip_state, &mut target.strips, cur_y);
cur_y += 1;
}
if !target.strips.last().is_some_and(Strip::is_sentinel) {
target.strips.push(Strip::new(
u16::MAX,
end_y * Tile::HEIGHT,
target.alphas.len() as u32,
false,
));
}
}
struct Overlap {
start: u16,
end: u16,
advance: Advance,
}
impl Overlap {
fn width(&self) -> u16 {
self.end - self.start
}
}
enum Advance {
Left,
Right,
}
enum OverlapRelationship {
Advance(Advance),
Overlap(Overlap),
}
#[derive(Debug, Clone, Copy)]
struct FillRegion {
start: u16,
width: u16,
}
#[derive(Debug, Clone, Copy)]
struct StripRegion<'a> {
start: u16,
width: u16,
alphas: &'a [u8],
}
#[derive(Debug, Clone, Copy)]
enum Region<'a> {
Fill(FillRegion),
Strip(StripRegion<'a>),
}
impl Region<'_> {
#[inline(always)]
fn start(&self) -> u16 {
match self {
Region::Fill(fill) => fill.start,
Region::Strip(strip) => strip.start,
}
}
#[inline(always)]
fn width(&self) -> u16 {
match self {
Region::Fill(fill) => fill.width,
Region::Strip(strip) => strip.width,
}
}
#[inline(always)]
fn end(&self) -> u16 {
self.start() + self.width()
}
fn overlap_relationship(&self, other: &Region<'_>) -> OverlapRelationship {
if self.end() <= other.start() {
OverlapRelationship::Advance(Advance::Left)
} else if self.start() >= other.end() {
OverlapRelationship::Advance(Advance::Right)
} else {
let start = self.start().max(other.start());
let end = self.end().min(other.end());
let shift = if self.end() <= other.end() {
Advance::Left
} else {
Advance::Right
};
OverlapRelationship::Overlap(Overlap {
advance: shift,
start,
end,
})
}
}
}
struct RowIterator<'a> {
input: PathDataRef<'a>,
strip_y: u16,
cur_idx: &'a mut usize,
on_strip: bool,
}
impl<'a> RowIterator<'a> {
fn new(input: PathDataRef<'a>, cur_idx: &'a mut usize, strip_y: u16) -> Self {
while input.strips[*cur_idx].strip_y() < strip_y {
*cur_idx += 1;
}
Self {
input,
cur_idx,
strip_y,
on_strip: true,
}
}
#[inline(always)]
fn cur_strip(&self) -> &Strip {
&self.input.strips[*self.cur_idx]
}
#[inline(always)]
fn next_strip(&self) -> &Strip {
&self.input.strips[*self.cur_idx + 1]
}
#[inline(always)]
fn cur_strip_width(&self) -> u16 {
let cur = self.cur_strip();
let next = self.next_strip();
((next.alpha_idx() - cur.alpha_idx()) / Tile::HEIGHT as u32) as u16
}
#[inline(always)]
fn cur_strip_alphas(&self) -> &'a [u8] {
let cur = self.cur_strip();
let next = self.next_strip();
&self.input.alphas[cur.alpha_idx() as usize..next.alpha_idx() as usize]
}
fn cur_strip_fill_area(&self) -> Option<FillRegion> {
let next = self.next_strip();
if next.fill_gap() {
let cur = self.cur_strip();
let x = cur.x + self.cur_strip_width();
let width = next.x - x;
(width > 0).then_some(FillRegion { start: x, width })
} else {
None
}
}
}
impl<'a> Iterator for RowIterator<'a> {
type Item = Region<'a>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if !self.on_strip {
self.on_strip = true;
if let Some(fill_area) = self.cur_strip_fill_area() {
*self.cur_idx += 1;
return Some(Region::Fill(fill_area));
} else {
*self.cur_idx += 1;
}
}
self.on_strip = false;
if self.cur_strip().is_sentinel() || self.cur_strip().strip_y() != self.strip_y {
return None;
}
let x = self.cur_strip().x;
let width = self.cur_strip_width();
let alphas = self.cur_strip_alphas();
Some(Region::Strip(StripRegion {
start: x,
width,
alphas,
}))
}
}
struct StripState {
x: u16,
alpha_idx: u32,
fill_gap: bool,
}
fn flush_strip(strip_state: &mut Option<StripState>, strips: &mut Vec<Strip>, cur_y: u16) {
if let Some(state) = core::mem::take(strip_state) {
strips.push(Strip::new(
state.x,
cur_y * Tile::HEIGHT,
state.alpha_idx,
state.fill_gap,
));
}
}
#[inline(always)]
fn start_strip(strip_data: &mut Option<StripState>, alphas: &[u8], x: u16, fill_gap: bool) {
*strip_data = Some(StripState {
x,
alpha_idx: alphas.len() as u32,
fill_gap,
});
}
fn should_create_new_strip(
strip_state: &Option<StripState>,
alphas: &[u8],
overlap_start: u16,
) -> bool {
strip_state.as_ref().is_none_or(|state| {
let width = ((alphas.len() as u32 - state.alpha_idx) / Tile::HEIGHT as u32) as u16;
let strip_end = state.x + width;
strip_end < overlap_start - 1
})
}
#[cfg(test)]
mod tests {
use crate::clip::{PathDataRef, Region, RowIterator, intersect};
use crate::geometry::RectU16;
use crate::strip::Strip;
use crate::strip_generator::StripStorage;
use crate::tile::Tile;
use fearless_simd::Level;
use std::vec;
#[test]
fn intersect_partly_overlapping_strips() {
let path_1 = StripBuilder::new().add_strip(0, 0, 32, false).finish();
let path_2 = StripBuilder::new().add_strip(8, 0, 44, false).finish();
let expected = StripBuilder::new().add_strip(8, 0, 32, false).finish();
run_test(expected, path_1, path_2);
}
#[test]
fn intersect_multiple_overlapping_strips() {
let path_1 = StripBuilder::new()
.add_strip(0, 1, 4, false)
.add_strip(12, 1, 20, true)
.add_strip(28, 1, 32, false)
.add_strip(44, 1, 52, true)
.finish();
let path_2 = StripBuilder::new()
.add_strip(4, 1, 8, false)
.add_strip(16, 1, 20, true)
.add_strip(24, 1, 28, false)
.add_strip(32, 1, 36, false)
.add_strip(44, 1, 48, true)
.finish();
let expected = StripBuilder::new()
.add_strip(4, 1, 8, false)
.add_strip(12, 1, 20, true)
.add_strip(32, 1, 36, false)
.add_strip(44, 1, 48, true)
.finish();
run_test(expected, path_1, path_2);
}
#[test]
fn multiple_rows() {
let path_1 = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(16, 0, 20, true)
.add_strip(4, 1, 8, false)
.add_strip(12, 1, 24, true)
.add_strip(4, 2, 8, false)
.add_strip(16, 2, 32, true)
.finish();
let path_2 = StripBuilder::new()
.add_strip(0, 2, 4, false)
.add_strip(16, 2, 24, true)
.add_strip(8, 3, 12, false)
.add_strip(16, 3, 28, true)
.finish();
let expected = StripBuilder::new()
.add_strip(4, 2, 8, false)
.add_strip(16, 2, 24, true)
.finish();
run_test(expected, path_1, path_2);
}
#[test]
fn alpha_buffer_correct_width() {
let path_1 = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(0, 1, 12, false)
.finish();
let path_2 = StripBuilder::new()
.add_strip(4, 0, 8, false)
.add_strip(0, 1, 4, false)
.add_strip(12, 1, 16, true)
.finish();
let expected = StripBuilder::new().add_strip(0, 1, 12, false).finish();
run_test(expected, path_1, path_2);
}
#[test]
fn row_iterator_abort_next_line() {
let path_1 = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(0, 1, 4, false)
.finish();
let path_ref = PathDataRef {
strips: &path_1.strips,
alphas: &path_1.alphas,
bbox: RectU16::new(0, 0, u16::MAX, u16::MAX),
};
let mut idx = 0;
let mut iter = RowIterator::new(path_ref, &mut idx, 0);
assert!(iter.next().is_some());
assert!(iter.next().is_none());
}
#[test]
fn row_iterator_sentinel_fill_gap() {
let path = StripBuilder::new()
.add_strip(0, 0, Tile::WIDTH, false)
.finish_with_fill_gap_sentinel();
let path_ref = path_ref(&path);
let mut idx = 0;
let mut iter = RowIterator::new(path_ref, &mut idx, 0);
assert_strip_region(iter.next(), 0, Tile::WIDTH);
assert_fill_region(iter.next(), Tile::WIDTH, u16::MAX - Tile::WIDTH);
assert!(iter.next().is_none());
}
#[test]
fn intersect_strip_with_sentinel_fill_gap() {
let path_1 = StripBuilder::new()
.add_strip(0, 0, Tile::WIDTH, false)
.finish_with_fill_gap_sentinel();
let path_2 = StripBuilder::new().add_strip(8, 0, 12, false).finish();
let expected = StripBuilder::new().add_strip(8, 0, 12, false).finish();
run_test(expected, path_1, path_2);
}
#[test]
fn intersect_two_sentinel_fill_gaps() {
let path_1 = StripBuilder::new()
.add_strip(0, 0, 8, false)
.finish_with_fill_gap_sentinel();
let path_2 = StripBuilder::new()
.add_strip(4, 0, 12, false)
.finish_with_fill_gap_sentinel();
let expected = StripBuilder::new()
.add_strip(4, 0, 12, false)
.finish_with_fill_gap_sentinel();
run_test(expected, path_1, path_2);
}
#[test]
fn row_iterator_fill_gap_stops_at_row_boundary() {
let mut path = StripBuilder::new()
.add_strip(0, 0, 4, false)
.finish_with_fill_gap_sentinel();
let idx = path.alphas.len();
path.strips
.push(Strip::new(0, Tile::HEIGHT, idx as u32, false));
path.alphas
.extend([0; Tile::HEIGHT as usize * Tile::WIDTH as usize]);
path.strips.push(Strip::new(
u16::MAX,
Tile::HEIGHT,
path.alphas.len() as u32,
false,
));
let path_ref = path_ref(&path);
let mut idx = 0;
let mut iter = RowIterator::new(path_ref, &mut idx, 0);
assert_strip_region(iter.next(), 0, 4);
assert_fill_region(iter.next(), 4, u16::MAX - 4);
assert!(iter.next().is_none());
let mut iter = RowIterator::new(path_ref, &mut idx, 1);
assert_strip_region(iter.next(), 0, Tile::WIDTH);
assert!(iter.next().is_none());
}
#[test]
fn row_iterator_adjacent_unmerged_strips_no_fill() {
let path = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(4, 0, 8, false)
.finish();
let path_ref = path_ref(&path);
let mut idx = 0;
let mut iter = RowIterator::new(path_ref, &mut idx, 0);
assert_strip_region(iter.next(), 0, 4);
assert_strip_region(iter.next(), 4, 4);
assert!(iter.next().is_none());
}
#[test]
fn row_iterator_adjacent_unmerged_strips_with_fill_gap() {
let path = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(4, 0, 8, true)
.finish();
let path_ref = path_ref(&path);
let mut idx = 0;
let mut iter = RowIterator::new(path_ref, &mut idx, 0);
assert_strip_region(iter.next(), 0, 4);
assert_strip_region(iter.next(), 4, 4);
assert!(iter.next().is_none());
}
#[test]
fn intersect_adjacent_unmerged_strips() {
let path = StripBuilder::new()
.add_strip(0, 0, 4, false)
.add_strip(4, 0, 8, true)
.finish();
let cover = StripBuilder::new().add_strip(0, 0, 8, false).finish();
let expected = StripBuilder::new().add_strip(0, 0, 8, false).finish();
run_test(expected, path, cover);
}
#[test]
fn row_iterator_zero_width_alpha_region() {
let mut path = StripStorage::default();
path.strips.push(Strip::new(8, 0, 0, false));
path.strips.push(Strip::new(16, 0, 0, true));
path.strips.push(Strip::new(u16::MAX, 0, 0, false));
let path_ref = path_ref(&path);
let mut idx = 0;
let iter = RowIterator::new(path_ref, &mut idx, 0);
assert_eq!(iter.cur_strip_width(), 0);
let fill = iter.cur_strip_fill_area().unwrap();
assert_eq!(fill.start, 8);
assert_eq!(fill.width, 8);
let cover = StripBuilder::new().add_strip(0, 0, 20, false).finish();
let expected = StripBuilder::new().add_strip(8, 0, 16, false).finish();
run_test(expected, path, cover);
}
fn run_test(expected: StripStorage, path_1: StripStorage, path_2: StripStorage) {
let mut write_target = StripStorage::default();
let path_1 = path_ref(&path_1);
let path_2 = path_ref(&path_2);
intersect(Level::new(), path_1, path_2, &mut write_target);
assert_eq!(write_target, expected);
}
fn path_ref(path: &StripStorage) -> PathDataRef<'_> {
PathDataRef {
strips: &path.strips,
alphas: &path.alphas,
bbox: RectU16::new(0, 0, u16::MAX, u16::MAX),
}
}
fn assert_strip_region(region: Option<Region<'_>>, start: u16, width: u16) {
match region {
Some(Region::Strip(strip)) => {
assert_eq!(strip.start, start);
assert_eq!(strip.width, width);
assert_eq!(strip.alphas.len(), (width * Tile::HEIGHT) as usize);
}
other => panic!("expected strip region, got {other:?}"),
}
}
fn assert_fill_region(region: Option<Region<'_>>, start: u16, width: u16) {
match region {
Some(Region::Fill(fill)) => {
assert_eq!(fill.start, start);
assert_eq!(fill.width, width);
}
other => panic!("expected fill region, got {other:?}"),
}
}
struct StripBuilder {
storage: StripStorage,
}
impl StripBuilder {
fn new() -> Self {
Self {
storage: StripStorage::default(),
}
}
fn add_strip(self, x: u16, strip_y: u16, end: u16, fill_gap: bool) -> Self {
let width = end - x;
self.add_strip_with(
x,
strip_y,
end,
fill_gap,
&vec![0; (width * Tile::HEIGHT) as usize],
)
}
fn add_strip_with(
mut self,
x: u16,
strip_y: u16,
end: u16,
fill_gap: bool,
alphas: &[u8],
) -> Self {
let width = end - x;
assert_eq!(alphas.len(), (width * Tile::HEIGHT) as usize);
let idx = self.storage.alphas.len();
self.storage
.strips
.push(Strip::new(x, strip_y * Tile::HEIGHT, idx as u32, fill_gap));
self.storage.alphas.extend_from_slice(alphas);
self
}
fn finish(mut self) -> StripStorage {
let last_y = self.storage.strips.last().unwrap().y;
let idx = self.storage.alphas.len();
self.storage
.strips
.push(Strip::new(u16::MAX, last_y, idx as u32, false));
self.storage
}
fn finish_with_fill_gap_sentinel(mut self) -> StripStorage {
let last_y = self.storage.strips.last().unwrap().y;
let idx = self.storage.alphas.len();
self.storage
.strips
.push(Strip::new(u16::MAX, last_y, idx as u32, true));
self.storage
}
}
}