/* 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/. */
//! Internal representation of clips in WebRender. //! //! # Data structures //! //! There are a number of data structures involved in the clip module: //! //! - ClipStore - Main interface used by other modules. //! //! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow). //! These are an exposed API type, stored inline in a ClipNode. //! //! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated //! when a ClipNodeInstance is built from this node (which happens while //! preparing primitives for render). //! //! ClipNodeInstance - A ClipNode with attached positioning information (a spatial //! node index). This is stored as a contiguous array of nodes //! within the ClipStore. //! //! ```ascii //! +-----------------------+-----------------------+-----------------------+ //! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | //! +-----------------------+-----------------------+-----------------------+ //! | ClipItem | ClipItem | ClipItem | //! | Spatial Node Index | Spatial Node Index | Spatial Node Index | //! | GPU cache handle | GPU cache handle | GPU cache handle | //! | ... | ... | ... | //! +-----------------------+-----------------------+-----------------------+ //! 0 1 2 //! +----------------+ | | //! | ClipNodeRange |____| | //! | index: 1 | | //! | count: 2 |___________________________________________________| //! +----------------+ //! ``` //! //! - ClipNodeRange - A clip item range identifies a range of clip nodes instances. //! It is stored as an (index, count). //! //! - ClipChainNode - A clip chain node contains a handle to an interned clip item, //! positioning information (from where the clip was defined), and //! an optional parent link to another ClipChainNode. ClipChainId //! is an index into an array, or ClipChainId::NONE for no parent. //! //! ```ascii //! +----------------+ ____+----------------+ ____+----------------+ /---> ClipChainId::NONE //! | ClipChainNode | | | ClipChainNode | | | ClipChainNode | | //! +----------------+ | +----------------+ | +----------------+ | //! | ClipDataHandle | | | ClipDataHandle | | | ClipDataHandle | | //! | Spatial index | | | Spatial index | | | Spatial index | | //! | Parent Id |___| | Parent Id |___| | Parent Id |___| //! | ... | | ... | | ... | //! +----------------+ +----------------+ +----------------+ //! ``` //! //! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node. //! //! When given a clip chain ID, and a local primitive rect and its spatial node, the clip module //! creates a clip chain instance. This is a struct with various pieces of useful information //! (such as a local clip rect). It also contains a (index, count) //! range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant //! for this clip chain instance. The index buffer structure allows a single array to be used for //! all of the clip-chain instances built in a single frame. Each entry in the index buffer //! also stores some flags relevant to the clip node in this positioning context. //! //! ```ascii //! +----------------------+ //! | ClipChainInstance | //! +----------------------+ //! | ... | //! | local_clip_rect |________________________________________________________________________ //! | clips_range |_______________ | //! +----------------------+ | | //! | | //! +------------------+------------------+------------------+------------------+------------------+ //! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | //! +------------------+------------------+------------------+------------------+------------------+ //! | flags | flags | flags | flags | flags | //! | ... | ... | ... | ... | ... | //! +------------------+------------------+------------------+------------------+------------------+ //! ``` //! //! # Rendering clipped primitives //! //! See the [`segment` module documentation][segment.rs]. //! //! //! [segment.rs]: ../segment/index.html //!
use api::{BorderRadius, ClipMode, ImageMask, ClipId, ClipChainId}; use api::{FillRule, ImageKey, ImageRendering}; use api::units::*; usecrate::image_tiling::{self, Repetition}; usecrate::border::{ensure_no_corner_overlap, BorderRadiusAu}; usecrate::renderer::GpuBufferBuilderF; usecrate::spatial_tree::{SceneSpatialTree, SpatialTree, SpatialNodeIndex}; usecrate::ellipse::Ellipse; usecrate::intern; usecrate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo}; usecrate::prim_store::{VisibleMaskImageTile}; usecrate::prim_store::{RectKey, PolygonKey}; usecrate::render_task::RenderTask; usecrate::render_task_graph::RenderTaskGraphBuilder; usecrate::resource_cache::{ImageRequest, ResourceCache}; usecrate::scene_builder_thread::Interners; usecrate::space::{SpaceMapper, SpaceSnapper}; usecrate::util::{extract_inner_rect_safe, project_rect, MatrixHelpers, MaxRect, ScaleOffset}; use euclid::approxeq::ApproxEq; use std::{iter, ops, u32, mem};
/// A (non-leaf) node inside a clip-tree #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pubstruct ClipTreeNode { pub handle: ClipDataHandle, pub spatial_node_index: SpatialNodeIndex, /// Clip rect as authored by the display list (not snapped to the device /// pixel grid). Snapped on demand by `ClipTreeNode::snapped_clip_rect` /// during clip-chain construction. pub unsnapped_clip_rect: LayoutRect, pub parent: ClipNodeId,
children: FastHashMap<ClipEntry, ClipNodeId>,
// TODO(gw): Consider adding a default leaf for cases when the local_clip_rect is not relevant, // that can be shared among primitives (to reduce amount of clip-chain building).
}
impl ClipTreeNode { /// Snap `unsnapped_clip_rect` against the current spatial tree, in this /// node's own spatial-node space, relative to the consuming prim's surface /// raster node. Built on demand during clip-chain construction: the snapped /// rect depends on the per-frame spatial tree, and a clip node can be shared /// by prims in different surfaces, so it can't be pre-snapped to a single /// space. Only the root sentinel node carries an `INVALID` spatial node, and /// that node is never visited during clip-chain construction. fn snapped_clip_rect(
&self,
snapper: &mut SpaceSnapper,
spatial_tree: &SpatialTree,
) -> LayoutRect {
debug_assert!(self.spatial_node_index != SpatialNodeIndex::INVALID);
snapper.set_target_spatial_node(self.spatial_node_index, spatial_tree);
snapper.snap_rect(&self.unsnapped_clip_rect)
}
}
/// A leaf node in a clip-tree. Any primitive that is clipped will have a handle to /// a clip-tree leaf. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pubstruct ClipTreeLeaf { pub node_id: ClipNodeId,
// TODO(gw): For now, this preserves the ability to build a culling rect // from the supplied leaf local clip rect on the primitive. In // future, we'll expand this to be more efficient by combining // it will compatible clip rects from the `node_id`. /// Leaf-local clip rect as authored by the display list (not snapped to /// the device pixel grid). pub unsnapped_local_clip_rect: LayoutRect, /// `unsnapped_local_clip_rect` snapped against the current spatial tree /// in the owning primitive's cluster spatial-node space. Written each /// frame by the visibility pass from the cluster loop, using the cluster's /// (resolved) spatial node as the snap target. Picture / tile-cache leaves /// carry `max_rect` and pass through unchanged. pub snapped_local_clip_rect: LayoutRect,
}
/// ID for a ClipTreeNode #[derive(Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipNodeId(u32);
/// A clip-tree built during scene building and used during frame-building to apply clips to primitives. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipTree {
nodes: Vec<ClipTreeNode>,
leaves: Vec<ClipTreeLeaf>,
clip_root_stack: Vec<ClipNodeId>,
}
/// Add a set of clips to the provided tree node id, reusing existing /// nodes in the tree where possible fn add_impl( mut id: ClipNodeId,
clips: &[ClipEntry],
nodes: &mut Vec<ClipTreeNode>,
) -> ClipNodeId { if clips.is_empty() { return id;
}
for clip in clips { let key = *clip; // ClipEntry is Copy
let node_index = nodes[id.0as usize]
.children
.get(&key)
.cloned();
let node_index = match node_index {
Some(node_index) => node_index,
None => { let node_index = ClipNodeId(nodes.len() as u32);
nodes[id.0as usize].children.insert(key, node_index);
nodes.push(ClipTreeNode {
handle: key.handle,
spatial_node_index: key.spatial_node_index,
unsnapped_clip_rect: key.clip_rect.into(),
children: FastHashMap::default(),
parent: id,
});
node_index
}
};
id = node_index;
}
id
}
/// Add a set of clips to the provided tree node id, reusing existing /// nodes in the tree where possible pubfn add(
&mutself,
root: ClipNodeId,
clips: &[ClipEntry],
) -> ClipNodeId {
ClipTree::add_impl(
root,
clips,
&mutself.nodes,
)
}
/// Get the current clip root (the node in the clip-tree where clips can be /// ignored when building the clip-chain instance for a primitive) pubfn current_clip_root(&self) -> ClipNodeId { self.clip_root_stack.last().cloned().unwrap()
}
/// Push a clip root (e.g. when a surface is encountered) that prevents clips /// from this node and above being applied to primitives within the root. pubfn push_clip_root_leaf(&mutself, clip_leaf_id: ClipLeafId) { let leaf = &self.leaves[clip_leaf_id.0as usize]; self.clip_root_stack.push(leaf.node_id);
}
/// Push a clip root (e.g. when a surface is encountered) that prevents clips /// from this node and above being applied to primitives within the root. pubfn push_clip_root_node(&mutself, clip_node_id: ClipNodeId) { self.clip_root_stack.push(clip_node_id);
}
/// Pop a clip root, when exiting a surface. pubfn pop_clip_root(&mutself) { self.clip_root_stack.pop().unwrap();
}
/// Retrieve a clip tree node by id pubfn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
assert!(id != ClipNodeId::NONE);
&self.nodes[id.0as usize]
}
pubfn get_parent(&self, id: ClipNodeId) -> Option<ClipNodeId> { // Invalid ids point to the first item in the nodes vector which // has an invalid id for the parent so we don't need to handle // `id` being invalid separately. let parent = self.nodes[id.0as usize].parent; if parent == ClipNodeId::NONE { return None;
}
return Some(parent)
}
/// Retrieve a clip tree leaf by id pubfn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
&self.leaves[id.0as usize]
}
/// Mutable accessor for a single leaf. Used by the visibility pass from /// inside the cluster loop to refresh `snapped_local_clip_rect` against /// the same spatial node as the owning prim's rect. pubfn get_leaf_mut(&mutself, id: ClipLeafId) -> &mut ClipTreeLeaf {
&mutself.leaves[id.0as usize]
}
/// Debug print the clip-tree #[allow(unused)] pubfn print(&self) { usecrate::print_tree::PrintTree;
for i in0 .. self.leaves.len() {
print_leaf(ClipLeafId(i as u32), &self.leaves, &mut pt);
}
}
/// Find the lowest common ancestor of two clip tree nodes. This is useful /// to identify shared clips between primitives attached to different clip-leaves. pubfn find_lowest_common_ancestor(
&self, mut node1: ClipNodeId, mut node2: ClipNodeId,
) -> ClipNodeId { // TODO(gw): Consider caching / storing the depth in the node? fn get_node_depth(
id: ClipNodeId,
nodes: &[ClipTreeNode],
) -> usize { letmut depth = 0; letmut current = id;
while current != ClipNodeId::NONE { let node = &nodes[current.0as usize];
depth += 1;
current = node.parent;
}
/// A reference to an interned clip paired with the spatial node that positions it. #[derive(Copy, Clone, PartialEq, Eq, Hash, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipEntry { pub handle: ClipDataHandle, pub spatial_node_index: SpatialNodeIndex, pub clip_rect: RectKey,
}
/// Represents a clip-chain as defined by the public API that we decompose in to /// the clip-tree. In future, we would like to remove this and have Gecko directly /// build the clip-tree. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipChain {
parent: Option<usize>,
clips: Vec<ClipEntry>,
}
#[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipStackEntry { /// Cache the previous clip-chain build, since this is a common case
last_clip_chain_cache: Option<(ClipChainId, ClipNodeId)>,
/// Set of clips that were already seen and included in clip_node_id
seen_clips: FastHashSet<ClipEntry>,
/// The build clip_node_id for this level of the stack
clip_node_id: ClipNodeId,
}
/// Used by the scene builder to build the clip-tree that is part of the built scene. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipTreeBuilder { /// Clips defined by the display list
clip_map: FastHashMap<ClipId, ClipEntry>,
/// Clip-chains defined by the display list
clip_chains: Vec<ClipChain>,
clip_chain_map: FastHashMap<ClipChainId, usize>,
/// List of clips pushed/popped by grouping items, such as stacking contexts and iframes
clip_stack: Vec<ClipStackEntry>,
/// The tree we are building
tree: ClipTree,
/// A temporary buffer stored here to avoid constant heap allocs/frees
clip_handles_buffer: Vec<ClipEntry>,
}
/// Define a clip-chain pubfn define_clip_chain<I: Iterator<Item = ClipId>>(
&mutself,
id: ClipChainId,
parent: Option<ClipChainId>,
clips: I,
) { let parent = parent.map(|ref id| self.clip_chain_map[id]); let index = self.clip_chains.len(); let clips = clips.map(|clip_id| { self.clip_map[&clip_id]
}).collect(); self.clip_chains.push(ClipChain {
parent,
clips,
}); self.clip_chain_map.insert(id, index);
}
/// Push a clip-chain that will be applied to any prims built prior to next pop pubfn push_clip_chain(
&mutself,
clip_chain_id: Option<ClipChainId>,
reset_seen: bool,
ignore_ancestor_clips: bool,
) { let (mut clip_node_id, mut seen_clips) = { let prev = self.clip_stack.last().unwrap(); let clip_node_id = if ignore_ancestor_clips {
ClipNodeId::NONE
} else {
prev.clip_node_id
};
(clip_node_id, prev.seen_clips.clone())
};
/// Push a clip-id that will be applied to any prims built prior to next pop pubfn push_clip_id(
&mutself,
clip_id: ClipId,
) { let (clip_node_id, mut seen_clips) = { let prev = self.clip_stack.last().unwrap();
(prev.clip_node_id, prev.seen_clips.clone())
};
self.clip_handles_buffer.clear(); let clip_entry = self.clip_map[&clip_id];
if seen_clips.insert(clip_entry) { self.clip_handles_buffer.push(clip_entry);
}
let clip_node_id = self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
);
/// Pop a clip off the clip_stack, when exiting a grouping item pubfn pop_clip(&mutself) { self.clip_stack.pop().unwrap();
}
/// Add clips from a given clip-chain to the set of clips for a primitive during clip-set building fn add_clips(
clip_chain_index: usize,
seen_clips: &mut FastHashSet<ClipEntry>,
output: &mut Vec<ClipEntry>,
clip_chains: &[ClipChain],
) { // TODO(gw): It's possible that we may see clip outputs that include identical clips // (e.g. if there is a clip positioned by two spatial nodes, where one spatial // node is a child of the other, and has an identity transform). If we ever // see this in real-world cases, it might be worth checking for that here and // excluding them, to ensure the shape of the tree matches what we need for // finding shared_clips for tile caches etc.
for clip_entry in clip_chain.clips.iter().rev() { if seen_clips.insert(*clip_entry) {
output.push(*clip_entry);
}
}
}
/// Main entry point to build a path in the clip-tree for a given primitive pubfn build_clip_set(
&mutself,
clip_chain_id: ClipChainId,
) -> ClipNodeId { let clip_stack = self.clip_stack.last_mut().unwrap();
// We mutated the `clip_stack.seen_clips` in order to remove duplicate clips from // the supplied `clip_chain_id`. Now step through and remove any clips we added // to the set, so we don't get incorrect results next time `build_clip_set` is // called for a different clip-chain. Doing it this way rather than cloning means // we avoid heap allocations for each `build_clip_set` call. for entry in &self.clip_handles_buffer {
clip_stack.seen_clips.remove(entry);
}
let clip_node_id = self.tree.add(
clip_stack.clip_node_id,
&self.clip_handles_buffer,
);
/// Check if a clip-chain has complex (non-rectangular) clips pubfn clip_chain_has_complex_clips(
&self,
clip_chain_id: ClipChainId,
interners: &Interners,
) -> bool { let clip_chain_index = self.clip_chain_map[&clip_chain_id]; self.has_complex_clips_impl(clip_chain_index, interners)
}
/// Check if all complex clips in a clip chain are fixed-position rounded /// rectangles (in Clip mode). When true, the intermediate surface for a /// root-level stacking context can be skipped because the clips will be /// promoted to compositor clips on the tile cache slices. pubfn clip_chain_complex_clips_are_promotable(
&self,
clip_chain_id: ClipChainId,
interners: &Interners,
spatial_tree: &SceneSpatialTree,
) -> bool { let clip_chain_index = self.clip_chain_map[&clip_chain_id]; self.complex_clips_are_promotable_impl(clip_chain_index, interners, spatial_tree)
}
/// Finalize building and return the clip-tree pubfn finalize(&mutself) -> ClipTree { // Note: After this, the builder's clip tree does not hold allocations and // is not in valid state. `ClipTreeBuilder::begin()` must be called before // building can happen again.
std::mem::replace(&mutself.tree, ClipTree {
nodes: Vec::new(),
leaves: Vec::new(),
clip_root_stack: Vec::new(),
})
}
/// Get a clip node by id pubfn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
assert!(id != ClipNodeId::NONE);
&self.tree.nodes[id.0as usize]
}
/// Get a clip leaf by id pubfn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
&self.tree.leaves[id.0as usize]
}
/// Build a clip-leaf for a tile-cache pubfn build_for_tile_cache(
&mutself,
clip_node_id: ClipNodeId,
extra_clips: &[ClipId],
) -> ClipLeafId { self.clip_handles_buffer.clear();
for clip_id in extra_clips { let entry = self.clip_map[clip_id]; self.clip_handles_buffer.push(entry);
}
let node_id = self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
);
let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
/// Build a clip-leaf for a normal primitive pubfn build_for_prim(
&mutself,
clip_node_id: ClipNodeId,
info: &LayoutPrimitiveInfo,
extra_clips: &[ClipItemEntry],
interners: &mut Interners,
) -> ClipLeafId {
let node_id = if extra_clips.is_empty() {
clip_node_id
} else { // TODO(gw): Cache the previous build of clip-node / clip-leaf to handle cases where we get a // lot of primitives referencing the same clip set (e.g. dl_mutate and similar tests) self.clip_handles_buffer.clear();
for clip_item_entry in extra_clips { // Intern this clip item, and store the handle // in the clip chain node. let handle = interners.clip.intern(&clip_item_entry.key, || {
ClipInternData {
key: clip_item_entry.key.clone(),
}
});
/// Helper to identify simple clips (normal rects) from other kinds of clips, /// which can often be handled via fast code paths. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone, MallocSizeOf)] pubenum ClipNodeKind { /// A normal clip rectangle, with Clip mode.
Rectangle, /// A rectangle with ClipOut, or any other kind of clip.
Complex,
}
// Result of comparing a clip node instance against a local rect. #[derive(Debug)] enum ClipResult { // The clip does not affect the region at all.
Accept, // The clip prevents the region from being drawn.
Reject, // The clip affects part of the region. This may // require a clip mask, depending on other factors.
Partial,
}
// A clip node is a single clip source, along with some // positioning information and implementation details // that control where the GPU data for this clip source // can be found. #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(MallocSizeOf)] pubstruct ClipNode { pub item: ClipItem,
}
// Convert from an interning key for a clip item // to a clip node, which is cached in the document. impl From<ClipItemKey> for ClipNode { fn from(item: ClipItemKey) -> Self { let kind = match item.kind {
ClipItemKeyKind::Rectangle(mode) => {
ClipItemKind::Rectangle { mode }
}
ClipItemKeyKind::RoundedRectangle(radius, mode) => {
ClipItemKind::RoundedRectangle {
radius: radius.into(),
mode,
}
}
ClipItemKeyKind::ImageMask(image, polygon_handle) => {
ClipItemKind::Image {
image,
polygon_handle,
}
}
};
ClipNode {
item: ClipItem {
kind,
},
}
}
}
// Flags that are attached to instances of clip nodes. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash, MallocSizeOf)] pubstruct ClipNodeFlags(u8);
// When a clip node is found to be valid for a // clip chain instance, it's stored in an index // buffer style structure. This struct contains // an index to the node data itself, as well as // some flags describing how this clip node instance // is positioned. #[derive(Debug, Clone, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipNodeInstance { pub handle: ClipDataHandle, pub spatial_node_index: SpatialNodeIndex, pub clip_rect: LayoutRect, pub flags: ClipNodeFlags, pub visible_tiles: Option<ops::Range<usize>>,
}
// A range of clip node instances that were found by // building a clip chain instance. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipNodeRange { pub first: u32, pub count: u32,
}
impl ClipNodeRange { pubfn to_range(&self) -> ops::Range<usize> { let start = self.first as usize; let end = start + self.count as usize;
ops::Range {
start,
end,
}
}
}
/// A helper struct for converting between coordinate systems /// of clip sources and primitives. /// /// Note that the variants don't represent the same transformation /// because depending on the situation we either map between the /// clip and primitive spaces or project them both to visibility /// space. // todo(gw): optimize: // separate arrays for matrices // cache and only build as needed. //TODO: merge with `CoordinateSpaceMapping`? #[derive(Debug, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] pubenum ClipSpaceConversion { /// The clip and the clipped primitive are in the same coordinate space.
Local, /// The clip and the clipped primitive are in the same coordinate system. /// /// This variant represents the transform from the clip's local space to /// the clipped primitive's local space.
ScaleOffset(ScaleOffset), /// The clip and the clipped primitive are in different coordinate system. /// /// This Variant represents the transform from the clip's local space to /// the visibility space.
Transform(LayoutToVisTransform),
}
impl ClipSpaceConversion { /// Construct a new clip space converter between two spatial nodes. pubfn new(
prim_spatial_node_index: SpatialNodeIndex,
clip_spatial_node_index: SpatialNodeIndex,
visibility_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
) -> Self { //Note: this code is different from `get_relative_transform` in a way that we only try // getting the relative transform if it's Local or ScaleOffset, // falling back to the world transform otherwise. let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index); let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index);
// Temporary information that is cached and reused // during building of a clip chain instance. #[derive(MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] struct ClipNodeInfo {
conversion: ClipSpaceConversion,
handle: ClipDataHandle,
spatial_node_index: SpatialNodeIndex,
clip_rect: LayoutRect,
}
impl ClipNodeInfo { fn create_instance(
&self,
node: &ClipNode,
clipped_rect: &LayoutRect,
gpu_buffer: &mut GpuBufferBuilderF,
resource_cache: &mut ResourceCache,
mask_tiles: &mut Vec<VisibleMaskImageTile>,
spatial_tree: &SpatialTree,
rg_builder: &mut RenderTaskGraphBuilder,
request_resources: bool,
) -> Option<ClipNodeInstance> { // Calculate some flags that are required for the segment // building logic. letmut flags = self.conversion.to_flags();
// Some clip shaders support a fast path mode for simple clips. // TODO(gw): We could also apply fast path when segments are created, since we only write // the mask for a single corner at a time then, so can always consider radii uniform. let is_raster_2d =
flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
spatial_tree
.get_world_viewport_transform(self.spatial_node_index)
.is_2d_axis_aligned(); if is_raster_2d && node.item.kind.supports_fast_path_rendering(self.clip_rect) {
flags |= ClipNodeFlags::USE_FAST_PATH;
}
letmut visible_tiles = None;
iflet ClipItemKind::Image { image, .. } = node.item.kind { let rect = self.clip_rect; let request = ImageRequest {
key: image,
rendering: ImageRendering::Auto,
tile: None,
};
// Bug 1648323 - It is unclear why on rare occasions we get // a clipped_rect that does not intersect the clip's mask rect. // defaulting to clipped_rect here results in zero repetitions // which clips the primitive entirely. let visible_rect =
clipped_rect.intersection(&rect).unwrap_or(*clipped_rect);
let repetitions = image_tiling::repetitions(
&rect,
&visible_rect,
rect.size(),
);
for Repetition { origin, .. } in repetitions { let layout_image_rect = LayoutRect::from_origin_and_size(
origin,
rect.size(),
); let tiles = image_tiling::tiles(
&layout_image_rect,
&visible_rect,
&props.visible_rect,
tile_size as i32,
); for tile in tiles { let req = request.with_tile(tile.offset);
if request_resources {
resource_cache.request_image(
req,
gpu_buffer,
);
}
let task_id = rg_builder.add().init(
RenderTask::new_image(props.descriptor.size, req, false)
);
visible_tiles = Some(tile_range_start .. mask_tiles.len());
}
} else { // If the supplied image key doesn't exist in the resource cache, // skip the clip node since there is nothing to mask with.
warn!("Clip mask with missing image key {:?}", request.key); return None;
}
}
/// The main clipping public interface that other modules access. #[derive(MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] pubstruct ClipStore { pub clip_node_instances: Vec<ClipNodeInstance>,
mask_tiles: Vec<VisibleMaskImageTile>,
// A clip chain instance is what gets built for a given clip // chain id + local primitive region + positioning node. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] pubstruct ClipChainInstance { pub clips_range: ClipNodeRange, // Combined clip rect for clips that are in the // same coordinate system as the primitive. pub local_clip_rect: LayoutRect, pub has_non_local_clips: bool, // If true, this clip chain requires allocation // of a clip mask. pub needs_mask: bool, // Combined clip rect in picture space (may // be more conservative that local_clip_rect). pub pic_coverage_rect: PictureRect, // Space, in which the `pic_coverage_rect` is defined. pub pic_spatial_node_index: SpatialNodeIndex,
}
/// Setup the active clip chains for building a clip chain instance. pubfn set_active_clips(
&mutself,
prim_spatial_node_index: SpatialNodeIndex,
pic_spatial_node_index: SpatialNodeIndex,
visibility_spatial_node_index: SpatialNodeIndex,
snapper: &mut SpaceSnapper,
clip_leaf_id: ClipLeafId,
spatial_tree: &SpatialTree,
clip_data_store: &ClipDataStore,
clip_tree: &ClipTree,
) { self.active_clip_node_info.clear(); self.active_local_clip_rect = None; self.active_pic_coverage_rect = PictureRect::max_rect();
let clip_root = clip_tree.current_clip_root(); let clip_leaf = clip_tree.get_leaf(clip_leaf_id);
// The leaf has been pre-snapped by the visibility pass for this frame; // ancestor node clip rects are snapped on demand below, via `snapper` // (bound to the same surface raster node the prim snapped against). letmut local_clip_rect = clip_leaf.snapped_local_clip_rect; letmut current = clip_leaf.node_id;
while current != clip_root && current != ClipNodeId::NONE { let node = clip_tree.get_node(current);
/// Setup the active clip chains, based on an existing primitive clip chain instance. pubfn set_active_clips_from_clip_chain(
&mutself,
prim_clip_chain: &ClipChainInstance,
prim_spatial_node_index: SpatialNodeIndex,
visibility_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
) { // TODO(gw): Although this does less work than set_active_clips(), it does // still do some unnecessary work (such as the clip space conversion). // We could consider optimizing this if it ever shows up in a profile.
let clip_instances = &self
.clip_node_instances[prim_clip_chain.clips_range.to_range()]; for clip_instance in clip_instances { let conversion = ClipSpaceConversion::new(
prim_spatial_node_index,
clip_instance.spatial_node_index,
visibility_spatial_node_index,
spatial_tree,
); self.active_clip_node_info.push(ClipNodeInfo {
handle: clip_instance.handle,
conversion,
spatial_node_index: clip_instance.spatial_node_index,
clip_rect: clip_instance.clip_rect,
});
}
}
/// Given a clip-chain instance, return a safe rect within the visible region /// that can be assumed to be unaffected by clip radii. Returns None if it /// encounters any complex cases, just handling rounded rects in the same /// coordinate system as the clip-chain for now. pubfn get_inner_rect_for_clip_chain(
&self,
clip_chain: &ClipChainInstance,
clip_data_store: &ClipDataStore,
spatial_tree: &SpatialTree,
) -> Option<PictureRect> { letmut inner_rect = clip_chain.pic_coverage_rect; let clip_instances = &self
.clip_node_instances[clip_chain.clips_range.to_range()];
for clip_instance in clip_instances { // Don't handle mapping between coord systems for now if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { return None;
}
let clip_node = &clip_data_store[clip_instance.handle];
match clip_node.item.kind { // Ignore any clips which are complex or impossible to calculate // inner rects for now
ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
ClipItemKind::Image { .. } |
ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => { return None;
} // Normal Clip rects are already handled by the clip-chain pic_coverage_rect, // no need to do anything here
ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {}
ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, radius } => { // Get an inner rect for the rounded-rect clip let radius = clamped_radius(&radius, clip_instance.clip_rect.size()); let local_inner_rect = match extract_inner_rect_safe(&clip_instance.clip_rect, &radius) {
Some(rect) => rect,
None => return None,
};
// Map it from local -> picture space let mapper = SpaceMapper::new_with_target(
clip_chain.pic_spatial_node_index,
clip_instance.spatial_node_index,
PictureRect::max_rect(),
spatial_tree,
);
// Accumulate in to the inner_rect, in case there are multiple rounded-rect clips iflet Some(pic_inner_rect) = mapper.map(&local_inner_rect) {
inner_rect = inner_rect.intersection(&pic_inner_rect).unwrap_or(PictureRect::zero());
}
}
}
}
Some(inner_rect)
}
// Directly construct a clip node range, ready for rendering, from an interned clip handle. // Typically useful for drawing specific clips on custom pattern / child render tasks that // aren't primitives. // TODO(gw): For now, we assume they are local clips only - in future we might want to support // non-local clips. pubfn push_clip_instance(
&mutself,
handle: ClipDataHandle,
spatial_node_index: SpatialNodeIndex,
clip_rect: LayoutRect,
) -> ClipNodeRange { let first = self.clip_node_instances.len() as u32;
/// The main interface external code uses. Given a local primitive, positioning /// information, and a clip chain id, build an optimized clip chain instance. pubfn build_clip_chain_instance(
&mutself,
local_prim_rect: LayoutRect,
prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
pic_to_vis_mapper: &SpaceMapper<PicturePixel, VisPixel>,
spatial_tree: &SpatialTree,
gpu_buffer: &mut GpuBufferBuilderF,
resource_cache: &mut ResourceCache,
culling_rect: &VisRect,
clip_data_store: &mut ClipDataStore,
rg_builder: &mut RenderTaskGraphBuilder,
request_resources: bool,
) -> Option<ClipChainInstance> { let local_clip_rect = matchself.active_local_clip_rect {
Some(rect) => rect,
None => return None,
};
profile_scope!("build_clip_chain_instance");
let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?; letmut pic_coverage_rect = prim_to_pic_mapper.map(&local_bounding_rect)?; let vis_clip_rect = pic_to_vis_mapper.map(&pic_coverage_rect)?;
// Now, we've collected all the clip nodes that *potentially* affect this // primitive region, and reduced the size of the prim region as much as possible.
// Run through the clip nodes, and see which ones affect this prim region.
let first_clip_node_index = self.clip_node_instances.len() as u32; letmut has_non_local_clips = false; letmut needs_mask = false;
// For each potential clip node for node_info inself.active_clip_node_info.drain(..) { let node = &mut clip_data_store[node_info.handle];
// See how this clip affects the prim region. let clip_result = match node_info.conversion {
ClipSpaceConversion::Local => {
node.item.kind.get_clip_result(&local_bounding_rect, node_info.clip_rect)
}
ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
has_non_local_clips = true;
node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect), node_info.clip_rect)
}
ClipSpaceConversion::Transform(ref transform) => {
has_non_local_clips = true;
node.item.kind.get_clip_result_complex(
transform,
&vis_clip_rect,
culling_rect,
node_info.clip_rect,
)
}
};
match clip_result {
ClipResult::Accept => { // Doesn't affect the primitive at all, so skip adding to list
}
ClipResult::Reject => { // Completely clips the supplied prim rect return None;
}
ClipResult::Partial => { // Needs a mask -> add to clip node indices
// Create the clip node instance for this clip node iflet Some(instance) = node_info.create_instance(
node,
&local_bounding_rect,
gpu_buffer,
resource_cache,
&mutself.mask_tiles,
spatial_tree,
rg_builder,
request_resources,
) { // As a special case, a partial accept of a clip rect that is // in the same coordinate system as the primitive doesn't need // a clip mask. Instead, it can be handled by the primitive // vertex shader as part of the local clip rect. This is an // important optimization for reducing the number of clip // masks that are allocated on common pages.
needs_mask |= match node.item.kind {
ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
ClipItemKind::RoundedRectangle { .. } |
ClipItemKind::Image { .. } => { true
}
// Store this in the index buffer for this clip chain instance. self.clip_node_instances.push(instance);
}
}
}
}
// Get the range identifying the clip nodes in the index buffer. let clips_range = ClipNodeRange {
first: first_clip_node_index,
count: self.clip_node_instances.len() as u32 - first_clip_node_index,
};
// If this clip chain needs a mask, reduce the size of the mask allocation // by any clips that were in the same space as the picture. This can result // in much smaller clip mask allocations in some cases. Note that the ordering // here is important - the reduction must occur *after* the clip item accept // reject checks above, so that we don't eliminate masks accidentally (since // we currently only support a local clip rect in the vertex shader). if needs_mask {
pic_coverage_rect = pic_coverage_rect.intersection(&self.active_pic_coverage_rect)?;
}
// The ClipItemKey is a hashable representation of the geometry of // a clip item. It is used during interning to de-duplicate clip nodes // between frames and display lists. This allows quick comparison of // clip node equality by handle, and also allows the uploaded GPU cache // handle to be retained between display lists. The spatial node index // and clip rect are intentionally excluded from the key so that clips // with the same shape but different positioning or size can share // interned data. For rounded-rect clips the stored radii are unclamped; // each consumer clamps against the instance clip rect as needed. // TODO(gw): Maybe we should consider constructing these directly // in the DL builder? #[derive(Copy, Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubenum ClipItemKeyKind {
Rectangle(ClipMode),
RoundedRectangle(BorderRadiusAu, ClipMode),
ImageMask(ImageKey, Option<PolygonDataHandle>),
}
/// A clip item key paired with the spatial node that positions it, used during scene building. #[derive(Copy, Clone)] pubstruct ClipItemEntry { pub key: ClipItemKey, pub spatial_node_index: SpatialNodeIndex, pub clip_rect: LayoutRect,
}
/// The data available about an interned clip node during scene building #[derive(Debug, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ClipInternData { pub key: ClipItemKey,
}
impl intern::InternDebug for ClipItemKey {}
impl intern::Internable for ClipIntern { type Key = ClipItemKey; type StoreData = ClipNode; type InternData = ClipInternData; const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CLIPS;
}
/// Clamp corner radii so adjacent radii don't overlap along an edge of `size`. /// Rounded-rect radii are interned unclamped, so consumers must clamp against /// the instance-specific clip rect before use. pubfn clamped_radius(radius: &BorderRadius, size: LayoutSize) -> BorderRadius { letmut r = *radius;
ensure_no_corner_overlap(&mut r, size);
r
}
impl ClipItemKind { /// Returns true if this clip mask can run through the fast path /// for the given clip item type. /// /// Note: this logic has to match `ClipBatcher::add` behavior. fn supports_fast_path_rendering(&self, clip_rect: LayoutRect) -> bool { match *self {
ClipItemKind::Rectangle { .. } |
ClipItemKind::Image { .. } => { false
}
ClipItemKind::RoundedRectangle { ref radius, .. } => {
radius.can_use_fast_path_in(&clip_rect)
}
}
}
// Get an optional clip rect that a clip source can provide to // reduce the size of a primitive region. This is typically // used to eliminate redundant clips, and reduce the size of // any clip mask that eventually gets drawn. pubfn get_local_clip_rect(&self, clip_rect: LayoutRect) -> Option<LayoutRect> { match *self {
ClipItemKind::Rectangle { mode: ClipMode::Clip } => Some(clip_rect),
ClipItemKind::Rectangle { mode: ClipMode::ClipOut } => None,
ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, .. } => Some(clip_rect),
ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => None,
ClipItemKind::Image { .. } => Some(clip_rect),
}
}
// Check how a given clip source affects a local primitive region. fn get_clip_result(
&self,
prim_rect: &LayoutRect,
clip_rect: LayoutRect,
) -> ClipResult { match *self {
ClipItemKind::Rectangle { mode: ClipMode::Clip } => { let rect = clip_rect; if rect.contains_box(prim_rect) { return ClipResult::Accept;
}
match rect.intersection(prim_rect) {
Some(..) => {
ClipResult::Partial
}
None => {
ClipResult::Reject
}
}
}
ClipItemKind::Rectangle { mode: ClipMode::ClipOut } => { let rect = clip_rect; if rect.contains_box(prim_rect) { return ClipResult::Reject;
}
match rect.intersection(prim_rect) {
Some(_) => {
ClipResult::Partial
}
None => {
ClipResult::Accept
}
}
}
ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip } => { let rect = clip_rect; let radius = clamped_radius(radius, rect.size()); // TODO(gw): Consider caching this in the ClipNode // if it ever shows in profiles. if rounded_rectangle_contains_box_quick(&rect, &radius, &prim_rect) { return ClipResult::Accept;
}
match rect.intersection(prim_rect) {
Some(..) => {
ClipResult::Partial
}
None => {
ClipResult::Reject
}
}
}
ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::ClipOut } => { let rect = clip_rect; let radius = clamped_radius(radius, rect.size()); // TODO(gw): Consider caching this in the ClipNode // if it ever shows in profiles. if rounded_rectangle_contains_box_quick(&rect, &radius, &prim_rect) { return ClipResult::Reject;
}
/// Return true if the rounded rectangle described by `container` and `radii` /// definitely contains `containee`. May return false negatives, but never false /// positives. fn rounded_rectangle_contains_box_quick(
container: &LayoutRect,
radii: &BorderRadius,
containee: &LayoutRect,
) -> bool { if !container.contains_box(containee) { returnfalse;
}
/// Return true if `point` falls within `corner`. This only covers the /// upper-left case; we transform the other corners into that form. fn foul(point: LayoutPoint, corner: LayoutPoint) -> bool {
point.x < corner.x && point.y < corner.y
}
/// Flip `pt` about the y axis (i.e. negate `x`). fn flip_x(pt: LayoutPoint) -> LayoutPoint {
LayoutPoint { x: -pt.x, .. pt }
}
/// Flip `pt` about the x axis (i.e. negate `y`). fn flip_y(pt: LayoutPoint) -> LayoutPoint {
LayoutPoint { y: -pt.y, .. pt }
}
/// Test where point p is relative to the infinite line that passes through the segment /// defined by p0 and p1. Point p is on the "left" of the line if the triangle (p0, p1, p) /// forms a counter-clockwise triangle. /// > 0 is left of the line /// < 0 is right of the line /// == 0 is on the line pubfn is_left_of_line(
p_x: f32,
p_y: f32,
p0_x: f32,
p0_y: f32,
p1_x: f32,
p1_y: f32,
) -> f32 {
(p1_x - p0_x) * (p_y - p0_y) - (p_x - p0_x) * (p1_y - p0_y)
}
// p is a LayoutPoint that we'll be comparing to dimensionless PointKeys, // which were created from LayoutPoints, so it all works out. let p = LayoutPoint::new(point.x - rect.min.x, point.y - rect.min.y);
// Calculate a winding number for this point. letmut winding_number: i32 = 0;
let count = polygon.point_count as usize;
for i in0..count { let p0 = polygon.points[i]; let p1 = polygon.points[(i + 1) % count];
pubfn projected_rect_contains(
source_rect: &LayoutRect,
transform: &LayoutToVisTransform,
target_rect: &VisRect,
) -> Option<()> { let points = [
transform.transform_point2d(source_rect.top_left())?,
transform.transform_point2d(source_rect.top_right())?,
transform.transform_point2d(source_rect.bottom_right())?,
transform.transform_point2d(source_rect.bottom_left())?,
]; let target_points = [
target_rect.top_left(),
target_rect.top_right(),
target_rect.bottom_right(),
target_rect.bottom_left(),
]; // iterate the edges of the transformed polygon for (a, b) in points
.iter()
.cloned()
.zip(points[1..].iter().cloned().chain(iter::once(points[0])))
{ // If this edge is redundant, it's a weird, case, and we shouldn't go // length in trying to take the fast path (e.g. when the whole rectangle is a point). // If any of edges of the target rectangle crosses the edge, it's not completely // inside our transformed polygon either. if a.approx_eq(&b) || target_points.iter().any(|&c| (b - a).cross(c - a) < 0.0) { return None
}
}
Some(())
}
// Add a clip node into the list of clips to be processed // for the current clip chain. Returns false if the clip // results in the entire primitive being culled out. fn add_clip_node_to_current_chain(
handle: ClipDataHandle,
clip_spatial_node_index: SpatialNodeIndex,
clip_rect: LayoutRect,
prim_spatial_node_index: SpatialNodeIndex,
pic_spatial_node_index: SpatialNodeIndex,
visibility_spatial_node_index: SpatialNodeIndex,
local_clip_rect: &mut LayoutRect,
clip_node_info: &mut Vec<ClipNodeInfo>,
pic_coverage_rect: &mut PictureRect,
clip_data_store: &ClipDataStore,
spatial_tree: &SpatialTree,
) -> bool {
let clip_node = &clip_data_store[handle];
// Determine the most efficient way to convert between coordinate // systems of the primitive and clip node.
let conversion = ClipSpaceConversion::new(
prim_spatial_node_index,
clip_spatial_node_index,
visibility_spatial_node_index,
spatial_tree,
);
// If we can convert spaces, try to reduce the size of the region // requested, and cache the conversion information for the next step. if let Some(clip_rect) = clip_node.item.kind.get_local_clip_rect(clip_rect) {
match conversion {
ClipSpaceConversion::Local => {
*local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
Some(rect) => rect,
None => returnfalse,
};
}
ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
let clip_rect = scale_offset.map_rect(&clip_rect);
*local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
Some(rect) => rect,
None => returnfalse,
};
}
ClipSpaceConversion::Transform(..) => { // Map the local clip rect directly into the same space as the picture // surface. This will often be the same space as the clip itself, which // results in a reduction in allocated clip mask size.
// For simplicity, only apply this optimization if the clip is in the // same coord system as the picture. There are some 'advanced' perspective // clip tests in wrench that break without this check. Those cases are // never used in Gecko, and we aim to remove support in WR for that // in future to simplify the clipping pipeline.
let pic_coord_system = spatial_tree
.get_spatial_node(pic_spatial_node_index)
.coordinate_system_id;
let clip_coord_system = spatial_tree
.get_spatial_node(clip_spatial_node_index)
.coordinate_system_id;
if pic_coord_system == clip_coord_system {
let mapper = SpaceMapper::new_with_target(
pic_spatial_node_index,
clip_spatial_node_index,
PictureRect::max_rect(),
spatial_tree,
);
if let Some(pic_clip_rect) = mapper.map(&clip_rect) {
*pic_coverage_rect = pic_clip_rect
.intersection(pic_coverage_rect)
.unwrap_or(PictureRect::zero());
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::projected_rect_contains;
use euclid::{Transform3D, rect};
use api::units::{LayoutRect, LayoutSize, LayoutPoint};
#[test]
fn test_empty_projected_rect() {
assert_eq!(
None,
projected_rect_contains(
&rect(10.0, 10.0, 0.0, 0.0).to_box2d(),
&Transform3D::identity(),
&rect(20.0, 20.0, 10.0, 10.0).to_box2d(),
), "Empty rectangle is considered to include a non-empty!"
);
}
#[test]
fn test_intersect_identical() {
let rect = lr(0.0, 0.0, 400.0, 400.0);
let radius = uniform_radius(20.0);
let result = intersect_rounded_rects(rect, radius, rect, radius);
assert!(result.is_some());
let (r, rad) = result.unwrap();
assert_eq!(r, rect);
assert_eq!(rad.top_left.width, 20.0);
}
#[test]
fn test_intersect_inner_fully_inside() {
let outer = lr(0.0, 0.0, 400.0, 400.0);
let inner = lr(50.0, 50.0, 300.0, 300.0);
let result = intersect_rounded_rects(
outer, uniform_radius(20.0),
inner, uniform_radius(15.0),
);
assert!(result.is_some());
let (r, rad) = result.unwrap();
assert_eq!(r, inner);
assert_eq!(rad.top_left.width, 15.0);
assert_eq!(rad.bottom_right.width, 15.0);
}
#[test]
fn test_intersect_shared_top_different_bottom() {
let outer = lr(0.0, 0.0, 400.0, 400.0);
let inner = lr(0.0, 0.0, 400.0, 350.0);
let result = intersect_rounded_rects(
outer, uniform_radius(20.0),
inner, uniform_radius(15.0),
);
assert!(result.is_some());
let (r, rad) = result.unwrap();
assert_eq!(r, inner);
assert_eq!(rad.top_left.width, 20.0);
assert_eq!(rad.top_right.width, 20.0);
assert_eq!(rad.bottom_left.width, 15.0);
assert_eq!(rad.bottom_right.width, 15.0);
}
#[test]
fn test_intersect_no_overlap() {
let a = lr(0.0, 0.0, 100.0, 100.0);
let b = lr(200.0, 200.0, 100.0, 100.0);
let result = intersect_rounded_rects(a, uniform_radius(10.0), b, uniform_radius(10.0));
assert!(result.is_none());
}
#[test]
fn test_intersect_encroaching_corner() {
let outer = lr(0.0, 0.0, 400.0, 400.0);
let inner = lr(10.0, 10.0, 380.0, 380.0);
let result = intersect_rounded_rects(
outer, uniform_radius(200.0),
inner, uniform_radius(15.0),
);
assert!(result.is_none());
}
#[test]
fn test_intersect_zero_radius_no_encroach() {
let outer = lr(0.0, 0.0, 400.0, 400.0);
let inner = lr(50.0, 50.0, 300.0, 300.0);
let result = intersect_rounded_rects(
outer, uniform_radius(20.0),
inner, BorderRadius::zero(),
);
assert!(result.is_some());
let (_, rad) = result.unwrap();
assert_eq!(rad.top_left.width, 0.0);
assert_eq!(rad.bottom_right.width, 0.0);
}
#[test]
fn test_intersect_linux_window_corners() {
let window = lr(0.0, 0.0, 1920.0, 1080.0);
let content = lr(0.0, 40.0, 1920.0, 1040.0);
let window_radius = uniform_radius(10.0);
let content_radius = per_corner_radius(8.0, 0.0, 0.0, 0.0);
let result = intersect_rounded_rects(window, window_radius, content, content_radius);
assert!(result.is_some());
let (r, rad) = result.unwrap();
assert_eq!(r, content);
assert_eq!(rad.top_left.width, 8.0);
assert_eq!(rad.top_right.width, 0.0);
assert_eq!(rad.bottom_left.width, 10.0);
assert_eq!(rad.bottom_right.width, 10.0);
}
}
/// Try to intersect two ClipMode::Clip rounded rects (in the same coordinate /// space) into a single rounded rect. Returns None if the two rounded rects /// cannot be combined (e.g. their curved regions overlap in a way that can't /// be represented by a single rounded rect).
pub fn intersect_rounded_rects(
rect_a: LayoutRect,
radius_a: BorderRadius,
rect_b: LayoutRect,
radius_b: BorderRadius,
) -> Option<(LayoutRect, BorderRadius)> {
let result_rect = rect_a.intersection(&rect_b)?; if result_rect.is_empty() { return None;
}
if !radius_a.shapes_all_round() || !radius_b.shapes_all_round() { return None;
}
if !result_radius.can_use_fast_path_in(&result_rect) { return None;
}
Some((result_rect, result_radius))
}
/// Determine the radius at a single corner of the intersection of two rounded /// rects. Each corner is identified by: /// - (ix, iy): corner position in the intersection rect /// - (ax, ay), ra: corner position and radius from rect A /// - (bx, by), rb: corner position and radius from rect B /// - (sx, sy): direction signs toward the interior (e.g. top-left = +1,+1)
fn resolve_corner_radius(
ix: f32, iy: f32,
ax: f32, ay: f32, ra: LayoutSize,
bx: f32, by: f32, rb: LayoutSize,
sx: f32, sy: f32,
) -> Option<LayoutSize> {
let a_matches = ax == ix && ay == iy;
let b_matches = bx == ix && by == iy;
/// Check if a rounded corner region from a rect whose corner is at (cx, cy) /// with radius r extends into the intersection rect at corner (ix, iy). /// (sx, sy) are direction signs toward the rect interior from this corner.
fn corner_encroaches(
ix: f32, iy: f32,
cx: f32, cy: f32,
r: LayoutSize,
sx: f32, sy: f32,
) -> bool { if r.width == 0.0 || r.height == 0.0 { returnfalse;
}
let dx = sx * (ix - cx);
let dy = sy * (iy - cy);
r.width > dx && r.height > dy
}
/// PolygonKeys get interned, because it's a convenient way to move the data /// for the polygons out of the ClipItemKind and ClipItemKeyKind enums. The /// polygon data is both interned and retrieved by the scene builder, and not /// accessed at all by the frame builder. Another oddity is that the /// PolygonKey contains the totality of the information about the polygon, so /// the InternData and StoreData types are both PolygonKey. #[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] #[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
pub enum PolygonIntern {}
pub type PolygonDataHandle = intern::Handle<PolygonIntern>;
impl intern::InternDebug for PolygonKey {}
impl intern::Internable for PolygonIntern {
type Key = PolygonKey;
type StoreData = PolygonKey;
type InternData = PolygonKey; const PROFILE_COUNTER: usize = crate::profiler::INTERNED_POLYGONS;
}
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.