/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Primitive segmentation //! //! # Overview //! //! Segmenting is the process of breaking rectangular primitives into smaller rectangular //! primitives in order to extract parts that could benefit from a fast paths. //! //! Typically this is used to allow fully opaque segments to be rendered in the opaque //! pass. For example when an opaque rectangle has a non-axis-aligned transform applied, //! we usually have to apply some anti-aliasing around the edges which requires alpha //! blending. By segmenting the edges out of the center of the primitive, we can keep a //! large amount of pixels in the opaque pass. //! Segmenting also lets us avoids rasterizing parts of clip masks that we know to have //! no effect or to be fully masking. For example by segmenting the corners of a rounded //! rectangle clip, we can optimize both rendering the mask and the primitive by only //! rasterize the corners in the mask and not applying any clipping to the segments of //! the primitive that don't overlap the borders. //! //! It is a flexible system in the sense that different sources of segmentation (for //! example two rounded rectangle clips) can affect the segmentation, and the possibility //! to segment some effects such as specific clip kinds does not necessarily mean the //! primitive will actually be segmented. //! //! ## Segments and clipping //! //! Segments of a primitive can be either not clipped, fully clipped, or partially clipped. //! In the first two case we don't need a clip mask. For each partially masked segments, a //! mask is rasterized using a render task. All of the interesting steps happen during frame //! building. //! //! - The first step is to determine the segmentation and write the associated GPU data. //! See `PrimitiveInstance::build_segments_if_needed` and `write_brush_segment_description` //! in `prim_store/mod.rs` which uses the segment builder of this module. //! - The second step is to generate the mask render tasks. //! See `BrushSegment::update_clip_task` and `RenderTask::new_mask`. For each segment that //! needs a mask, the contribution of all clips that affect the segment is added to the //! mask's render task. //! - Segments are assigned to batches (See `batch.rs`). Segments of a given primitive can //! be assigned to different batches. //! //! See also the [`clip` module documentation][clip.rs] for details about how clipping //! information is represented. //! //! //! [clip.rs]: ../clip/index.html //! use api::{BorderRadius, ClipMode}; use api::units::*; use std::{cmp, usize}; use crate::util::{extract_inner_rect_safe}; use smallvec::SmallVec; // We don't want to generate too many segments in edge cases, as it will result in a lot of // clip mask overhead, and possibly exceeding the maximum row size of the GPU cache. const MAX_SEGMENTS: usize = 64; bitflags! { // Note: This can use up to 4 bits due to how it will be packed in // the instance data. /// Each bit of the edge AA mask is: /// 0, when the edge of the primitive needs to be considered for AA /// 1, when the edge of the segment needs to be considered for AA /// /// *Note*: the bit values have to match the shader logic in /// `write_transform_vertex()` function. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pub struct EdgeAaSegmentMask: u8 { /// const LEFT = 0x1; /// const TOP = 0x2; /// const RIGHT = 0x4; /// const BOTTOM = 0x8; } } bitflags! { pub struct ItemFlags: u8 { const X_ACTIVE = 0x1; const Y_ACTIVE = 0x2; const HAS_MASK = 0x4; } } // The segment builder outputs a list of these segments. #[derive(Debug, PartialEq)] pub struct Segment { pub rect: LayoutRect, pub has_mask: bool, pub edge_flags: EdgeAaSegmentMask, pub region_x: usize, pub region_y: usize, } // The segment builder creates a list of x/y axis events // that are used to build a segment list. Right now, we // don't bother providing a list of *which* clip regions // are active for a given segment. Instead, if there is // any clip mask present in a segment, we will just end // up drawing each of the masks to that segment clip. // This is a fairly rare case, but we can detect this // in the future and only apply clip masks that are // relevant to each segment region. // TODO(gw): Provide clip region info with each segment. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] enum EventKind { // Beginning of a clip (rounded) rect. BeginClip, // End of a clip (rounded) rect. EndClip, // Begin the next region in the primitive. BeginRegion, } // Events must be ordered such that when the coordinates // of two events are the same, the end events are processed // before the begin events. This ensures that we're able // to detect which regions are active for a given segment. impl Ord for EventKind { fn cmp(&self, other: &EventKind) -> cmp::Ordering { match (*self, *other) { (EventKind::BeginRegion, EventKind::BeginRegion) => { panic!("bug: regions must be non-overlapping") } (EventKind::EndClip, EventKind::BeginRegion) | (EventKind::BeginRegion, EventKind::BeginClip) => { cmp::Ordering::Less } (EventKind::BeginClip, EventKind::BeginRegion) | (EventKind::BeginRegion, EventKind::EndClip) => { cmp::Ordering::Greater } (EventKind::BeginClip, EventKind::BeginClip) | (EventKind::EndClip, EventKind::EndClip) => { cmp::Ordering::Equal } (EventKind::BeginClip, EventKind::EndClip) => { cmp::Ordering::Greater } (EventKind::EndClip, EventKind::BeginClip) => { cmp::Ordering::Less } } } } // A x/y event where we will create a vertex in the // segment builder. #[derive(Debug, Eq, PartialEq, PartialOrd)] struct Event { value: Au, item_index: ItemIndex, kind: EventKind, } impl Ord for Event { fn cmp(&self, other: &Event) -> cmp::Ordering { self.value .cmp(&other.value) .then(self.kind.cmp(&other.kind)) } } impl Event { fn begin(value: f32, index: usize) -> Event { Event { value: Au::from_f32_px(value), item_index: ItemIndex(index), kind: EventKind::BeginClip, } } fn end(value: f32, index: usize) -> Event { Event { value: Au::from_f32_px(value), item_index: ItemIndex(index), kind: EventKind::EndClip, } } fn region(value: f32) -> Event { Event { value: Au::from_f32_px(value), kind: EventKind::BeginRegion, item_index: ItemIndex(usize::MAX), } } fn update( &self, flag: ItemFlags, items: &mut [Item], region: &mut usize, ) { let is_active = match self.kind { EventKind::BeginClip => true, EventKind::EndClip => false, EventKind::BeginRegion => { *region += 1; return; } }; items[self.item_index.0].flags.set(flag, is_active); } } // An item that provides some kind of clip region (either // a clip in/out rect, or a mask region). #[derive(Debug)] struct Item { rect: LayoutRect, mode: Option, flags: ItemFlags, } impl Item { fn new( rect: LayoutRect, mode: Option, has_mask: bool, ) -> Item { let flags = if has_mask { ItemFlags::HAS_MASK } else { ItemFlags::empty() }; Item { rect, mode, flags, } } } #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] struct ItemIndex(usize); // The main public interface to the segment module. pub struct SegmentBuilder { items: Vec, inner_rect: Option, bounding_rect: Option, has_interesting_clips: bool, #[cfg(debug_assertions)] initialized: bool, } impl SegmentBuilder { // Create a new segment builder, supplying the primitive // local rect and associated local clip rect. pub fn new() -> SegmentBuilder { SegmentBuilder { items: Vec::with_capacity(4), bounding_rect: None, inner_rect: None, has_interesting_clips: false, #[cfg(debug_assertions)] initialized: false, } } pub fn initialize( &mut self, local_rect: LayoutRect, inner_rect: Option, local_clip_rect: LayoutRect, ) { self.items.clear(); self.inner_rect = inner_rect; self.bounding_rect = Some(local_rect); self.push_clip_rect(local_rect, None, ClipMode::Clip); self.push_clip_rect(local_clip_rect, None, ClipMode::Clip); // This must be set after the push_clip_rect calls above, since we // want to skip segment building if those are the only clips. self.has_interesting_clips = false; #[cfg(debug_assertions)] { self.initialized = true; } } // Push a region defined by an inner and outer rect where there // is a mask required. This ensures that segments which intersect // with these areas will get a clip mask task allocated. This // is currently used to mark where a box-shadow region can affect // the pixels of a clip-mask. It might be useful for other types // such as dashed and dotted borders in the future. pub fn push_mask_region( &mut self, outer_rect: LayoutRect, inner_rect: LayoutRect, inner_clip_mode: Option, ) { self.has_interesting_clips = true; if inner_rect.is_empty() { self.items.push(Item::new( outer_rect, None, true )); return; } debug_assert!(outer_rect.contains_box(&inner_rect)); let p0 = outer_rect.min; let p1 = inner_rect.min; let p2 = inner_rect.max; let p3 = outer_rect.max; let segments = &[ LayoutRect { min: LayoutPoint::new(p0.x, p0.y), max: LayoutPoint::new(p1.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p0.y), max: LayoutPoint::new(p3.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p2.y), max: LayoutPoint::new(p3.x, p3.y), }, LayoutRect { min: LayoutPoint::new(p0.x, p2.y), max: LayoutPoint::new(p1.x, p3.y), }, LayoutRect { min: LayoutPoint::new(p1.x, p0.y), max: LayoutPoint::new(p2.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p1.y), max: LayoutPoint::new(p3.x, p2.y), }, LayoutRect { min: LayoutPoint::new(p1.x, p2.y), max: LayoutPoint::new(p2.x, p3.y), }, LayoutRect { min: LayoutPoint::new(p0.x, p1.y), max: LayoutPoint::new(p1.x, p2.y), }, ]; self.items.reserve(segments.len() + 1); for segment in segments { self.items.push(Item::new( *segment, None, true )); } if inner_clip_mode.is_some() { self.items.push(Item::new( inner_rect, inner_clip_mode, false, )); } } // Push some kind of clipping region into the segment builder. // If radius is None, it's a simple rect. pub fn push_clip_rect( &mut self, rect: LayoutRect, radius: Option, mode: ClipMode, ) { self.has_interesting_clips = true; // Keep track of a minimal bounding rect for the set of // segments that will be generated. if mode == ClipMode::Clip { self.bounding_rect = self.bounding_rect.and_then(|bounding_rect| { bounding_rect.intersection(&rect) }); } let mode = Some(mode); match radius { Some(radius) => { // For a rounded rect, try to create a nine-patch where there // is a clip item for each corner, inner and edge region. match extract_inner_rect_safe(&rect, &radius) { Some(inner) => { let p0 = rect.min; let p1 = inner.min; let p2 = inner.max; let p3 = rect.max; self.items.reserve(9); let corner_segments = &[ LayoutRect { min: LayoutPoint::new(p0.x, p0.y), max: LayoutPoint::new(p1.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p0.y), max: LayoutPoint::new(p3.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p2.y), max: LayoutPoint::new(p3.x, p3.y), }, LayoutRect { min: LayoutPoint::new(p0.x, p2.y), max: LayoutPoint::new(p1.x, p3.y), }, ]; for segment in corner_segments { self.items.push(Item::new( *segment, mode, true )); } let other_segments = &[ LayoutRect { min: LayoutPoint::new(p1.x, p0.y), max: LayoutPoint::new(p2.x, p1.y), }, LayoutRect { min: LayoutPoint::new(p2.x, p1.y), max: LayoutPoint::new(p3.x, p2.y), }, LayoutRect { min: LayoutPoint::new(p1.x, p2.y), max: LayoutPoint::new(p2.x, p3.y), }, LayoutRect { min: LayoutPoint::new(p0.x, p1.y), max: LayoutPoint::new(p1.x, p2.y), }, LayoutRect { min: LayoutPoint::new(p1.x, p1.y), max: LayoutPoint::new(p2.x, p2.y), }, ]; for segment in other_segments { self.items.push(Item::new( *segment, mode, false, )); } } None => { // If we get here, we could not extract an inner rectangle // for this clip region. This can occur in cases such as // a rounded rect where the top-left and bottom-left radii // result in overlapping rects. In that case, just create // a single clip region for the entire rounded rect. self.items.push(Item::new( rect, mode, true, )) } } } None => { // For a simple rect, just create one clipping item. self.items.push(Item::new( rect, mode, false, )) } } } // Consume this segment builder and produce a list of segments. pub fn build(&mut self, mut f: F) where F: FnMut(&Segment) { #[cfg(debug_assertions)] debug_assert!(self.initialized); #[cfg(debug_assertions)] { self.initialized = false; } let bounding_rect = match self.bounding_rect { Some(bounding_rect) => bounding_rect, None => return, }; if !self.has_interesting_clips { // There were no additional clips added, so don't bother building segments. // Just emit a single segment for the bounding rect of the primitive. f(&Segment { edge_flags: EdgeAaSegmentMask::all(), region_x: 0, region_y: 0, has_mask: false, rect: bounding_rect, }); return } // First, filter out any items that don't intersect // with the visible bounding rect. self.items.retain(|item| item.rect.intersects(&bounding_rect)); // Create events for each item let mut x_events : SmallVec<[Event; 4]> = SmallVec::new(); let mut y_events : SmallVec<[Event; 4]> = SmallVec::new(); for (item_index, item) in self.items.iter().enumerate() { let p0 = item.rect.min; let p1 = item.rect.max; x_events.push(Event::begin(p0.x, item_index)); x_events.push(Event::end(p1.x, item_index)); y_events.push(Event::begin(p0.y, item_index)); y_events.push(Event::end(p1.y, item_index)); } // Add the region events, if provided. if let Some(inner_rect) = self.inner_rect { x_events.push(Event::region(inner_rect.min.x)); x_events.push(Event::region(inner_rect.max.x)); y_events.push(Event::region(inner_rect.min.y)); y_events.push(Event::region(inner_rect.max.y)); } // Get the minimal bounding rect in app units. We will // work in fixed point in order to avoid float precision // error while handling events. let p0 = LayoutPointAu::new( Au::from_f32_px(bounding_rect.min.x), Au::from_f32_px(bounding_rect.min.y), ); let p1 = LayoutPointAu::new( Au::from_f32_px(bounding_rect.max.x), Au::from_f32_px(bounding_rect.max.y), ); // Sort the events in ascending order. x_events.sort(); y_events.sort(); // Generate segments from the event lists, by sweeping the y-axis // and then the x-axis for each event. This can generate a significant // number of segments, but most importantly, it ensures that there are // no t-junctions in the generated segments. It's probably possible // to come up with more efficient segmentation algorithms, at least // for simple / common cases. // Each coordinate is clamped to the bounds of the minimal // bounding rect. This ensures that we don't generate segments // outside that bounding rect, but does allow correctly handling // clips where the clip region starts outside the minimal // rect but still intersects with it. let mut prev_y = clamp(p0.y, y_events[0].value, p1.y); let mut region_y = 0; let mut segments : SmallVec<[_; 16]> = SmallVec::new(); let mut x_count = 0; let mut y_count = 0; for ey in &y_events { let cur_y = clamp(p0.y, ey.value, p1.y); if cur_y != prev_y { let mut prev_x = clamp(p0.x, x_events[0].value, p1.x); let mut region_x = 0; for ex in &x_events { let cur_x = clamp(p0.x, ex.value, p1.x); if cur_x != prev_x { segments.push(emit_segment_if_needed( prev_x, prev_y, cur_x, cur_y, region_x, region_y, &self.items, )); prev_x = cur_x; if y_count == 0 { x_count += 1; } } ex.update( ItemFlags::X_ACTIVE, &mut self.items, &mut region_x, ); } prev_y = cur_y; y_count += 1; } ey.update( ItemFlags::Y_ACTIVE, &mut self.items, &mut region_y, ); } // If we created more than 64 segments, just bail out and draw it as a single primitive // with a single mask, to avoid overhead of excessive amounts of segments. This can only // happen in pathological cases, for example a cascade of a dozen or more overlapping // and intersecting rounded clips. if segments.len() > MAX_SEGMENTS { f(&Segment { edge_flags: EdgeAaSegmentMask::all(), region_x: 0, region_y: 0, has_mask: true, rect: bounding_rect, }); return } // Run user supplied closure for each valid segment. debug_assert_eq!(segments.len(), x_count * y_count); for y in 0 .. y_count { for x in 0 .. x_count { let mut edge_flags = EdgeAaSegmentMask::empty(); if x == 0 || segments[y * x_count + x - 1].is_none() { edge_flags |= EdgeAaSegmentMask::LEFT; } if x == x_count-1 || segments[y * x_count + x + 1].is_none() { edge_flags |= EdgeAaSegmentMask::RIGHT; } if y == 0 || segments[(y-1) * x_count + x].is_none() { edge_flags |= EdgeAaSegmentMask::TOP; } if y == y_count-1 || segments[(y+1) * x_count + x].is_none() { edge_flags |= EdgeAaSegmentMask::BOTTOM; } if let Some(ref mut segment) = segments[y * x_count + x] { segment.edge_flags = edge_flags; f(segment); } } } } } fn clamp(low: Au, value: Au, high: Au) -> Au { value.max(low).min(high) } fn emit_segment_if_needed( x0: Au, y0: Au, x1: Au, y1: Au, region_x: usize, region_y: usize, items: &[Item], ) -> Option { debug_assert!(x1 > x0); debug_assert!(y1 > y0); // TODO(gw): Don't scan the whole list of items for // each segment rect. Store active list // in a hash set or similar if this ever // shows up in a profile. let mut has_clip_mask = false; for item in items { if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) { has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK); if item.mode == Some(ClipMode::ClipOut) && !item.flags.contains(ItemFlags::HAS_MASK) { return None; } } } let segment_rect = LayoutRect { min: LayoutPoint::new( x0.to_f32_px(), y0.to_f32_px(), ), max: LayoutPoint::new( x1.to_f32_px(), y1.to_f32_px(), ), }; Some(Segment { rect: segment_rect, has_mask: has_clip_mask, edge_flags: EdgeAaSegmentMask::empty(), region_x, region_y, }) } #[cfg(test)] mod test { use api::{BorderRadius, ClipMode}; use api::units::{LayoutPoint, LayoutRect}; use super::{Segment, SegmentBuilder, EdgeAaSegmentMask}; use std::cmp; fn rect(x0: f32, y0: f32, x1: f32, y1: f32) -> LayoutRect { LayoutRect { min: LayoutPoint::new(x0, y0), max: LayoutPoint::new(x1, y1), } } fn seg( x0: f32, y0: f32, x1: f32, y1: f32, has_mask: bool, edge_flags: Option, ) -> Segment { seg_region(x0, y0, x1, y1, 0, 0, has_mask, edge_flags) } fn seg_region( x0: f32, y0: f32, x1: f32, y1: f32, region_x: usize, region_y: usize, has_mask: bool, edge_flags: Option, ) -> Segment { Segment { rect: LayoutRect { min: LayoutPoint::new(x0, y0), max: LayoutPoint::new(x1, y1), }, has_mask, edge_flags: edge_flags.unwrap_or(EdgeAaSegmentMask::empty()), region_x, region_y, } } fn segment_sorter(s0: &Segment, s1: &Segment) -> cmp::Ordering { let r0 = &s0.rect; let r1 = &s1.rect; ( (r0.min.x, r0.min.y, r0.max.x, r0.max.y) ).partial_cmp(& (r1.min.x, r1.min.y, r1.max.x, r1.max.y) ).unwrap() } fn seg_test( local_rect: LayoutRect, inner_rect: Option, local_clip_rect: LayoutRect, clips: &[(LayoutRect, Option, ClipMode)], expected_segments: &mut [Segment] ) { let mut sb = SegmentBuilder::new(); sb.initialize( local_rect, inner_rect, local_clip_rect, ); sb.push_clip_rect(local_rect, None, ClipMode::Clip); sb.push_clip_rect(local_clip_rect, None, ClipMode::Clip); let mut segments = Vec::new(); for &(rect, radius, mode) in clips { sb.push_clip_rect(rect, radius, mode); } sb.build(|segment| { segments.push(Segment { ..*segment }); }); segments.sort_by(segment_sorter); expected_segments.sort_by(segment_sorter); assert_eq!( segments.len(), expected_segments.len(), "segments\n{:?}\nexpected\n{:?}\n", segments, expected_segments ); for (segment, expected) in segments.iter().zip(expected_segments.iter()) { assert_eq!(segment, expected); } } #[test] fn segment_empty() { seg_test( rect(0.0, 0.0, 0.0, 0.0), None, rect(0.0, 0.0, 0.0, 0.0), &[], &mut [], ); } #[test] fn segment_single() { seg_test( rect(10.0, 20.0, 30.0, 40.0), None, rect(10.0, 20.0, 30.0, 40.0), &[], &mut [ seg(10.0, 20.0, 30.0, 40.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_single_clip() { seg_test( rect(10.0, 20.0, 30.0, 40.0), None, rect(10.0, 20.0, 25.0, 35.0), &[], &mut [ seg(10.0, 20.0, 25.0, 35.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_inner_clip() { seg_test( rect(10.0, 20.0, 30.0, 40.0), None, rect(15.0, 25.0, 25.0, 35.0), &[], &mut [ seg(15.0, 25.0, 25.0, 35.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_outer_clip() { seg_test( rect(15.0, 25.0, 25.0, 35.0), None, rect(10.0, 20.0, 30.0, 40.0), &[], &mut [ seg(15.0, 25.0, 25.0, 35.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_clip_int() { seg_test( rect(10.0, 20.0, 30.0, 40.0), None, rect(20.0, 10.0, 40.0, 30.0), &[], &mut [ seg(20.0, 20.0, 30.0, 30.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_clip_disjoint() { seg_test( rect(10.0, 20.0, 30.0, 40.0), None, rect(30.0, 20.0, 50.0, 40.0), &[], &mut [], ); } #[test] fn segment_clips() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(-1000.0, -1000.0, 1000.0, 1000.0), &[ (rect(20.0, 20.0, 40.0, 40.0), None, ClipMode::Clip), (rect(40.0, 20.0, 60.0, 40.0), None, ClipMode::Clip), ], &mut [ ], ); } #[test] fn segment_rounded_clip() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(-1000.0, -1000.0, 1000.0, 1000.0), &[ (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), ], &mut [ // corners seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)), seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), // inner seg(30.0, 30.0, 50.0, 50.0, false, None), // edges seg(30.0, 20.0, 50.0, 30.0, false, Some(EdgeAaSegmentMask::TOP)), seg(30.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::BOTTOM)), seg(20.0, 30.0, 30.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT)), seg(50.0, 30.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT)), ], ); } #[test] fn segment_clip_out() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(-1000.0, -1000.0, 2000.0, 2000.0), &[ (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::ClipOut), ], &mut [ seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), seg(20.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)), seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), seg(0.0, 20.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)), seg(60.0, 20.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)), seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), seg(20.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)), seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)), ], ); } #[test] fn segment_rounded_clip_out() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(-1000.0, -1000.0, 2000.0, 2000.0), &[ (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::ClipOut), ], &mut [ // top row seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), seg(20.0, 0.0, 30.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)), seg(30.0, 0.0, 50.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)), seg(50.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)), seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), // left seg(0.0, 20.0, 20.0, 30.0, false, Some(EdgeAaSegmentMask::LEFT)), seg(0.0, 30.0, 20.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)), seg(0.0, 50.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT)), // right seg(60.0, 20.0, 100.0, 30.0, false, Some(EdgeAaSegmentMask::RIGHT)), seg(60.0, 30.0, 100.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)), seg(60.0, 50.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT)), // bottom row seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), seg(20.0, 60.0, 30.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), seg(30.0, 60.0, 50.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)), seg(50.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), // inner corners seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), ], ); } #[test] fn segment_clip_in_clip_out() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(-1000.0, -1000.0, 2000.0, 2000.0), &[ (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::Clip), (rect(50.0, 50.0, 80.0, 80.0), None, ClipMode::ClipOut), ], &mut [ seg(20.0, 20.0, 50.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), seg(50.0, 20.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), seg(20.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)), ], ); } #[test] fn segment_rounded_clip_overlap() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(0.0, 0.0, 100.0, 100.0), &[ (rect(0.0, 0.0, 10.0, 10.0), None, ClipMode::ClipOut), (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), ], &mut [ // corners seg(0.0, 90.0, 10.0, 100.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), seg(90.0, 0.0, 100.0, 10.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)), seg(90.0, 90.0, 100.0, 100.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), // inner seg(10.0, 10.0, 90.0, 90.0, false, None), // edges seg(10.0, 0.0, 90.0, 10.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), seg(10.0, 90.0, 90.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), seg(0.0, 10.0, 10.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), seg(90.0, 10.0, 100.0, 90.0, false, Some(EdgeAaSegmentMask::RIGHT)), ], ); } #[test] fn segment_rounded_clip_overlap_reverse() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(0.0, 0.0, 100.0, 100.0), &[ (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip), (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), ], &mut [ seg(10.0, 10.0, 90.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM ) ), ], ); } #[test] fn segment_clip_in_clip_out_overlap() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(0.0, 0.0, 100.0, 100.0), &[ (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip), (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::ClipOut), ], &mut [ ], ); } #[test] fn segment_event_order() { seg_test( rect(0.0, 0.0, 100.0, 100.0), None, rect(0.0, 0.0, 100.0, 100.0), &[ (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut), ], &mut [ seg(0.0, 90.0, 100.0, 100.0, false, Some( EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP )), ], ); } #[test] fn segment_region_simple() { seg_test( rect(0.0, 0.0, 100.0, 100.0), Some(rect(20.0, 40.0, 60.0, 80.0)), rect(0.0, 0.0, 100.0, 100.0), &[ ], &mut [ seg_region( 0.0, 0.0, 20.0, 40.0, 0, 0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP) ), seg_region( 20.0, 0.0, 60.0, 40.0, 1, 0, false, Some(EdgeAaSegmentMask::TOP) ), seg_region( 60.0, 0.0, 100.0, 40.0, 2, 0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT) ), seg_region( 0.0, 40.0, 20.0, 80.0, 0, 1, false, Some(EdgeAaSegmentMask::LEFT) ), seg_region( 20.0, 40.0, 60.0, 80.0, 1, 1, false, None, ), seg_region( 60.0, 40.0, 100.0, 80.0, 2, 1, false, Some(EdgeAaSegmentMask::RIGHT) ), seg_region( 0.0, 80.0, 20.0, 100.0, 0, 2, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM) ), seg_region( 20.0, 80.0, 60.0, 100.0, 1, 2, false, Some(EdgeAaSegmentMask::BOTTOM), ), seg_region( 60.0, 80.0, 100.0, 100.0, 2, 2, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM) ), ], ); } #[test] fn segment_region_clip() { seg_test( rect(0.0, 0.0, 100.0, 100.0), Some(rect(20.0, 40.0, 60.0, 80.0)), rect(0.0, 0.0, 100.0, 100.0), &[ (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut), ], &mut [ seg_region( 0.0, 90.0, 20.0, 100.0, 0, 2, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP) ), seg_region( 20.0, 90.0, 60.0, 100.0, 1, 2, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP), ), seg_region( 60.0, 90.0, 100.0, 100.0, 2, 2, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP) ), ], ); } #[test] fn segment_region_clip2() { seg_test( rect(0.0, 0.0, 100.0, 100.0), Some(rect(20.0, 20.0, 80.0, 80.0)), rect(0.0, 0.0, 100.0, 100.0), &[ (rect(20.0, 20.0, 100.0, 100.0), None, ClipMode::ClipOut), ], &mut [ seg_region( 0.0, 0.0, 20.0, 20.0, 0, 0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP) ), seg_region( 20.0, 0.0, 80.0, 20.0, 1, 0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM), ), seg_region( 80.0, 0.0, 100.0, 20.0, 2, 0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM) ), seg_region( 0.0, 20.0, 20.0, 80.0, 0, 1, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT) ), seg_region( 0.0, 80.0, 20.0, 100.0, 0, 2, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT) ), ], ); } #[test] fn segment_region_clip3() { seg_test( rect(0.0, 0.0, 100.0, 100.0), Some(rect(20.0, 20.0, 80.0, 80.0)), rect(0.0, 0.0, 100.0, 100.0), &[ (rect(10.0, 10.0, 30.0, 30.0), None, ClipMode::Clip), ], &mut [ seg_region( 10.0, 10.0, 20.0, 20.0, 0, 0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT), ), seg_region( 20.0, 10.0, 30.0, 20.0, 1, 0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT), ), seg_region( 10.0, 20.0, 20.0, 30.0, 0, 1, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT), ), seg_region( 20.0, 20.0, 30.0, 30.0, 1, 1, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT), ), ], ); } }