diff options
Diffstat (limited to 'gfx/wr/webrender/src/segment.rs')
-rw-r--r-- | gfx/wr/webrender/src/segment.rs | 1374 |
1 files changed, 1374 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/segment.rs b/gfx/wr/webrender/src/segment.rs new file mode 100644 index 0000000000..55e76f33f2 --- /dev/null +++ b/gfx/wr/webrender/src/segment.rs @@ -0,0 +1,1374 @@ +/* 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<ClipMode>, + flags: ItemFlags, +} + +impl Item { + fn new( + rect: LayoutRect, + mode: Option<ClipMode>, + 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<Item>, + inner_rect: Option<LayoutRect>, + bounding_rect: Option<LayoutRect>, + 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<LayoutRect>, + 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<ClipMode>, + ) { + 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<BorderRadius>, + 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<F>(&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<Segment> { + 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<EdgeAaSegmentMask>, + ) -> 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<EdgeAaSegmentMask>, + ) -> 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<LayoutRect>, + local_clip_rect: LayoutRect, + clips: &[(LayoutRect, Option<BorderRadius>, 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), + ), + ], + ); + } +} |