use kurbo::{Affine, PathEl, Rect, RoundedRect, RoundedRectRadii, Shape};
use peniko::Fill;
use vello_common::clip::{ClipContext, PathDataRef};
use vello_common::geometry::RectU16;
use vello_common::strip::Strip;
use vello_common::strip_generator::{StripGenerator, StripStorage};
use vello_common::tile::Tile;
use super::{FLATTEN_TOLERANCE, fast_rect};
const UNCLIPPED: RectU16 = RectU16::new(0, 0, u16::MAX, u16::MAX);
const FULL_COVERAGE: u8 = 255;
const MAX_ALPHA_INDEX: u32 = u32::MAX >> 1;
#[derive(Debug)]
enum Entry {
Scissor { restore: RectU16 },
Mask,
}
#[derive(Debug, Clone, Copy)]
struct Span {
y: u16,
x: u16,
width: u16,
alpha_idx: Option<u32>,
}
impl Span {
fn x1(&self) -> u32 {
u32::from(self.x) + u32::from(self.width)
}
}
#[derive(Debug)]
pub struct ClipStack {
entries: Vec<Entry>,
masks: ClipContext,
scissor: RectU16,
strips: Vec<Strip>,
alphas: Vec<u8>,
scissor_clips: u32,
mask_clips: u32,
mask_strips: usize,
}
impl Default for ClipStack {
fn default() -> Self {
Self::new()
}
}
impl ClipStack {
pub fn new() -> Self {
Self {
entries: Vec::new(),
masks: ClipContext::new(),
scissor: UNCLIPPED,
strips: Vec::new(),
alphas: Vec::new(),
scissor_clips: 0,
mask_clips: 0,
mask_strips: 0,
}
}
pub fn reset(&mut self) {
self.entries.clear();
self.masks.reset();
self.scissor = UNCLIPPED;
self.scissor_clips = 0;
self.mask_clips = 0;
self.mask_strips = 0;
}
pub fn scissor_clips(&self) -> u32 {
self.scissor_clips
}
pub fn mask_clips(&self) -> u32 {
self.mask_clips
}
pub fn mask_strips(&self) -> usize {
self.mask_strips
}
pub fn mask(&self) -> Option<PathDataRef<'_>> {
self.masks.get()
}
pub fn blocks_everything(&self) -> bool {
self.scissor.is_empty()
}
pub fn push_rect(&mut self, rect: Rect, transform: Affine, generator: &mut StripGenerator) {
match fast_rect(rect, transform) {
Some(device) => self.push_scissor(device),
None => self.push_mask(rect.path_elements(FLATTEN_TOLERANCE), transform, generator),
}
}
pub fn push_rounded(
&mut self,
rect: Rect,
radii: RoundedRectRadii,
transform: Affine,
generator: &mut StripGenerator,
) {
if radii_are_square(radii) {
self.push_rect(rect, transform, generator);
return;
}
let shape = RoundedRect::from_rect(rect, radii);
self.push_mask(shape.path_elements(FLATTEN_TOLERANCE), transform, generator);
}
pub fn pop(&mut self) {
match self.entries.pop() {
Some(Entry::Scissor { restore }) => self.scissor = restore,
Some(Entry::Mask) => self.masks.pop_clip(),
None => {}
}
}
fn push_scissor(&mut self, device: Rect) {
let restore = self.scissor;
self.scissor = self.scissor.intersect(device_rect(device));
self.entries.push(Entry::Scissor { restore });
self.scissor_clips = self.scissor_clips.saturating_add(1);
}
fn push_mask(
&mut self,
path: impl IntoIterator<Item = PathEl>,
transform: Affine,
generator: &mut StripGenerator,
) {
self.masks
.push_clip(path, generator, Fill::NonZero, transform, None);
self.entries.push(Entry::Mask);
self.mask_clips = self.mask_clips.saturating_add(1);
self.mask_strips = self
.mask_strips
.saturating_add(self.masks.get().map_or(0, |mask| mask.strips.len()));
}
pub fn clip_run(&mut self, storage: &mut StripStorage, strip_start: usize, alpha_start: usize) {
if self.scissor == UNCLIPPED {
return;
}
let Some(run) = storage.strips.get(strip_start..) else {
return;
};
if !spans(run).any(|span| self.clips(&span)) {
return;
}
self.strips.clear();
self.alphas.clear();
let base = alpha_start.min(storage.alphas.len());
let scissor = self.scissor;
for span in spans(run) {
if let Some(clipped) = Clipped::of(&span, scissor) {
self.emit(&span, &clipped, &storage.alphas, base);
}
}
self.close_run(base);
storage.strips.truncate(strip_start);
storage.alphas.truncate(base);
storage.strips.extend_from_slice(&self.strips);
storage.alphas.extend_from_slice(&self.alphas);
}
fn clips(&self, span: &Span) -> bool {
Clipped::of(span, self.scissor).is_none_or(|clipped| !clipped.covers_all_of(span))
}
fn close_run(&mut self, base: usize) {
let Some(last) = self.strips.last() else {
return;
};
let y = last.y;
let end = alpha_index(base.saturating_add(self.alphas.len()));
self.strips.push(Strip::sentinel(y, end));
}
fn emit(&mut self, span: &Span, clipped: &Clipped, alphas: &[u8], base: usize) {
if span.alpha_idx.is_none()
&& clipped.rows_are_whole()
&& let Some((x0, x1)) = clipped.interior_tiles()
{
self.emit_split_solid(span, clipped, (x0, x1), alphas, base);
return;
}
let (x0, x1) = clipped.tiles();
self.emit_masked(span, clipped, (x0, x1), alphas, base);
}
fn emit_split_solid(
&mut self,
span: &Span,
clipped: &Clipped,
interior: (u32, u32),
alphas: &[u8],
base: usize,
) {
let (tile_x0, tile_x1) = clipped.tiles();
let (interior_x0, interior_x1) = interior;
if tile_x0 < interior_x0 {
self.emit_masked(span, clipped, (tile_x0, interior_x0), alphas, base);
}
self.emit_solid(
pixel(interior_x0),
span.y,
pixel(interior_x1.saturating_sub(interior_x0)),
base,
);
if interior_x1 < tile_x1 {
self.emit_masked(span, clipped, (interior_x1, tile_x1), alphas, base);
}
}
fn emit_masked(
&mut self,
span: &Span,
clipped: &Clipped,
extent: (u32, u32),
alphas: &[u8],
base: usize,
) {
let (x0, x1) = extent;
let width = x1.saturating_sub(x0);
if width == 0 {
return;
}
let origin = u32::from(span.x);
let start = alpha_index(base.saturating_add(self.alphas.len()));
for column in 0..width {
let x = x0.saturating_add(column);
for row in 0..u32::from(Tile::HEIGHT) {
let value = match span.alpha_idx {
_ if !clipped.contains(x, row) => 0,
None => FULL_COVERAGE,
Some(idx) => coverage_at(alphas, idx, x.saturating_sub(origin), row),
};
self.alphas.push(value);
}
}
self.strips
.push(Strip::new(pixel(x0), span.y, start, false));
}
fn emit_solid(&mut self, x: u16, y: u16, width: u16, base: usize) {
if width == 0 {
return;
}
let index = alpha_index(base.saturating_add(self.alphas.len()));
self.strips.push(Strip::new(x, y, index, false));
self.strips
.push(Strip::new(x.saturating_add(width), y, index, true));
}
}
#[derive(Debug, Clone, Copy)]
struct Clipped {
x0: u32,
x1: u32,
row0: u32,
row1: u32,
}
impl Clipped {
fn of(span: &Span, scissor: RectU16) -> Option<Self> {
let top = u32::from(span.y);
let bottom = top.saturating_add(u32::from(Tile::HEIGHT));
let row0 = u32::from(scissor.y0).clamp(top, bottom) - top;
let row1 = u32::from(scissor.y1).clamp(top, bottom) - top;
if row0 >= row1 {
return None;
}
let x0 = u32::from(span.x).max(u32::from(scissor.x0));
let x1 = span.x1().min(u32::from(scissor.x1));
if x0 >= x1 {
return None;
}
Some(Self { x0, x1, row0, row1 })
}
fn covers_all_of(&self, span: &Span) -> bool {
self.rows_are_whole() && self.x0 == u32::from(span.x) && self.x1 == span.x1()
}
fn rows_are_whole(&self) -> bool {
self.row0 == 0 && self.row1 == u32::from(Tile::HEIGHT)
}
fn contains(&self, x: u32, row: u32) -> bool {
x >= self.x0 && x < self.x1 && row >= self.row0 && row < self.row1
}
fn tiles(&self) -> (u32, u32) {
(tile_floor(self.x0), tile_ceil(self.x1))
}
fn interior_tiles(&self) -> Option<(u32, u32)> {
let x0 = tile_ceil(self.x0);
let x1 = tile_floor(self.x1);
(x0 < x1).then_some((x0, x1))
}
}
fn spans(run: &[Strip]) -> impl Iterator<Item = Span> + '_ {
run.windows(2)
.flat_map(|pair| {
let [strip, next] = pair else {
return [None, None];
};
if strip.is_sentinel() {
return [None, None];
}
let width = strip.width_to(next);
let alpha = (width > 0).then(|| Span {
y: strip.y,
x: strip.x,
width,
alpha_idx: Some(strip.alpha_idx()),
});
let gap = if next.fill_gap() && next.y == strip.y {
let x = strip.x.saturating_add(width);
let gap_width = next.x.saturating_sub(x);
(gap_width > 0).then_some(Span {
y: strip.y,
x,
width: gap_width,
alpha_idx: None,
})
} else {
None
};
[alpha, gap]
})
.flatten()
}
fn coverage_at(alphas: &[u8], alpha_idx: u32, column: u32, row: u32) -> u8 {
let offset = column
.saturating_mul(u32::from(Tile::HEIGHT))
.saturating_add(row);
let index = alpha_idx.saturating_add(offset) as usize;
alphas.get(index).copied().unwrap_or(0)
}
fn pixel(pixels: u32) -> u16 {
u16::try_from(pixels).unwrap_or(u16::MAX)
}
fn alpha_index(index: usize) -> u32 {
u32::try_from(index)
.unwrap_or(MAX_ALPHA_INDEX)
.min(MAX_ALPHA_INDEX)
}
fn tile_floor(x: u32) -> u32 {
x - x % u32::from(Tile::WIDTH)
}
fn tile_ceil(x: u32) -> u32 {
tile_floor(x.saturating_add(u32::from(Tile::WIDTH) - 1))
}
fn radii_are_square(radii: RoundedRectRadii) -> bool {
radii.top_left <= 0.0
&& radii.top_right <= 0.0
&& radii.bottom_right <= 0.0
&& radii.bottom_left <= 0.0
}
fn device_rect(rect: Rect) -> RectU16 {
let coordinate = |value: f64| value.clamp(0.0, f64::from(u16::MAX)) as u16;
RectU16::new(
coordinate(rect.x0),
coordinate(rect.y0),
coordinate(rect.x1),
coordinate(rect.y1),
)
}
#[cfg(test)]
mod tests {
use super::*;
use vello_common::fearless_simd::Level;
fn generator() -> StripGenerator {
StripGenerator::new(64, 64, Level::baseline())
}
#[test]
fn a_run_decodes_into_its_alpha_and_solid_spans() {
let run = [
Strip::new(0, 0, 0, false),
Strip::new(12, 0, 16, true),
Strip::sentinel(0, 16),
];
let decoded: Vec<Span> = spans(&run).collect();
assert_eq!(decoded.len(), 2);
assert_eq!((decoded[0].x, decoded[0].width), (0, 4));
assert_eq!(decoded[0].alpha_idx, Some(0));
assert_eq!((decoded[1].x, decoded[1].width), (4, 8));
assert_eq!(decoded[1].alpha_idx, None);
}
#[test]
fn a_span_the_scissor_misses_does_not_survive() {
let span = Span {
y: 0,
x: 0,
width: 8,
alpha_idx: None,
};
assert!(Clipped::of(&span, RectU16::new(16, 0, 32, 4)).is_none());
assert!(Clipped::of(&span, RectU16::new(0, 8, 32, 12)).is_none());
assert!(Clipped::of(&span, RectU16::new(0, 0, 8, 4)).is_some());
}
#[test]
fn tile_rounding_brackets_a_pixel_extent() {
assert_eq!(tile_floor(5), 4);
assert_eq!(tile_ceil(5), 8);
assert_eq!(tile_floor(8), 8);
assert_eq!(tile_ceil(8), 8);
}
#[test]
fn interior_tiles_are_only_the_whole_ones() {
let span = Span {
y: 0,
x: 0,
width: 32,
alpha_idx: None,
};
let wide = Clipped::of(&span, RectU16::new(5, 0, 19, 4)).expect("overlaps");
assert_eq!(wide.interior_tiles(), Some((8, 16)));
let narrow = Clipped::of(&span, RectU16::new(5, 0, 7, 4)).expect("overlaps");
assert_eq!(narrow.interior_tiles(), None);
}
#[test]
fn an_enormous_clip_rectangle_clamps_rather_than_wrapping() {
let huge = device_rect(Rect::new(-1e30, -1e30, 1e30, 1e30));
assert_eq!(huge, UNCLIPPED);
let behind = device_rect(Rect::new(-1e30, -1e30, -1e29, -1e29));
assert!(behind.is_empty());
}
#[test]
fn an_unbalanced_pop_leaves_the_stack_alone() {
let mut stack = ClipStack::new();
stack.pop();
stack.pop();
assert!(!stack.blocks_everything());
assert_eq!(stack.scissor_clips(), 0);
assert_eq!(stack.mask_clips(), 0);
}
#[test]
fn nested_scissors_intersect_and_unwind() {
let mut generator = generator();
let mut stack = ClipStack::new();
stack.push_rect(
Rect::new(0.0, 0.0, 40.0, 40.0),
Affine::IDENTITY,
&mut generator,
);
stack.push_rect(
Rect::new(20.0, 20.0, 60.0, 60.0),
Affine::IDENTITY,
&mut generator,
);
assert_eq!(stack.scissor, RectU16::new(20, 20, 40, 40));
stack.pop();
assert_eq!(stack.scissor, RectU16::new(0, 0, 40, 40));
stack.pop();
assert_eq!(stack.scissor, UNCLIPPED);
assert_eq!(stack.scissor_clips(), 2);
assert_eq!(stack.mask_strips(), 0);
}
#[test]
fn a_square_cornered_rounded_clip_still_scissors() {
let mut generator = generator();
let mut stack = ClipStack::new();
stack.push_rounded(
Rect::new(4.0, 4.0, 20.0, 20.0),
RoundedRectRadii::from_single_radius(0.0),
Affine::IDENTITY,
&mut generator,
);
assert_eq!(stack.scissor_clips(), 1);
assert_eq!(stack.mask_clips(), 0);
assert_eq!(stack.mask_strips(), 0);
}
#[test]
fn a_rounded_clip_rasterizes_a_mask() {
let mut generator = generator();
let mut stack = ClipStack::new();
stack.push_rounded(
Rect::new(4.0, 4.0, 40.0, 40.0),
RoundedRectRadii::from_single_radius(8.0),
Affine::IDENTITY,
&mut generator,
);
assert_eq!(stack.mask_clips(), 1);
assert!(stack.mask_strips() > 0);
assert!(stack.mask().is_some());
stack.pop();
assert!(stack.mask().is_none());
}
}