/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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}; usecrate::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;
// Note: This can use up to 4 bits due to how it will be packed in // the instance data.
/// *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(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, MallocSizeOf)] pubstruct EdgeMask(u8);
bitflags! { impl EdgeMask: u8 { /// const LEFT = 0x1; /// const TOP = 0x2; /// const RIGHT = 0x4; /// const BOTTOM = 0x8;
}
}
impl EdgeMask { /// Returns a rectangle with the egdes of `a` or `b`, selected indicidually. /// /// For each side bit, if it is set then use the edge from `a`, else use the edge from `b`. pubfn select<T: Copy, U>(&self, a: euclid::Box2D<T, U>, b: euclid::Box2D<T, U>) -> euclid::Box2D<T, U> { letmut rect = b; ifself.contains(Self::LEFT) {
rect.min.x = a.min.x;
} ifself.contains(Self::TOP) {
rect.min.y = a.min.y;
} ifself.contains(Self::RIGHT) {
rect.max.x = a.max.x;
} ifself.contains(Self::BOTTOM) {
rect.max.y = a.max.y;
}
// The segment builder outputs a list of these segments. #[derive(Debug, PartialEq)] pubstruct Segment { pub rect: LayoutRect, pub has_mask: bool, pub edge_flags: EdgeMask, 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))
}
}
// 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,
}
// The main public interface to the segment module. pubstruct 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. pubfn new() -> SegmentBuilder {
SegmentBuilder {
items: Vec::with_capacity(4),
bounding_rect: None,
inner_rect: None,
has_interesting_clips: false, #[cfg(debug_assertions)]
initialized: false,
}
}
// 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;
// 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. pubfn push_mask_region(
&mutself,
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;
}
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. pubfn push_clip_rect(
&mutself,
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;
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. pubfn build<F>(&mutself, mut f: F) where F: FnMut(&Segment) { #[cfg(debug_assertions)]
debug_assert!(self.initialized);
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: EdgeMask::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 letmut x_events : SmallVec<[Event; 4]> = SmallVec::new(); letmut y_events : SmallVec<[Event; 4]> = SmallVec::new();
for (item_index, item) inself.items.iter().enumerate() { let p0 = item.rect.min; let p1 = item.rect.max;
// Add the region events, if provided. iflet Some(inner_rect) = self.inner_rect {
x_events.push(Event::region(inner_rect.min.x));
x_events.push(Event::region(inner_rect.max.x));
// 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.
// 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: EdgeMask::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 in0 .. y_count { for x in0 .. x_count { letmut edge_flags = EdgeMask::empty();
if x == 0 || segments[y * x_count + x - 1].is_none() {
edge_flags |= EdgeMask::LEFT;
} if x == x_count-1 || segments[y * x_count + x + 1].is_none() {
edge_flags |= EdgeMask::RIGHT;
} if y == 0 || segments[(y-1) * x_count + x].is_none() {
edge_flags |= EdgeMask::TOP;
} if y == y_count-1 || segments[(y+1) * x_count + x].is_none() {
edge_flags |= EdgeMask::BOTTOM;
}
// 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. letmut 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);
#[cfg(test)] mod test { use api::{BorderRadius, ClipMode}; use api::units::{LayoutPoint, LayoutRect}; usesuper::{Segment, SegmentBuilder, EdgeMask}; use std::cmp;
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.28Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.