Skip to main content

vello_common/
clip.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Managing clipping state.
5
6use crate::kurbo::{Affine, BezPath};
7use crate::strip::Strip;
8use crate::strip_generator::{GenerationMode, StripGenerator, StripStorage};
9use crate::tile::Tile;
10use crate::util::normalized_mul_u8x16;
11use alloc::vec;
12use alloc::vec::Vec;
13use fearless_simd::{Level, Simd, SimdBase, dispatch, u8x16};
14use peniko::Fill;
15
16#[derive(Debug)]
17struct ClipData {
18    alpha_start: u32,
19    strip_start: u32,
20}
21
22impl ClipData {
23    fn to_path_data_ref<'a>(&self, storage: &'a StripStorage) -> PathDataRef<'a> {
24        PathDataRef {
25            strips: storage
26                .strips
27                .get(self.strip_start as usize..)
28                .unwrap_or(&[]),
29            alphas: storage
30                .alphas
31                .get(self.alpha_start as usize..)
32                .unwrap_or(&[]),
33        }
34    }
35}
36
37/// A context for managing clip stacks.
38#[derive(Debug)]
39pub struct ClipContext {
40    storage: StripStorage,
41    temp_storage: StripStorage,
42    clip_stack: Vec<ClipData>,
43}
44
45impl Default for ClipContext {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl ClipContext {
52    /// Create a new clip context.
53    #[inline]
54    pub fn new() -> Self {
55        let mut main_storage = StripStorage::default();
56        main_storage.set_generation_mode(GenerationMode::Append);
57        Self {
58            storage: main_storage,
59            temp_storage: Default::default(),
60            clip_stack: vec![],
61        }
62    }
63
64    /// Reset the clip context.
65    #[inline]
66    pub fn reset(&mut self) {
67        self.clip_stack.clear();
68        self.storage.clear();
69        self.temp_storage.clear();
70    }
71
72    /// Get the data of the current clip path.
73    #[inline]
74    pub fn get(&self) -> Option<PathDataRef<'_>> {
75        self.clip_stack
76            .last()
77            .map(|c| c.to_path_data_ref(&self.storage))
78    }
79
80    /// Push a new clip path to the stack.
81    #[inline]
82    pub fn push_clip(
83        &mut self,
84        clip_path: &BezPath,
85        strip_generator: &mut StripGenerator,
86        fill_rule: Fill,
87        transform: Affine,
88        aliasing_threshold: Option<u8>,
89    ) {
90        self.temp_storage.clear();
91
92        let alpha_start = self.storage.alphas.len() as u32;
93        let strip_start = self.storage.strips.len() as u32;
94
95        let clip_data = ClipData {
96            alpha_start,
97            strip_start,
98        };
99
100        let existing_clip = self
101            .clip_stack
102            .last()
103            .map(|c| c.to_path_data_ref(&self.storage));
104
105        strip_generator.generate_filled_path(
106            clip_path,
107            fill_rule,
108            transform,
109            aliasing_threshold,
110            &mut self.temp_storage,
111            existing_clip,
112        );
113
114        self.storage.extend(&self.temp_storage);
115        self.clip_stack.push(clip_data);
116    }
117
118    /// Pop the least recent clip path.
119    #[inline]
120    pub fn pop_clip(&mut self) {
121        let data = self.clip_stack.pop().expect("clip stack underflowed");
122        self.storage.strips.truncate(data.strip_start as usize);
123        self.storage.alphas.truncate(data.alpha_start as usize);
124    }
125}
126
127/// Borrowed data of a stripped path.
128#[derive(Clone, Copy, Debug)]
129pub struct PathDataRef<'a> {
130    /// The strips.
131    pub strips: &'a [Strip],
132    /// The alpha buffer.
133    pub alphas: &'a [u8],
134}
135
136/// Compute the sparse strips representation of a path that results
137/// from intersecting the two input paths. This can be used to implement
138/// clip paths.
139pub fn intersect(
140    level: Level,
141    path_1: PathDataRef<'_>,
142    path_2: PathDataRef<'_>,
143    target: &mut StripStorage,
144) {
145    dispatch!(level, simd => intersect_impl(simd, path_1, path_2, target));
146}
147
148/// The implementation of the clipping algorithm using sparse strips. Conceptually, it is relatively
149/// simple: We iterate over each strip and fill region of the two paths in lock step and determine
150/// all overlaps between the two. For each overlap, we proceed depending on what kind of region
151/// we have in the first path and the second one.
152/// - In case we have two fill regions, the overlap region will also be filled.
153/// - In case we have one strip and one fill region, the overlap region will copy the alpha mask of the strip region.
154/// - Finally, if we have two strip regions, we combine the alpha masks of both.
155/// - All regions that are not filled in either path are simply ignored.
156///
157/// This is all that this method does. It just looks more complicated as the logic for iterating
158/// in lock step is a bit tricky.
159fn intersect_impl<S: Simd>(
160    simd: S,
161    path_1: PathDataRef<'_>,
162    path_2: PathDataRef<'_>,
163    target: &mut StripStorage,
164) {
165    // In case either path is empty, the clip path should be empty.
166    if path_1.strips.is_empty() || path_2.strips.is_empty() {
167        return;
168    }
169
170    // Ignore any y values that are outside the bounding box of either of the two paths, as
171    // those are guaranteed to have neither fill nor strip regions.
172    let mut cur_y = path_1.strips[0].strip_y().min(path_2.strips[0].strip_y());
173    let end_y = path_1.strips[path_1.strips.len() - 1]
174        .strip_y()
175        .min(path_2.strips[path_2.strips.len() - 1].strip_y());
176
177    let mut path_1_idx = 0;
178    let mut path_2_idx = 0;
179    let mut strip_state = None;
180
181    // Iterate over each strip row and handle them.
182    while cur_y <= end_y {
183        // For each row, we create two iterators that alternatingly yield the strips and fill
184        // regions in that row, until the last strip has been reached.
185        let mut p1_iter = RowIterator::new(path_1, &mut path_1_idx, cur_y);
186        let mut p2_iter = RowIterator::new(path_2, &mut path_2_idx, cur_y);
187
188        let mut p1_region = p1_iter.next();
189        let mut p2_region = p2_iter.next();
190
191        // If at least one region is none, it means that we reached the end of the row
192        // for that path, meaning that we exceeded the bounding box of that path and no
193        // additional strips should be generated for that row, even if the other path might
194        // still have more strips left. They will all be clipped away. So only consider it
195        // if both paths have a region left.
196        while let (Some(region_1), Some(region_2)) = (p1_region, p2_region) {
197            match region_1.overlap_relationship(&region_2) {
198                // This means there is no overlap between the regions, so we need to advance
199                // the iterator of the region that is further behind.
200                OverlapRelationship::Advance(advance) => {
201                    match advance {
202                        Advance::Left => p1_region = p1_iter.next(),
203                        Advance::Right => p2_region = p2_iter.next(),
204                    };
205
206                    continue;
207                }
208                // We have an overlap!
209                OverlapRelationship::Overlap(overlap) => {
210                    match (region_1, region_2) {
211                        // Both regions are a fill. Flush the current strip and start a new
212                        // one at the end of the overlap region setting `fill_gap` to true,
213                        // so that the whole area before that will be filled with a sparse
214                        // fill.
215                        (Region::Fill(_), Region::Fill(_)) => {
216                            flush_strip(&mut strip_state, &mut target.strips, cur_y);
217                            start_strip(&mut strip_state, &target.alphas, overlap.end, true);
218                        }
219                        // One fill one strip, so we simply use the alpha mask from the strip region.
220                        (Region::Strip(s), Region::Fill(_))
221                        | (Region::Fill(_), Region::Strip(s)) => {
222                            // If possible, don't create a new strip but just extend the current one.
223                            if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
224                            {
225                                flush_strip(&mut strip_state, &mut target.strips, cur_y);
226                                start_strip(&mut strip_state, &target.alphas, overlap.start, false);
227                            }
228
229                            let s_alphas = &s.alphas[(overlap.start - s.start) as usize * 4..]
230                                [..overlap.width() as usize * 4];
231                            target.alphas.extend_from_slice(s_alphas);
232                        }
233                        // Two strips, we need to multiply the opacity masks from both paths.
234                        (Region::Strip(s_region_1), Region::Strip(s_region_2)) => {
235                            // Once again, only create a new strip if we can't extend the current one.
236                            if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
237                            {
238                                flush_strip(&mut strip_state, &mut target.strips, cur_y);
239                                start_strip(&mut strip_state, &target.alphas, overlap.start, false);
240                            }
241
242                            let num_blocks = overlap.width() / Tile::HEIGHT;
243
244                            // Get the right alpha values for the specific position.
245                            let s1_alphas = s_region_1.alphas
246                                [(overlap.start - s_region_1.start) as usize * 4..]
247                                .chunks_exact(16)
248                                .take(num_blocks as usize);
249                            let s2_alphas = s_region_2.alphas
250                                [(overlap.start - s_region_2.start) as usize * 4..]
251                                .chunks_exact(16)
252                                .take(num_blocks as usize);
253
254                            for (s1_alpha, s2_alpha) in s1_alphas.zip(s2_alphas) {
255                                let s1 = u8x16::from_slice(simd, s1_alpha);
256                                let s2 = u8x16::from_slice(simd, s2_alpha);
257
258                                // Combine them.
259                                let res = simd.narrow_u16x16(normalized_mul_u8x16(s1, s2));
260                                target.alphas.extend(res.as_slice());
261                            }
262                        }
263                    }
264
265                    // Advance the iterator of the path whose region's end is further behind.
266                    match overlap.advance {
267                        Advance::Left => p1_region = p1_iter.next(),
268                        Advance::Right => p2_region = p2_iter.next(),
269                    };
270                }
271            }
272        }
273
274        // Flush the strip before advancing to the next strip row.
275        flush_strip(&mut strip_state, &mut target.strips, cur_y);
276        cur_y += 1;
277    }
278
279    // Push the sentinel strip.
280    target.strips.push(Strip::new(
281        u16::MAX,
282        end_y * Tile::HEIGHT,
283        target.alphas.len() as u32,
284        false,
285    ));
286}
287
288/// An overlap between two regions.
289struct Overlap {
290    /// The start x coordinate.
291    start: u16,
292    /// The end x coordinate.
293    end: u16,
294    /// Whether the left or right region iterator should be advanced next.
295    advance: Advance,
296}
297
298impl Overlap {
299    fn width(&self) -> u16 {
300        self.end - self.start
301    }
302}
303
304enum Advance {
305    Left,
306    Right,
307}
308
309/// The relationship between two regions.
310enum OverlapRelationship {
311    /// There is no overlap between the regions, advance the region iterator on the given side.
312    Advance(Advance),
313    /// There is an overlap between the regions.
314    Overlap(Overlap),
315}
316
317#[derive(Debug, Clone, Copy)]
318struct FillRegion {
319    start: u16,
320    width: u16,
321}
322
323#[derive(Debug, Clone, Copy)]
324struct StripRegion<'a> {
325    start: u16,
326    width: u16,
327    alphas: &'a [u8],
328}
329
330#[derive(Debug, Clone, Copy)]
331enum Region<'a> {
332    Fill(FillRegion),
333    Strip(StripRegion<'a>),
334}
335
336impl Region<'_> {
337    #[inline(always)]
338    fn start(&self) -> u16 {
339        match self {
340            Region::Fill(fill) => fill.start,
341            Region::Strip(strip) => strip.start,
342        }
343    }
344
345    #[inline(always)]
346    fn width(&self) -> u16 {
347        match self {
348            Region::Fill(fill) => fill.width,
349            Region::Strip(strip) => strip.width,
350        }
351    }
352
353    #[inline(always)]
354    fn end(&self) -> u16 {
355        self.start() + self.width()
356    }
357
358    fn overlap_relationship(&self, other: &Region<'_>) -> OverlapRelationship {
359        if self.end() <= other.start() {
360            OverlapRelationship::Advance(Advance::Left)
361        } else if self.start() >= other.end() {
362            OverlapRelationship::Advance(Advance::Right)
363        } else {
364            let start = self.start().max(other.start());
365            let end = self.end().min(other.end());
366
367            let shift = if self.end() <= other.end() {
368                Advance::Left
369            } else {
370                Advance::Right
371            };
372
373            OverlapRelationship::Overlap(Overlap {
374                advance: shift,
375                start,
376                end,
377            })
378        }
379    }
380}
381
382/// An iterator of strip and fill regions of a single strip row.
383struct RowIterator<'a> {
384    /// The path in question.
385    input: PathDataRef<'a>,
386    /// The strip row we want to iterate over.
387    strip_y: u16,
388    /// The index of the current strip.
389    cur_idx: &'a mut usize,
390    /// Whether the iterator should yield a strip next or not.
391    /// When iterating over a row, we alternate between emitting strips and filled regions (unless
392    /// the region between two strips is not filled), so this flag acts as a toggle to store what
393    /// should be yielded next.
394    on_strip: bool,
395}
396
397impl<'a> RowIterator<'a> {
398    fn new(input: PathDataRef<'a>, cur_idx: &'a mut usize, strip_y: u16) -> Self {
399        // Forward the index until we have found the right strip.
400        while input.strips[*cur_idx].strip_y() < strip_y {
401            *cur_idx += 1;
402        }
403
404        Self {
405            input,
406            cur_idx,
407            strip_y,
408            on_strip: true,
409        }
410    }
411
412    #[inline(always)]
413    fn cur_strip(&self) -> &Strip {
414        &self.input.strips[*self.cur_idx]
415    }
416
417    #[inline(always)]
418    fn next_strip(&self) -> &Strip {
419        &self.input.strips[*self.cur_idx + 1]
420    }
421
422    #[inline(always)]
423    fn cur_strip_width(&self) -> u16 {
424        let cur = self.cur_strip();
425        let next = self.next_strip();
426        ((next.alpha_idx() - cur.alpha_idx()) / Tile::HEIGHT as u32) as u16
427    }
428
429    #[inline(always)]
430    fn cur_strip_alphas(&self) -> &'a [u8] {
431        let cur = self.cur_strip();
432        let next = self.next_strip();
433        &self.input.alphas[cur.alpha_idx() as usize..next.alpha_idx() as usize]
434    }
435
436    fn cur_strip_fill_area(&self) -> Option<FillRegion> {
437        let next = self.next_strip();
438
439        // Note that if the next strip happens to be on the next line, it will always have
440        // zero winding so we don't need to special case this.
441        if next.fill_gap() {
442            let cur = self.cur_strip();
443            let x = cur.x + self.cur_strip_width();
444            let width = next.x - x;
445
446            Some(FillRegion { start: x, width })
447        } else {
448            None
449        }
450    }
451}
452
453impl<'a> Iterator for RowIterator<'a> {
454    type Item = Region<'a>;
455
456    #[inline(always)]
457    fn next(&mut self) -> Option<Self::Item> {
458        // If we are currently not on a strip, we want to yield a filled region in case there is one.
459        if !self.on_strip {
460            // Flip boolean flag so we will yield a strip in the next iteration.
461            self.on_strip = true;
462
463            // if we have a filled area, yield it and return. Otherwise, do nothing and we will
464            // instead yield the next strip below. In any case, we need to advance the current index
465            // so that we point to the next strip now.
466            if let Some(fill_area) = self.cur_strip_fill_area() {
467                *self.cur_idx += 1;
468
469                return Some(Region::Fill(fill_area));
470            } else {
471                *self.cur_idx += 1;
472            }
473        }
474
475        // If we reached this point, we will yield a strip this iteration, so toggle the flag
476        // so that in the next iteration, we yield a filled region instead.
477        self.on_strip = false;
478
479        // If the current strip is sentinel or not within our target row, terminate.
480        if self.cur_strip().is_sentinel() || self.cur_strip().strip_y() != self.strip_y {
481            return None;
482        }
483
484        // Calculate the dimensions of the strip and yield it.
485        let x = self.cur_strip().x;
486        let width = self.cur_strip_width();
487        let alphas = self.cur_strip_alphas();
488
489        Some(Region::Strip(StripRegion {
490            start: x,
491            width,
492            alphas,
493        }))
494    }
495}
496
497/// The data of the current strip we are building.
498struct StripState {
499    x: u16,
500    alpha_idx: u32,
501    fill_gap: bool,
502}
503
504fn flush_strip(strip_state: &mut Option<StripState>, strips: &mut Vec<Strip>, cur_y: u16) {
505    if let Some(state) = core::mem::take(strip_state) {
506        strips.push(Strip::new(
507            state.x,
508            cur_y * Tile::HEIGHT,
509            state.alpha_idx,
510            state.fill_gap,
511        ));
512    }
513}
514
515#[inline(always)]
516fn start_strip(strip_data: &mut Option<StripState>, alphas: &[u8], x: u16, fill_gap: bool) {
517    *strip_data = Some(StripState {
518        x,
519        alpha_idx: alphas.len() as u32,
520        fill_gap,
521    });
522}
523
524fn should_create_new_strip(
525    strip_state: &Option<StripState>,
526    alphas: &[u8],
527    overlap_start: u16,
528) -> bool {
529    // Returns false in case we can append to the currently built strip.
530    strip_state.as_ref().is_none_or(|state| {
531        let width = ((alphas.len() as u32 - state.alpha_idx) / Tile::HEIGHT as u32) as u16;
532        let strip_end = state.x + width;
533
534        strip_end < overlap_start - 1
535    })
536}
537
538#[cfg(test)]
539mod tests {
540    use crate::clip::{PathDataRef, RowIterator, intersect};
541    use crate::strip::Strip;
542    use crate::strip_generator::StripStorage;
543    use crate::tile::Tile;
544    use fearless_simd::Level;
545    use std::vec;
546
547    #[test]
548    fn intersect_partly_overlapping_strips() {
549        let path_1 = StripBuilder::new().add_strip(0, 0, 32, false).finish();
550
551        let path_2 = StripBuilder::new().add_strip(8, 0, 44, false).finish();
552
553        let expected = StripBuilder::new().add_strip(8, 0, 32, false).finish();
554
555        run_test(expected, path_1, path_2);
556    }
557
558    #[test]
559    fn intersect_multiple_overlapping_strips() {
560        let path_1 = StripBuilder::new()
561            .add_strip(0, 1, 4, false)
562            .add_strip(12, 1, 20, true)
563            .add_strip(28, 1, 32, false)
564            .add_strip(44, 1, 52, true)
565            .finish();
566
567        let path_2 = StripBuilder::new()
568            .add_strip(4, 1, 8, false)
569            .add_strip(16, 1, 20, true)
570            .add_strip(24, 1, 28, false)
571            .add_strip(32, 1, 36, false)
572            .add_strip(44, 1, 48, true)
573            .finish();
574
575        let expected = StripBuilder::new()
576            .add_strip(4, 1, 8, false)
577            .add_strip(12, 1, 20, true)
578            .add_strip(32, 1, 36, false)
579            .add_strip(44, 1, 48, true)
580            .finish();
581
582        run_test(expected, path_1, path_2);
583    }
584
585    #[test]
586    fn multiple_rows() {
587        let path_1 = StripBuilder::new()
588            .add_strip(0, 0, 4, false)
589            .add_strip(16, 0, 20, true)
590            .add_strip(4, 1, 8, false)
591            .add_strip(12, 1, 24, true)
592            .add_strip(4, 2, 8, false)
593            .add_strip(16, 2, 32, true)
594            .finish();
595
596        let path_2 = StripBuilder::new()
597            .add_strip(0, 2, 4, false)
598            .add_strip(16, 2, 24, true)
599            .add_strip(8, 3, 12, false)
600            .add_strip(16, 3, 28, true)
601            .finish();
602
603        let expected = StripBuilder::new()
604            .add_strip(4, 2, 8, false)
605            .add_strip(16, 2, 24, true)
606            .finish();
607
608        run_test(expected, path_1, path_2);
609    }
610
611    #[test]
612    fn alpha_buffer_correct_width() {
613        let path_1 = StripBuilder::new()
614            .add_strip(0, 0, 4, false)
615            .add_strip(0, 1, 12, false)
616            .finish();
617
618        let path_2 = StripBuilder::new()
619            .add_strip(4, 0, 8, false)
620            .add_strip(0, 1, 4, false)
621            .add_strip(12, 1, 16, true)
622            .finish();
623
624        let expected = StripBuilder::new().add_strip(0, 1, 12, false).finish();
625
626        run_test(expected, path_1, path_2);
627    }
628
629    #[test]
630    fn row_iterator_abort_next_line() {
631        let path_1 = StripBuilder::new()
632            .add_strip(0, 0, 4, false)
633            .add_strip(0, 1, 4, false)
634            .finish();
635
636        let path_ref = PathDataRef {
637            strips: &path_1.strips,
638            alphas: &path_1.alphas,
639        };
640
641        let mut idx = 0;
642        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
643
644        assert!(iter.next().is_some());
645        assert!(iter.next().is_none());
646    }
647
648    fn run_test(expected: StripStorage, path_1: StripStorage, path_2: StripStorage) {
649        let mut write_target = StripStorage::default();
650
651        let path_1 = PathDataRef {
652            strips: &path_1.strips,
653            alphas: &path_1.alphas,
654        };
655
656        let path_2 = PathDataRef {
657            strips: &path_2.strips,
658            alphas: &path_2.alphas,
659        };
660
661        intersect(Level::new(), path_1, path_2, &mut write_target);
662
663        assert_eq!(write_target, expected);
664    }
665
666    struct StripBuilder {
667        storage: StripStorage,
668    }
669
670    impl StripBuilder {
671        fn new() -> Self {
672            Self {
673                storage: StripStorage::default(),
674            }
675        }
676
677        fn add_strip(self, x: u16, strip_y: u16, end: u16, fill_gap: bool) -> Self {
678            let width = end - x;
679            self.add_strip_with(
680                x,
681                strip_y,
682                end,
683                fill_gap,
684                &vec![0; (width * Tile::HEIGHT) as usize],
685            )
686        }
687
688        fn add_strip_with(
689            mut self,
690            x: u16,
691            strip_y: u16,
692            end: u16,
693            fill_gap: bool,
694            alphas: &[u8],
695        ) -> Self {
696            let width = end - x;
697            assert_eq!(alphas.len(), (width * Tile::HEIGHT) as usize);
698            let idx = self.storage.alphas.len();
699            self.storage
700                .strips
701                .push(Strip::new(x, strip_y * Tile::HEIGHT, idx as u32, fill_gap));
702            self.storage.alphas.extend_from_slice(alphas);
703
704            self
705        }
706
707        fn finish(mut self) -> StripStorage {
708            let last_y = self.storage.strips.last().unwrap().y;
709            let idx = self.storage.alphas.len();
710
711            self.storage
712                .strips
713                .push(Strip::new(u16::MAX, last_y, idx as u32, false));
714
715            self.storage
716        }
717    }
718}