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::geometry::RectU16;
7use crate::kurbo::{Affine, BezPath, PathEl};
8use crate::strip::Strip;
9use crate::strip_generator::{GenerationMode, StripGenerator, StripStorage};
10use crate::tile::Tile;
11use crate::util::{Clear, Pool, normalized_mul_u8x16, strip_bbox};
12use alloc::vec;
13use alloc::vec::Vec;
14use core::ops::Range;
15use fearless_simd::{Level, Simd, SimdBase, dispatch, u8x16};
16use peniko::Fill;
17
18#[derive(Debug)]
19struct ClipData {
20    alpha_start: u32,
21    strip_start: u32,
22
23    /// A coarse bounding box of the clip path in pixel coordinates.
24    ///
25    /// These bounds have already been intersected with the viewport.
26    bbox: RectU16,
27}
28
29impl ClipData {
30    fn to_path_data_ref<'a>(&self, storage: &'a StripStorage) -> PathDataRef<'a> {
31        PathDataRef {
32            strips: storage
33                .strips
34                .get(self.strip_start as usize..)
35                .unwrap_or(&[]),
36            alphas: storage
37                .alphas
38                .get(self.alpha_start as usize..)
39                .unwrap_or(&[]),
40            bbox: self.bbox,
41        }
42    }
43}
44
45/// A context for managing clip stacks.
46#[derive(Debug)]
47pub struct ClipContext {
48    storage: StripStorage,
49    temp_storage: StripStorage,
50    clip_stack: Vec<ClipData>,
51}
52
53impl Default for ClipContext {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl ClipContext {
60    /// Create a new clip context.
61    #[inline]
62    pub fn new() -> Self {
63        let mut main_storage = StripStorage::default();
64        main_storage.set_generation_mode(GenerationMode::Append);
65        Self {
66            storage: main_storage,
67            temp_storage: StripStorage::default(),
68            clip_stack: vec![],
69        }
70    }
71
72    /// Reset the clip context.
73    #[inline]
74    pub fn reset(&mut self) {
75        self.clip_stack.clear();
76        self.storage.clear();
77        self.temp_storage.clear();
78    }
79
80    /// Get the data of the current clip path.
81    #[inline]
82    pub fn get(&self) -> Option<PathDataRef<'_>> {
83        self.clip_stack
84            .last()
85            .map(|c| c.to_path_data_ref(&self.storage))
86    }
87
88    /// Push a new clip path to the stack.
89    #[inline]
90    pub fn push_clip(
91        &mut self,
92        clip_path: impl IntoIterator<Item = PathEl>,
93        strip_generator: &mut StripGenerator,
94        fill_rule: Fill,
95        transform: Affine,
96        aliasing_threshold: Option<u8>,
97    ) {
98        self.temp_storage.clear();
99
100        let alpha_start = self.storage.alphas.len() as u32;
101        let strip_start = self.storage.strips.len() as u32;
102
103        let existing_clip = self
104            .clip_stack
105            .last()
106            .map(|c| c.to_path_data_ref(&self.storage));
107
108        strip_generator.generate_filled_path(
109            clip_path,
110            fill_rule,
111            transform,
112            aliasing_threshold,
113            &mut self.temp_storage,
114            existing_clip,
115        );
116
117        let bbox = strip_bbox(&self.temp_storage.strips).unwrap_or(RectU16::ZERO);
118        let clip_data = ClipData {
119            alpha_start,
120            strip_start,
121            bbox,
122        };
123
124        self.storage.extend(&self.temp_storage);
125        self.clip_stack.push(clip_data);
126    }
127
128    /// Pop the least recent clip path.
129    #[inline]
130    pub fn pop_clip(&mut self) {
131        let data = self.clip_stack.pop().expect("clip stack underflowed");
132        self.storage.strips.truncate(data.strip_start as usize);
133        self.storage.alphas.truncate(data.alpha_start as usize);
134    }
135}
136
137/// Raw data of a previously pushed clip path.
138#[derive(Debug)]
139struct RawClip {
140    /// The range of commands in [`ClipState::path_elements`] belonging to this clip path.
141    path: Range<usize>,
142    fill_rule: Fill,
143    transform: Affine,
144    aliasing_threshold: Option<u8>,
145}
146
147/// A frame containing clipping-relevant state for the root layer or a filter layer.
148#[derive(Debug)]
149struct ClipFrame {
150    /// The clip context of the parent.
151    parent_context: ClipContext,
152    /// The accumulated source shift of the current layer.
153    source_shift: Affine,
154    /// The current revision of the clipping state.
155    clip_revision: u64,
156}
157
158// This struct implements an additional piece of logic to make non-isolated clips work properly
159// with filter layers. The root of all "evil" that requires us to implement this wrapper around
160// [`crate::clip::ClipContext`] is that, as the user pushes new filter layers into the
161// render context, we eagerly apply a shift to all subsequently rendered contents to ensure that
162// everything necessary for correct filter rendering is guaranteed to be visible. However, since
163// the clip stack eagerly generates strips for each clip path that is clipped to the original
164// viewport, those generated clip paths cannot just be translated on demand to account for the
165// source shift of the filter layer. Therefore, every time a new filter layer is pushed, we need
166// to regenerate the clip context for that specific layer to ensure clips are applied correctly.
167/// State for managing clip paths across multiple viewports.
168#[derive(Debug)]
169pub struct ClipState {
170    /// The currently active clip context.
171    context: ClipContext,
172    /// A pool of reusable clip contexts.
173    context_pool: Pool<ClipContext>,
174    /// A flat factor of path elements storing the original path data of clip paths.
175    path_elements: Vec<PathEl>,
176    /// Raw data of the currently active stack of clip paths
177    raw_clips: Vec<RawClip>,
178    /// Stack of pushed clip frames.
179    frames: Vec<ClipFrame>,
180    /// The current revision.
181    revision: u64,
182}
183
184impl Clear for ClipContext {
185    fn clear(&mut self) {
186        self.reset();
187    }
188}
189
190impl Default for ClipState {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196impl ClipState {
197    /// Create a new clip state.
198    pub fn new() -> Self {
199        Self {
200            context: ClipContext::new(),
201            context_pool: Pool::default(),
202            path_elements: Vec::new(),
203            raw_clips: Vec::new(),
204            frames: Vec::new(),
205            revision: 0,
206        }
207    }
208
209    /// Return the current clip path.
210    pub fn get(&self) -> Option<PathDataRef<'_>> {
211        self.context.get()
212    }
213
214    /// Push a new root viewport.
215    pub fn push_root_viewport(
216        &mut self,
217        source_shift: (u16, u16),
218        strip_generator: &mut StripGenerator,
219    ) {
220        let parent_context = core::mem::replace(&mut self.context, self.context_pool.take());
221        let source_shift =
222            Affine::translate((f64::from(source_shift.0), f64::from(source_shift.1)))
223                * self.active_shift();
224        self.frames.push(ClipFrame {
225            parent_context,
226            source_shift,
227            clip_revision: self.revision,
228        });
229        self.rebuild_context(strip_generator);
230    }
231
232    /// Pop the last root viewport.
233    pub fn pop_root_viewport(&mut self, strip_generator: &mut StripGenerator) {
234        let frame = self.frames.pop().expect("filter clip stack underflow");
235        let filter_context = core::mem::replace(&mut self.context, frame.parent_context);
236        self.context_pool.submit(filter_context);
237        if self.revision == frame.clip_revision {
238            // No new clip paths have been pushed or popped since then, so we don't have to rebuild it.
239        } else {
240            self.rebuild_context(strip_generator);
241        }
242    }
243
244    /// Push a clip path.
245    pub fn push_clip(
246        &mut self,
247        path: &BezPath,
248        strip_generator: &mut StripGenerator,
249        fill_rule: Fill,
250        transform: Affine,
251        aliasing_threshold: Option<u8>,
252    ) {
253        let path_start = self.path_elements.len();
254        self.path_elements.extend(path.iter());
255        let path = path_start..self.path_elements.len();
256        let clip_transform = self.active_shift() * transform;
257
258        self.context.push_clip(
259            self.path_elements[path.clone()].iter().copied(),
260            strip_generator,
261            fill_rule,
262            clip_transform,
263            aliasing_threshold,
264        );
265        self.raw_clips.push(RawClip {
266            path,
267            fill_rule,
268            transform,
269            aliasing_threshold,
270        });
271        self.revision = self.revision.wrapping_add(1);
272    }
273
274    /// Pop the active clip path.
275    pub fn pop_clip(&mut self) {
276        let raw_clip = self.raw_clips.pop().expect("clip stack underflowed");
277        self.path_elements.truncate(raw_clip.path.start);
278        self.context.pop_clip();
279        self.revision = self.revision.wrapping_add(1);
280    }
281
282    /// Reset the clip state.
283    pub fn reset(&mut self) {
284        self.context.reset();
285        for frame in self.frames.drain(..) {
286            self.context_pool.submit(frame.parent_context);
287        }
288        self.path_elements.clear();
289        self.raw_clips.clear();
290        self.revision = 0;
291    }
292
293    fn active_shift(&self) -> Affine {
294        self.frames
295            .last()
296            .map_or(Affine::IDENTITY, |frame| frame.source_shift)
297    }
298
299    fn rebuild_context(&mut self, strip_generator: &mut StripGenerator) {
300        self.context.reset();
301        let active_shift = self.active_shift();
302        for raw_clip in &self.raw_clips {
303            self.context.push_clip(
304                self.path_elements[raw_clip.path.clone()].iter().copied(),
305                strip_generator,
306                raw_clip.fill_rule,
307                active_shift * raw_clip.transform,
308                raw_clip.aliasing_threshold,
309            );
310        }
311    }
312}
313
314/// Borrowed data of a stripped path.
315#[derive(Clone, Copy, Debug)]
316pub struct PathDataRef<'a> {
317    /// The strips.
318    pub strips: &'a [Strip],
319    /// The alpha buffer.
320    pub alphas: &'a [u8],
321    /// A tile-aligned coarse bounding box of the clip path in pixel coordinates.
322    ///
323    /// These bounds have already been intersected with the viewport.
324    pub bbox: RectU16,
325}
326
327/// Compute the sparse strips representation of a path that results
328/// from intersecting the two input paths. This can be used to implement
329/// clip paths.
330pub fn intersect(
331    level: Level,
332    path_1: PathDataRef<'_>,
333    path_2: PathDataRef<'_>,
334    target: &mut StripStorage,
335) {
336    dispatch!(level, simd => intersect_impl(simd, path_1, path_2, target));
337}
338
339/// The implementation of the clipping algorithm using sparse strips. Conceptually, it is relatively
340/// simple: We iterate over each strip and fill region of the two paths in lock step and determine
341/// all overlaps between the two. For each overlap, we proceed depending on what kind of region
342/// we have in the first path and the second one.
343/// - In case we have two fill regions, the overlap region will also be filled.
344/// - In case we have one strip and one fill region, the overlap region will copy the alpha mask of the strip region.
345/// - Finally, if we have two strip regions, we combine the alpha masks of both.
346/// - All regions that are not filled in either path are simply ignored.
347///
348/// This is all that this method does. It just looks more complicated as the logic for iterating
349/// in lock step is a bit tricky.
350#[inline(always)]
351fn intersect_impl<S: Simd>(
352    simd: S,
353    path_1: PathDataRef<'_>,
354    path_2: PathDataRef<'_>,
355    target: &mut StripStorage,
356) {
357    // In case either path is empty, the clip path should be empty.
358    if path_1.strips.is_empty() || path_2.strips.is_empty() {
359        return;
360    }
361
362    // Ignore any y values that are outside the bounding box of either of the two paths, as
363    // those are guaranteed to have neither fill nor strip regions.
364    let path_1_start_y = path_1.strips[0].strip_y();
365    let path_2_start_y = path_2.strips[0].strip_y();
366    let mut cur_y = path_1_start_y.max(path_2_start_y);
367    let end_y = path_1.strips[path_1.strips.len() - 1]
368        .strip_y()
369        .min(path_2.strips[path_2.strips.len() - 1].strip_y());
370
371    let mut path_1_idx = 0;
372    let mut path_2_idx = 0;
373
374    // Use binary search to determine the first index of whichever
375    // path has a smaller y to avoid a large linear scan in the
376    // first iteration of the loop below in case the discrepancy
377    // is large.
378    if path_1_start_y < cur_y {
379        path_1_idx = first_strip_at_or_after(path_1.strips, cur_y);
380    } else if path_2_start_y < cur_y {
381        path_2_idx = first_strip_at_or_after(path_2.strips, cur_y);
382    }
383
384    let mut strip_state = None;
385
386    // Iterate over each strip row and handle them.
387    while cur_y <= end_y {
388        // For each row, we create two iterators that alternatingly yield the strips and fill
389        // regions in that row, until the last strip has been reached.
390        let mut p1_iter = RowIterator::new(path_1, &mut path_1_idx, cur_y);
391        let mut p2_iter = RowIterator::new(path_2, &mut path_2_idx, cur_y);
392
393        let mut p1_region = p1_iter.next();
394        let mut p2_region = p2_iter.next();
395
396        // If at least one region is none, it means that we reached the end of the row
397        // for that path, meaning that we exceeded the bounding box of that path and no
398        // additional strips should be generated for that row, even if the other path might
399        // still have more strips left. They will all be clipped away. So only consider it
400        // if both paths have a region left.
401        while let (Some(region_1), Some(region_2)) = (p1_region, p2_region) {
402            match region_1.overlap_relationship(&region_2) {
403                // This means there is no overlap between the regions, so we need to advance
404                // the iterator of the region that is further behind.
405                OverlapRelationship::Advance(advance) => {
406                    match advance {
407                        Advance::Left => p1_region = p1_iter.next(),
408                        Advance::Right => p2_region = p2_iter.next(),
409                    };
410
411                    continue;
412                }
413                // We have an overlap!
414                OverlapRelationship::Overlap(overlap) => {
415                    match (region_1, region_2) {
416                        // Both regions are a fill. Flush the current strip and start a new
417                        // one at the end of the overlap region setting `fill_gap` to true,
418                        // so that the whole area before that will be filled with a sparse
419                        // fill.
420                        (Region::Fill(_), Region::Fill(_)) => {
421                            flush_strip(&mut strip_state, &mut target.strips, cur_y);
422                            start_strip(&mut strip_state, &target.alphas, overlap.end, true);
423                        }
424                        // One fill one strip, so we simply use the alpha mask from the strip region.
425                        (Region::Strip(s), Region::Fill(_))
426                        | (Region::Fill(_), Region::Strip(s)) => {
427                            // If possible, don't create a new strip but just extend the current one.
428                            if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
429                            {
430                                flush_strip(&mut strip_state, &mut target.strips, cur_y);
431                                start_strip(&mut strip_state, &target.alphas, overlap.start, false);
432                            }
433
434                            let s_alphas = &s.alphas[(overlap.start - s.start) as usize * 4..]
435                                [..overlap.width() as usize * 4];
436                            target.alphas.extend_from_slice(s_alphas);
437                        }
438                        // Two strips, we need to multiply the opacity masks from both paths.
439                        (Region::Strip(s_region_1), Region::Strip(s_region_2)) => {
440                            // Once again, only create a new strip if we can't extend the current one.
441                            if should_create_new_strip(&strip_state, &target.alphas, overlap.start)
442                            {
443                                flush_strip(&mut strip_state, &mut target.strips, cur_y);
444                                start_strip(&mut strip_state, &target.alphas, overlap.start, false);
445                            }
446
447                            let num_blocks = overlap.width() / Tile::HEIGHT;
448
449                            // Get the right alpha values for the specific position.
450                            let s1_alphas = s_region_1.alphas
451                                [(overlap.start - s_region_1.start) as usize * 4..]
452                                .chunks_exact(16)
453                                .take(num_blocks as usize);
454                            let s2_alphas = s_region_2.alphas
455                                [(overlap.start - s_region_2.start) as usize * 4..]
456                                .chunks_exact(16)
457                                .take(num_blocks as usize);
458
459                            for (s1_alpha, s2_alpha) in s1_alphas.zip(s2_alphas) {
460                                let s1 = u8x16::from_slice(simd, s1_alpha);
461                                let s2 = u8x16::from_slice(simd, s2_alpha);
462
463                                // Combine them.
464                                let res = simd.narrow_u16x16(normalized_mul_u8x16(s1, s2));
465                                target.alphas.extend(res.as_slice());
466                            }
467                        }
468                    }
469
470                    // Advance the iterator of the path whose region's end is further behind.
471                    match overlap.advance {
472                        Advance::Left => p1_region = p1_iter.next(),
473                        Advance::Right => p2_region = p2_iter.next(),
474                    };
475                }
476            }
477        }
478
479        // Flush the strip before advancing to the next strip row.
480        flush_strip(&mut strip_state, &mut target.strips, cur_y);
481        cur_y += 1;
482    }
483
484    // Push the sentinel strip if the intersection is not empty.
485    if !target.strips.is_empty() {
486        target.strips.push(Strip::sentinel(
487            end_y * Tile::HEIGHT,
488            target.alphas.len() as u32,
489        ));
490    }
491}
492
493#[inline(always)]
494fn first_strip_at_or_after(strips: &[Strip], strip_y: u16) -> usize {
495    // Strips are guaranteed to be sorted in ascending y (and ascending x),
496    // hence why we can do this.
497    strips.partition_point(|strip| strip.strip_y() < strip_y)
498}
499
500/// An overlap between two regions.
501struct Overlap {
502    /// The start x coordinate.
503    start: u16,
504    /// The end x coordinate.
505    end: u16,
506    /// Whether the left or right region iterator should be advanced next.
507    advance: Advance,
508}
509
510impl Overlap {
511    fn width(&self) -> u16 {
512        self.end - self.start
513    }
514}
515
516enum Advance {
517    Left,
518    Right,
519}
520
521/// The relationship between two regions.
522enum OverlapRelationship {
523    /// There is no overlap between the regions, advance the region iterator on the given side.
524    Advance(Advance),
525    /// There is an overlap between the regions.
526    Overlap(Overlap),
527}
528
529#[derive(Debug, Clone, Copy)]
530struct FillRegion {
531    start: u16,
532    width: u16,
533}
534
535#[derive(Debug, Clone, Copy)]
536struct StripRegion<'a> {
537    start: u16,
538    width: u16,
539    alphas: &'a [u8],
540}
541
542#[derive(Debug, Clone, Copy)]
543enum Region<'a> {
544    Fill(FillRegion),
545    Strip(StripRegion<'a>),
546}
547
548impl Region<'_> {
549    #[inline(always)]
550    fn start(&self) -> u16 {
551        match self {
552            Region::Fill(fill) => fill.start,
553            Region::Strip(strip) => strip.start,
554        }
555    }
556
557    #[inline(always)]
558    fn width(&self) -> u16 {
559        match self {
560            Region::Fill(fill) => fill.width,
561            Region::Strip(strip) => strip.width,
562        }
563    }
564
565    #[inline(always)]
566    fn end(&self) -> u16 {
567        self.start() + self.width()
568    }
569
570    fn overlap_relationship(&self, other: &Region<'_>) -> OverlapRelationship {
571        if self.end() <= other.start() {
572            OverlapRelationship::Advance(Advance::Left)
573        } else if self.start() >= other.end() {
574            OverlapRelationship::Advance(Advance::Right)
575        } else {
576            let start = self.start().max(other.start());
577            let end = self.end().min(other.end());
578
579            let shift = if self.end() <= other.end() {
580                Advance::Left
581            } else {
582                Advance::Right
583            };
584
585            OverlapRelationship::Overlap(Overlap {
586                advance: shift,
587                start,
588                end,
589            })
590        }
591    }
592}
593
594/// An iterator of strip and fill regions of a single strip row.
595struct RowIterator<'a> {
596    /// The path in question.
597    input: PathDataRef<'a>,
598    /// The strip row we want to iterate over.
599    strip_y: u16,
600    /// The index of the current strip.
601    cur_idx: &'a mut usize,
602    /// Whether the iterator should yield a strip next or not.
603    /// When iterating over a row, we alternate between emitting strips and filled regions (unless
604    /// the region between two strips is not filled), so this flag acts as a toggle to store what
605    /// should be yielded next.
606    on_strip: bool,
607}
608
609impl<'a> RowIterator<'a> {
610    fn new(input: PathDataRef<'a>, cur_idx: &'a mut usize, strip_y: u16) -> Self {
611        // Forward the index until we have found the right strip.
612        while input.strips[*cur_idx].strip_y() < strip_y {
613            *cur_idx += 1;
614        }
615
616        Self {
617            input,
618            cur_idx,
619            strip_y,
620            on_strip: true,
621        }
622    }
623
624    #[inline(always)]
625    fn cur_strip(&self) -> &Strip {
626        &self.input.strips[*self.cur_idx]
627    }
628
629    #[inline(always)]
630    fn next_strip(&self) -> &Strip {
631        &self.input.strips[*self.cur_idx + 1]
632    }
633
634    #[inline(always)]
635    fn cur_strip_width(&self) -> u16 {
636        let cur = self.cur_strip();
637        let next = self.next_strip();
638        ((next.alpha_idx() - cur.alpha_idx()) / Tile::HEIGHT as u32) as u16
639    }
640
641    #[inline(always)]
642    fn cur_strip_alphas(&self) -> &'a [u8] {
643        let cur = self.cur_strip();
644        let next = self.next_strip();
645        &self.input.alphas[cur.alpha_idx() as usize..next.alpha_idx() as usize]
646    }
647
648    fn cur_strip_fill_area(&self) -> Option<FillRegion> {
649        let next = self.next_strip();
650
651        // Note that if the next strip happens to be on the next line, it will always have
652        // zero winding so we don't need to special case this.
653        if next.fill_gap() {
654            let cur = self.cur_strip();
655            let x = cur.x + self.cur_strip_width();
656            let width = next.x - x;
657
658            (width > 0).then_some(FillRegion { start: x, width })
659        } else {
660            None
661        }
662    }
663}
664
665impl<'a> Iterator for RowIterator<'a> {
666    type Item = Region<'a>;
667
668    #[inline(always)]
669    fn next(&mut self) -> Option<Self::Item> {
670        loop {
671            // If we are currently not on a strip, we want to yield a filled region in case there is one.
672            if !self.on_strip {
673                // Flip boolean flag so we will yield a strip in the next iteration.
674                self.on_strip = true;
675
676                // if we have a filled area, yield it and return. Otherwise, do nothing and we will
677                // instead yield the next strip below. In any case, we need to advance the current index
678                // so that we point to the next strip now.
679                if let Some(fill_area) = self.cur_strip_fill_area() {
680                    *self.cur_idx += 1;
681
682                    return Some(Region::Fill(fill_area));
683                } else {
684                    *self.cur_idx += 1;
685                }
686            }
687
688            // If we reached this point, we will yield a strip this iteration, so toggle the flag
689            // so that in the next iteration, we yield a filled region instead.
690            self.on_strip = false;
691
692            // If the current strip is sentinel or not within our target row, terminate.
693            if self.cur_strip().is_sentinel() || self.cur_strip().strip_y() != self.strip_y {
694                return None;
695            }
696
697            // Calculate the dimensions of the strip and yield it.
698            let x = self.cur_strip().x;
699            let width = self.cur_strip_width();
700
701            // Zero-width strips only act as markers for cheaply delimiting the width
702            // of filled regions, but are not actually relevant for clipping. This is assuming that
703            // zero-width strips can only appear at the end of a row, see the comment in
704            // `Strip::emit_culled_background`.
705            if width == 0 {
706                debug_assert!(
707                    self.next_strip().is_sentinel() || self.next_strip().strip_y() != self.strip_y,
708                    "zero-width strips must only appear at the end of a row"
709                );
710
711                continue;
712            }
713
714            let alphas = self.cur_strip_alphas();
715
716            return Some(Region::Strip(StripRegion {
717                start: x,
718                width,
719                alphas,
720            }));
721        }
722    }
723}
724
725/// The data of the current strip we are building.
726struct StripState {
727    x: u16,
728    alpha_idx: u32,
729    fill_gap: bool,
730}
731
732fn flush_strip(strip_state: &mut Option<StripState>, strips: &mut Vec<Strip>, cur_y: u16) {
733    if let Some(state) = core::mem::take(strip_state) {
734        strips.push(Strip::new(
735            state.x,
736            cur_y * Tile::HEIGHT,
737            state.alpha_idx,
738            state.fill_gap,
739        ));
740    }
741}
742
743#[inline(always)]
744fn start_strip(strip_data: &mut Option<StripState>, alphas: &[u8], x: u16, fill_gap: bool) {
745    *strip_data = Some(StripState {
746        x,
747        alpha_idx: alphas.len() as u32,
748        fill_gap,
749    });
750}
751
752fn should_create_new_strip(
753    strip_state: &Option<StripState>,
754    alphas: &[u8],
755    overlap_start: u16,
756) -> bool {
757    // Returns false in case we can append to the currently built strip.
758    strip_state.as_ref().is_none_or(|state| {
759        let width = ((alphas.len() as u32 - state.alpha_idx) / Tile::HEIGHT as u32) as u16;
760        let strip_end = state.x + width;
761
762        strip_end < overlap_start - 1
763    })
764}
765
766#[cfg(test)]
767mod tests {
768    use crate::clip::{PathDataRef, Region, RowIterator, first_strip_at_or_after, intersect};
769    use crate::geometry::RectU16;
770    use crate::strip::Strip;
771    use crate::strip_generator::StripStorage;
772    use crate::tile::Tile;
773    use fearless_simd::Level;
774    use std::vec;
775
776    #[test]
777    fn intersect_partly_overlapping_strips() {
778        let path_1 = StripBuilder::new().add_strip(0, 0, 32, false).finish();
779
780        let path_2 = StripBuilder::new().add_strip(8, 0, 44, false).finish();
781
782        let expected = StripBuilder::new().add_strip(8, 0, 32, false).finish();
783
784        run_test(expected, path_1, path_2);
785    }
786
787    #[test]
788    fn intersect_multiple_overlapping_strips() {
789        let path_1 = StripBuilder::new()
790            .add_strip(0, 1, 4, false)
791            .add_strip(12, 1, 20, true)
792            .add_strip(28, 1, 32, false)
793            .add_strip(44, 1, 52, true)
794            .finish();
795
796        let path_2 = StripBuilder::new()
797            .add_strip(4, 1, 8, false)
798            .add_strip(16, 1, 20, true)
799            .add_strip(24, 1, 28, false)
800            .add_strip(32, 1, 36, false)
801            .add_strip(44, 1, 48, true)
802            .finish();
803
804        let expected = StripBuilder::new()
805            .add_strip(4, 1, 8, false)
806            .add_strip(12, 1, 20, true)
807            .add_strip(32, 1, 36, false)
808            .add_strip(44, 1, 48, true)
809            .finish();
810
811        run_test(expected, path_1, path_2);
812    }
813
814    #[test]
815    fn multiple_rows() {
816        let path_1 = StripBuilder::new()
817            .add_strip(0, 0, 4, false)
818            .add_strip(16, 0, 20, true)
819            .add_strip(4, 1, 8, false)
820            .add_strip(12, 1, 24, true)
821            .add_strip(4, 2, 8, false)
822            .add_strip(16, 2, 32, true)
823            .finish();
824
825        let path_2 = StripBuilder::new()
826            .add_strip(0, 2, 4, false)
827            .add_strip(16, 2, 24, true)
828            .add_strip(8, 3, 12, false)
829            .add_strip(16, 3, 28, true)
830            .finish();
831
832        let expected = StripBuilder::new()
833            .add_strip(4, 2, 8, false)
834            .add_strip(16, 2, 24, true)
835            .finish();
836
837        run_test(expected, path_1, path_2);
838    }
839
840    #[test]
841    fn alpha_buffer_correct_width() {
842        let path_1 = StripBuilder::new()
843            .add_strip(0, 0, 4, false)
844            .add_strip(0, 1, 12, false)
845            .finish();
846
847        let path_2 = StripBuilder::new()
848            .add_strip(4, 0, 8, false)
849            .add_strip(0, 1, 4, false)
850            .add_strip(12, 1, 16, true)
851            .finish();
852
853        let expected = StripBuilder::new().add_strip(0, 1, 12, false).finish();
854
855        run_test(expected, path_1, path_2);
856    }
857
858    #[test]
859    fn first_strip_at_or_after_returns_first_matching_strip_y() {
860        let path = StripBuilder::new()
861            .add_strip(0, 0, 4, false)
862            .add_strip(0, 2, 4, false)
863            .add_strip(8, 2, 12, false)
864            .add_strip(16, 2, 20, false)
865            .add_strip(0, 4, 4, false)
866            .add_strip(8, 4, 12, false)
867            .add_strip(0, 6, 4, false)
868            .finish();
869
870        assert_eq!(first_strip_at_or_after(&path.strips, 0), 0);
871        assert_eq!(first_strip_at_or_after(&path.strips, 2), 1);
872        assert_eq!(first_strip_at_or_after(&path.strips, 3), 4);
873        assert_eq!(first_strip_at_or_after(&path.strips, 4), 4);
874        assert_eq!(first_strip_at_or_after(&path.strips, 5), 6);
875        assert_eq!(first_strip_at_or_after(&path.strips, 6), 6);
876        assert_eq!(first_strip_at_or_after(&path.strips, 7), path.strips.len());
877    }
878
879    #[test]
880    fn row_iterator_abort_next_line() {
881        let path_1 = StripBuilder::new()
882            .add_strip(0, 0, 4, false)
883            .add_strip(0, 1, 4, false)
884            .finish();
885
886        let path_ref = PathDataRef {
887            strips: &path_1.strips,
888            alphas: &path_1.alphas,
889            bbox: RectU16::new(0, 0, u16::MAX, u16::MAX),
890        };
891
892        let mut idx = 0;
893        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
894
895        assert!(iter.next().is_some());
896        assert!(iter.next().is_none());
897    }
898
899    #[test]
900    fn row_iterator_row_end_fill_gap() {
901        let path = StripBuilder::new()
902            .add_strip(0, 0, Tile::WIDTH, false)
903            .finish_with_fill_gap_row_end(16);
904        let path_ref = path_ref(&path);
905
906        let mut idx = 0;
907        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
908
909        assert_strip_region(iter.next(), 0, Tile::WIDTH);
910        assert_fill_region(iter.next(), Tile::WIDTH, 16 - Tile::WIDTH);
911        assert!(iter.next().is_none());
912    }
913
914    #[test]
915    fn intersect_strip_with_row_end_fill_gap() {
916        let path_1 = StripBuilder::new()
917            .add_strip(0, 0, Tile::WIDTH, false)
918            .finish_with_fill_gap_row_end(16);
919        let path_2 = StripBuilder::new().add_strip(8, 0, 12, false).finish();
920        let expected = StripBuilder::new().add_strip(8, 0, 12, false).finish();
921
922        run_test(expected, path_1, path_2);
923    }
924
925    #[test]
926    fn intersect_two_row_end_fill_gaps() {
927        let path_1 = StripBuilder::new()
928            .add_strip(0, 0, 8, false)
929            .finish_with_fill_gap_row_end(16);
930        let path_2 = StripBuilder::new()
931            .add_strip(4, 0, 12, false)
932            .finish_with_fill_gap_row_end(20);
933        let expected = StripBuilder::new()
934            .add_strip(4, 0, 12, false)
935            .finish_with_fill_gap_row_end(16);
936
937        run_test(expected, path_1, path_2);
938    }
939
940    #[test]
941    fn row_iterator_fill_gap_stops_at_row_boundary() {
942        let path = StripBuilder::new()
943            .add_strip(0, 0, 4, false)
944            .add_row_end(0, 16, true)
945            .add_strip(0, 1, 4, false)
946            .finish();
947
948        let path_ref = path_ref(&path);
949        let mut idx = 0;
950        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
951
952        assert_strip_region(iter.next(), 0, 4);
953        assert_fill_region(iter.next(), 4, 12);
954        assert!(iter.next().is_none());
955
956        let mut iter = RowIterator::new(path_ref, &mut idx, 1);
957
958        assert_strip_region(iter.next(), 0, Tile::WIDTH);
959        assert!(iter.next().is_none());
960    }
961
962    #[test]
963    fn row_iterator_adjacent_unmerged_strips_no_fill() {
964        let path = StripBuilder::new()
965            .add_strip(0, 0, 4, false)
966            .add_strip(4, 0, 8, false)
967            .finish();
968        let path_ref = path_ref(&path);
969
970        let mut idx = 0;
971        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
972
973        assert_strip_region(iter.next(), 0, 4);
974        assert_strip_region(iter.next(), 4, 4);
975        assert!(iter.next().is_none());
976    }
977
978    #[test]
979    fn row_iterator_adjacent_unmerged_strips_with_fill_gap() {
980        let path = StripBuilder::new()
981            .add_strip(0, 0, 4, false)
982            .add_strip(4, 0, 8, true)
983            .finish();
984        let path_ref = path_ref(&path);
985
986        let mut idx = 0;
987        let mut iter = RowIterator::new(path_ref, &mut idx, 0);
988
989        assert_strip_region(iter.next(), 0, 4);
990        assert_strip_region(iter.next(), 4, 4);
991        assert!(iter.next().is_none());
992    }
993
994    #[test]
995    fn intersect_adjacent_unmerged_strips() {
996        let path = StripBuilder::new()
997            .add_strip(0, 0, 4, false)
998            .add_strip(4, 0, 8, true)
999            .finish();
1000        let cover = StripBuilder::new().add_strip(0, 0, 8, false).finish();
1001        let expected = StripBuilder::new().add_strip(0, 0, 8, false).finish();
1002
1003        run_test(expected, path, cover);
1004    }
1005
1006    fn run_test(expected: StripStorage, path_1: StripStorage, path_2: StripStorage) {
1007        let mut write_target = StripStorage::default();
1008
1009        let path_1 = path_ref(&path_1);
1010        let path_2 = path_ref(&path_2);
1011
1012        intersect(Level::new(), path_1, path_2, &mut write_target);
1013
1014        assert_eq!(write_target, expected);
1015    }
1016
1017    fn path_ref(path: &StripStorage) -> PathDataRef<'_> {
1018        PathDataRef {
1019            strips: &path.strips,
1020            alphas: &path.alphas,
1021            bbox: RectU16::new(0, 0, u16::MAX, u16::MAX),
1022        }
1023    }
1024
1025    fn assert_strip_region(region: Option<Region<'_>>, start: u16, width: u16) {
1026        match region {
1027            Some(Region::Strip(strip)) => {
1028                assert_eq!(strip.start, start);
1029                assert_eq!(strip.width, width);
1030                assert_eq!(strip.alphas.len(), (width * Tile::HEIGHT) as usize);
1031            }
1032            other => panic!("expected strip region, got {other:?}"),
1033        }
1034    }
1035
1036    fn assert_fill_region(region: Option<Region<'_>>, start: u16, width: u16) {
1037        match region {
1038            Some(Region::Fill(fill)) => {
1039                assert_eq!(fill.start, start);
1040                assert_eq!(fill.width, width);
1041            }
1042            other => panic!("expected fill region, got {other:?}"),
1043        }
1044    }
1045
1046    struct StripBuilder {
1047        storage: StripStorage,
1048    }
1049
1050    impl StripBuilder {
1051        fn new() -> Self {
1052            Self {
1053                storage: StripStorage::default(),
1054            }
1055        }
1056
1057        fn add_strip(self, x: u16, strip_y: u16, end: u16, fill_gap: bool) -> Self {
1058            let width = end - x;
1059            self.add_strip_with(
1060                x,
1061                strip_y,
1062                end,
1063                fill_gap,
1064                &vec![0; (width * Tile::HEIGHT) as usize],
1065            )
1066        }
1067
1068        fn add_strip_with(
1069            mut self,
1070            x: u16,
1071            strip_y: u16,
1072            end: u16,
1073            fill_gap: bool,
1074            alphas: &[u8],
1075        ) -> Self {
1076            let width = end - x;
1077            assert_eq!(alphas.len(), (width * Tile::HEIGHT) as usize);
1078            let idx = self.storage.alphas.len();
1079            self.storage
1080                .strips
1081                .push(Strip::new(x, strip_y * Tile::HEIGHT, idx as u32, fill_gap));
1082            self.storage.alphas.extend_from_slice(alphas);
1083
1084            self
1085        }
1086
1087        fn finish(mut self) -> StripStorage {
1088            let last_y = self.storage.strips.last().unwrap().y;
1089            let idx = self.storage.alphas.len();
1090
1091            self.storage
1092                .strips
1093                .push(Strip::sentinel(last_y, idx as u32));
1094
1095            self.storage
1096        }
1097
1098        fn add_row_end(mut self, strip_y: u16, x: u16, fill_gap: bool) -> Self {
1099            let idx = self.storage.alphas.len();
1100            self.storage
1101                .strips
1102                .push(Strip::new(x, strip_y * Tile::HEIGHT, idx as u32, fill_gap));
1103
1104            self
1105        }
1106
1107        fn finish_with_fill_gap_row_end(self, x: u16) -> StripStorage {
1108            let strip_y = self.storage.strips.last().unwrap().strip_y();
1109
1110            self.add_row_end(strip_y, x, true).finish()
1111        }
1112    }
1113}