Quelle clip.rs
Sprache: unbekannt
|
|
/* 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/. */
//! 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::{BoxShadowClipMode, FillRule, ImageKey, ImageRendering};
use api::units::*;
use crate::image_tiling::{self, Repetition};
use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::ellipse::Ellipse;
use crate::gpu_cache::GpuCache;
use crate::gpu_types::{BoxShadowStretchMode};
use crate::intern;
use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo};
use crate::prim_store::{VisibleMaskImageTile};
use crate::prim_store::{PointKey, SizeKey, RectangleKey, PolygonKey};
use crate::render_task_cache::to_cache_size;
use crate::render_task::RenderTask;
use crate::render_task_graph::RenderTaskGraphBuilder;
use crate::resource_cache::{ImageRequest, ResourceCache};
use crate::scene_builder_thread::Interners;
use crate::space::SpaceMapper;
use crate::util::{clamp_to_scale_factor, MaxRect, extract_inner_rect_safe, project_rect, 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)]
pub struct ClipTreeNode {
pub handle: ClipDataHandle,
pub parent: ClipNodeId,
children: Vec<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).
}
/// 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)]
pub struct 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`.
pub local_clip_rect: LayoutRect,
}
/// ID for a ClipTreeNode
#[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipNodeId(u32);
impl ClipNodeId {
pub const NONE: ClipNodeId = ClipNodeId(0);
}
/// ID for a ClipTreeLeaf
#[derive(Debug, Copy, Clone, PartialEq, MallocSizeOf, Eq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipLeafId(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))]
pub struct ClipTree {
nodes: Vec<ClipTreeNode>,
leaves: Vec<ClipTreeLeaf>,
clip_root_stack: Vec<ClipNodeId>,
}
impl ClipTree {
pub fn new() -> Self {
ClipTree {
nodes: vec![
ClipTreeNode {
handle: ClipDataHandle::INVALID,
children: Vec::new(),
parent: ClipNodeId::NONE,
}
],
leaves: Vec::new(),
clip_root_stack: vec![
ClipNodeId::NONE,
],
}
}
pub fn reset(&mut self) {
self.nodes.clear();
self.nodes.push(ClipTreeNode {
handle: ClipDataHandle::INVALID,
children: Vec::new(),
parent: ClipNodeId::NONE,
});
self.leaves.clear();
self.clip_root_stack.clear();
self.clip_root_stack.push(ClipNodeId::NONE);
}
/// Add a set of clips to the provided tree node id, reusing existing
/// nodes in the tree where possible
fn add_impl(
id: ClipNodeId,
clips: &[ClipDataHandle],
nodes: &mut Vec<ClipTreeNode>,
) -> ClipNodeId {
if clips.is_empty() {
return id;
}
let handle = clips[0];
let next_clips = &clips[1..];
let node_index = nodes[id.0 as usize]
.children
.iter()
.find(|n| nodes[n.0 as usize].handle == handle)
.cloned();
let node_index = match node_index {
Some(node_index) => node_index,
None => {
let node_index = ClipNodeId(nodes.len() as u32);
nodes[id.0 as usize].children.push(node_index);
let node = ClipTreeNode {
handle,
children: Vec::new(),
parent: id,
};
nodes.push(node);
node_index
}
};
ClipTree::add_impl(
node_index,
next_clips,
nodes,
)
}
/// Add a set of clips to the provided tree node id, reusing existing
/// nodes in the tree where possible
pub fn add(
&mut self,
root: ClipNodeId,
clips: &[ClipDataHandle],
) -> ClipNodeId {
ClipTree::add_impl(
root,
clips,
&mut self.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)
pub fn 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.
pub fn push_clip_root_leaf(&mut self, clip_leaf_id: ClipLeafId) {
let leaf = &self.leaves[clip_leaf_id.0 as 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.
pub fn push_clip_root_node(&mut self, clip_node_id: ClipNodeId) {
self.clip_root_stack.push(clip_node_id);
}
/// Pop a clip root, when exiting a surface.
pub fn pop_clip_root(&mut self) {
self.clip_root_stack.pop().unwrap();
}
/// Retrieve a clip tree node by id
pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
assert!(id != ClipNodeId::NONE);
&self.nodes[id.0 as usize]
}
/// Retrieve a clip tree leaf by id
pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
&self.leaves[id.0 as usize]
}
/// Debug print the clip-tree
#[allow(unused)]
pub fn print(&self) {
use crate::print_tree::PrintTree;
fn print_node<T: crate::print_tree::PrintTreePrinter>(
id: ClipNodeId,
nodes: &[ClipTreeNode],
pt: &mut T,
) {
let node = &nodes[id.0 as usize];
pt.new_level(format!("{:?}", id));
pt.add_item(format!("{:?}", node.handle));
for child_id in &node.children {
print_node(*child_id, nodes, pt);
}
pt.end_level();
}
fn print_leaf<T: crate::print_tree::PrintTreePrinter>(
id: ClipLeafId,
leaves: &[ClipTreeLeaf],
pt: &mut T,
) {
let leaf = &leaves[id.0 as usize];
pt.new_level(format!("{:?}", id));
pt.add_item(format!("node_id: {:?}", leaf.node_id));
pt.add_item(format!("local_clip_rect: {:?}", leaf.local_clip_rect));
pt.end_level();
}
let mut pt = PrintTree::new("clip tree");
print_node(ClipNodeId::NONE, &self.nodes, &mut pt);
for i in 0 .. 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.
pub fn 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 {
let mut depth = 0;
let mut current = id;
while current != ClipNodeId::NONE {
let node = &nodes[current.0 as usize];
depth += 1;
current = node.parent;
}
depth
}
let mut depth1 = get_node_depth(node1, &self.nodes);
let mut depth2 = get_node_depth(node2, &self.nodes);
while depth1 > depth2 {
node1 = self.nodes[node1.0 as usize].parent;
depth1 -= 1;
}
while depth2 > depth1 {
node2 = self.nodes[node2.0 as usize].parent;
depth2 -= 1;
}
while node1 != node2 {
node1 = self.nodes[node1.0 as usize].parent;
node2 = self.nodes[node2.0 as usize].parent;
}
node1
}
}
/// 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))]
pub struct ClipChain {
parent: Option<usize>,
clips: Vec<ClipDataHandle>,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct 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<ClipDataHandle>,
/// 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))]
pub struct ClipTreeBuilder {
/// Clips defined by the display list
clip_map: FastHashMap<ClipId, ClipDataHandle>,
/// 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<ClipDataHandle>,
}
impl ClipTreeBuilder {
pub fn new() -> Self {
ClipTreeBuilder {
clip_map: FastHashMap::default(),
clip_chain_map: FastHashMap::default(),
clip_chains: Vec::new(),
clip_stack: vec![
ClipStackEntry {
clip_node_id: ClipNodeId::NONE,
last_clip_chain_cache: None,
seen_clips: FastHashSet::default(),
},
],
tree: ClipTree::new(),
clip_handles_buffer: Vec::new(),
}
}
pub fn begin(&mut self) {
self.clip_map.clear();
self.clip_chain_map.clear();
self.clip_chains.clear();
self.clip_stack.clear();
self.clip_stack.push(ClipStackEntry {
clip_node_id: ClipNodeId::NONE,
last_clip_chain_cache: None,
seen_clips: FastHashSet::default(),
});
self.tree.reset();
self.clip_handles_buffer.clear();
}
pub fn recycle_tree(&mut self, tree: ClipTree) {
self.tree = tree;
}
/// Define a new rect clip
pub fn define_rect_clip(
&mut self,
id: ClipId,
handle: ClipDataHandle,
) {
self.clip_map.insert(id, handle);
}
/// Define a new rounded rect clip
pub fn define_rounded_rect_clip(
&mut self,
id: ClipId,
handle: ClipDataHandle,
) {
self.clip_map.insert(id, handle);
}
/// Define a image mask clip
pub fn define_image_mask_clip(
&mut self,
id: ClipId,
handle: ClipDataHandle,
) {
self.clip_map.insert(id, handle);
}
/// Define a clip-chain
pub fn define_clip_chain<I: Iterator<Item = ClipId>>(
&mut self,
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
pub fn push_clip_chain(
&mut self,
clip_chain_id: Option<ClipChainId>,
reset_seen: bool,
) {
let (mut clip_node_id, mut seen_clips) = {
let prev = self.clip_stack.last().unwrap();
(prev.clip_node_id, prev.seen_clips.clone())
};
if let Some(clip_chain_id) = clip_chain_id {
if clip_chain_id != ClipChainId::INVALID {
self.clip_handles_buffer.clear();
let clip_chain_index = self.clip_chain_map[&clip_chain_id];
ClipTreeBuilder::add_clips(
clip_chain_index,
&mut seen_clips,
&mut self.clip_handles_buffer,
&self.clip_chains,
);
clip_node_id = self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
);
}
}
if reset_seen {
seen_clips.clear();
}
self.clip_stack.push(ClipStackEntry {
last_clip_chain_cache: None,
clip_node_id,
seen_clips,
});
}
/// Push a clip-id that will be applied to any prims built prior to next pop
pub fn push_clip_id(
&mut self,
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_index = self.clip_map[&clip_id];
if seen_clips.insert(clip_index) {
self.clip_handles_buffer.push(clip_index);
}
let clip_node_id = self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
);
self.clip_stack.push(ClipStackEntry {
last_clip_chain_cache: None,
seen_clips,
clip_node_id,
});
}
/// Pop a clip off the clip_stack, when exiting a grouping item
pub fn pop_clip(&mut self) {
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<ClipDataHandle>,
output: &mut Vec<ClipDataHandle>,
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.
let clip_chain = &clip_chains[clip_chain_index];
if let Some(parent) = clip_chain.parent {
ClipTreeBuilder::add_clips(
parent,
seen_clips,
output,
clip_chains,
);
}
for clip_index in clip_chain.clips.iter().rev() {
if seen_clips.insert(*clip_index) {
output.push(*clip_index);
}
}
}
/// Main entry point to build a path in the clip-tree for a given primitive
pub fn build_clip_set(
&mut self,
clip_chain_id: ClipChainId,
) -> ClipNodeId {
let clip_stack = self.clip_stack.last_mut().unwrap();
if clip_chain_id == ClipChainId::INVALID {
clip_stack.clip_node_id
} else {
if let Some((cached_clip_chain, cached_clip_node)) = clip_stack.last_clip_chain_cache {
if cached_clip_chain == clip_chain_id {
return cached_clip_node;
}
}
let clip_chain_index = self.clip_chain_map[&clip_chain_id];
self.clip_handles_buffer.clear();
ClipTreeBuilder::add_clips(
clip_chain_index,
&mut clip_stack.seen_clips,
&mut self.clip_handles_buffer,
&self.clip_chains,
);
// 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 handle in &self.clip_handles_buffer {
clip_stack.seen_clips.remove(handle);
}
let clip_node_id = self.tree.add(
clip_stack.clip_node_id,
&self.clip_handles_buffer,
);
clip_stack.last_clip_chain_cache = Some((clip_chain_id, clip_node_id));
clip_node_id
}
}
/// Recursive impl to check if a clip-chain has complex (non-rectangular) clips
fn has_complex_clips_impl(
&self,
clip_chain_index: usize,
interners: &Interners,
) -> bool {
let clip_chain = &self.clip_chains[clip_chain_index];
for clip_handle in &clip_chain.clips {
let clip_info = &interners.clip[*clip_handle];
if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
return true;
}
}
match clip_chain.parent {
Some(parent) => self.has_complex_clips_impl(parent, interners),
None => false,
}
}
/// Check if a clip-chain has complex (non-rectangular) clips
pub fn 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 a clip-node has complex (non-rectangular) clips
pub fn clip_node_has_complex_clips(
&self,
clip_node_id: ClipNodeId,
interners: &Interners,
) -> bool {
let mut current = clip_node_id;
while current != ClipNodeId::NONE {
let node = &self.tree.nodes[current.0 as usize];
let clip_info = &interners.clip[node.handle];
if let ClipNodeKind::Complex = clip_info.key.kind.node_kind() {
return true;
}
current = node.parent;
}
false
}
/// Finalize building and return the clip-tree
pub fn finalize(&mut self) -> 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(&mut self.tree, ClipTree {
nodes: Vec::new(),
leaves: Vec::new(),
clip_root_stack: Vec::new(),
})
}
/// Get a clip node by id
pub fn get_node(&self, id: ClipNodeId) -> &ClipTreeNode {
assert!(id != ClipNodeId::NONE);
&self.tree.nodes[id.0 as usize]
}
/// Get a clip leaf by id
pub fn get_leaf(&self, id: ClipLeafId) -> &ClipTreeLeaf {
&self.tree.leaves[id.0 as usize]
}
/// Build a clip-leaf for a tile-cache
pub fn build_for_tile_cache(
&mut self,
clip_node_id: ClipNodeId,
extra_clips: &[ClipId],
) -> ClipLeafId {
self.clip_handles_buffer.clear();
for clip_id in extra_clips {
let handle = self.clip_map[clip_id];
self.clip_handles_buffer.push(handle);
}
let node_id = self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
);
let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
self.tree.leaves.push(ClipTreeLeaf {
node_id,
local_clip_rect: LayoutRect::max_rect(),
});
clip_leaf_id
}
/// Build a clip-leaf for a picture
pub fn build_for_picture(
&mut self,
clip_node_id: ClipNodeId,
) -> ClipLeafId {
let node_id = self.tree.add(
clip_node_id,
&[],
);
let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
self.tree.leaves.push(ClipTreeLeaf {
node_id,
local_clip_rect: LayoutRect::max_rect(),
});
clip_leaf_id
}
/// Build a clip-leaf for a normal primitive
pub fn build_for_prim(
&mut self,
clip_node_id: ClipNodeId,
info: &LayoutPrimitiveInfo,
extra_clips: &[ClipItemKey],
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 item in extra_clips {
// Intern this clip item, and store the handle
// in the clip chain node.
let handle = interners.clip.intern(item, || {
ClipInternData {
key: item.clone(),
}
});
self.clip_handles_buffer.push(handle);
}
self.tree.add(
clip_node_id,
&self.clip_handles_buffer,
)
};
let clip_leaf_id = ClipLeafId(self.tree.leaves.len() as u32);
self.tree.leaves.push(ClipTreeLeaf {
node_id,
local_clip_rect: info.clip_rect,
});
clip_leaf_id
}
// Find the LCA for two given clip nodes
pub fn find_lowest_common_ancestor(
&self,
node1: ClipNodeId,
node2: ClipNodeId,
) -> ClipNodeId {
self.tree.find_lowest_common_ancestor(node1, node2)
}
}
// Type definitions for interning clip nodes.
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Eq, Hash)]
#[cfg_attr(any(feature = "serde"), derive(Deserialize, Serialize))]
pub enum ClipIntern {}
pub type ClipDataStore = intern::DataStore<ClipIntern>;
pub type ClipDataHandle = intern::Handle<ClipIntern>;
/// 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)]
pub enum 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)]
pub struct 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(rect, mode) => {
ClipItemKind::Rectangle { rect: rect.into(), mode }
}
ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
ClipItemKind::RoundedRectangle {
rect: rect.into(),
radius: radius.into(),
mode,
}
}
ClipItemKeyKind::ImageMask(rect, image, polygon_handle) => {
ClipItemKind::Image {
image,
rect: rect.into(),
polygon_handle,
}
}
ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
ClipItemKind::new_box_shadow(
shadow_rect_fract_offset.into(),
shadow_rect_size.into(),
shadow_radius.into(),
prim_shadow_rect.into(),
blur_radius.to_f32_px(),
clip_mode,
)
}
};
ClipNode {
item: ClipItem {
kind,
spatial_node_index: item.spatial_node_index,
},
}
}
}
// 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)]
pub struct ClipNodeFlags(u8);
bitflags! {
impl ClipNodeFlags : u8 {
const SAME_SPATIAL_NODE = 0x1;
const SAME_COORD_SYSTEM = 0x2;
const USE_FAST_PATH = 0x4;
}
}
impl core::fmt::Debug for ClipNodeFlags {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.is_empty() {
write!(f, "{:#x}", Self::empty().bits())
} else {
bitflags::parser::to_writer(self, f)
}
}
}
// 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))]
pub struct ClipNodeInstance {
pub handle: ClipDataHandle,
pub flags: ClipNodeFlags,
pub visible_tiles: Option<ops::Range<usize>>,
}
impl ClipNodeInstance {
pub fn has_visible_tiles(&self) -> bool {
self.visible_tiles.is_some()
}
}
// 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))]
pub struct ClipNodeRange {
pub first: u32,
pub count: u32,
}
impl ClipNodeRange {
pub fn 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.
// 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))]
pub enum ClipSpaceConversion {
Local,
ScaleOffset(ScaleOffset),
Transform(LayoutToWorldTransform),
}
impl ClipSpaceConversion {
/// Construct a new clip space converter between two spatial nodes.
pub fn new(
prim_spatial_node_index: SpatialNodeIndex,
clip_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);
if prim_spatial_node_index == clip_spatial_node_index {
ClipSpaceConversion::Local
} else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
let scale_offset = clip_spatial_node.content_transform
.then(&prim_spatial_node.content_transform.inverse());
ClipSpaceConversion::ScaleOffset(scale_offset)
} else {
ClipSpaceConversion::Transform(
spatial_tree
.get_world_transform(clip_spatial_node_index)
.into_transform()
)
}
}
fn to_flags(&self) -> ClipNodeFlags {
match *self {
ClipSpaceConversion::Local => {
ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
}
ClipSpaceConversion::ScaleOffset(..) => {
ClipNodeFlags::SAME_COORD_SYSTEM
}
ClipSpaceConversion::Transform(..) => {
ClipNodeFlags::empty()
}
}
}
}
// 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,
}
impl ClipNodeInfo {
fn create_instance(
&self,
node: &ClipNode,
clipped_rect: &LayoutRect,
gpu_cache: &mut GpuCache,
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.
let mut 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(node.item.spatial_node_index)
.is_2d_axis_aligned();
if is_raster_2d && node.item.kind.supports_fast_path_rendering() {
flags |= ClipNodeFlags::USE_FAST_PATH;
}
let mut visible_tiles = None;
if let ClipItemKind::Image { rect, image, .. } = node.item.kind {
let request = ImageRequest {
key: image,
rendering: ImageRendering::Auto,
tile: None,
};
if let Some(props) = resource_cache.get_image_properties(image) {
if let Some(tile_size) = props.tiling {
let tile_range_start = mask_tiles.len();
// 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_cache,
);
}
let task_id = rg_builder.add().init(
RenderTask::new_image(props.descriptor.size, req)
);
mask_tiles.push(VisibleMaskImageTile {
tile_offset: tile.offset,
tile_rect: tile.rect,
task_id,
});
}
}
visible_tiles = Some(tile_range_start..mask_tiles.len());
} else {
if request_resources {
resource_cache.request_image(request, gpu_cache);
}
let tile_range_start = mask_tiles.len();
let task_id = rg_builder.add().init(
RenderTask::new_image(props.descriptor.size, request)
);
mask_tiles.push(VisibleMaskImageTile {
tile_rect: rect,
tile_offset: TileOffset::zero(),
task_id,
});
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;
}
}
Some(ClipNodeInstance {
handle: self.handle,
flags,
visible_tiles,
})
}
}
impl ClipNode {
pub fn update(
&mut self,
device_pixel_scale: DevicePixelScale,
) {
match self.item.kind {
ClipItemKind::Image { .. } |
ClipItemKind::Rectangle { .. } |
ClipItemKind::RoundedRectangle { .. } => {}
ClipItemKind::BoxShadow { ref mut source } => {
// Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
// "the image that would be generated by applying to the shadow a
// Gaussian blur with a standard deviation equal to half the blur radius."
let blur_radius_dp = source.blur_radius * 0.5;
// Create scaling from requested size to cache size.
let mut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
content_scale.0 = clamp_to_scale_factor(content_scale.0, false);
// Create the cache key for this box-shadow render task.
let cache_size = to_cache_size(source.shadow_rect_alloc_size, &mut content_scale);
let bs_cache_key = BoxShadowCacheKey {
blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
clip_mode: source.clip_mode,
original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(),
br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(),
br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(),
br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(),
br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(),
device_pixel_scale: Au::from_f32_px(content_scale.0),
};
source.cache_key = Some((cache_size, bs_cache_key));
}
}
}
}
#[derive(Default)]
pub struct ClipStoreScratchBuffer {
clip_node_instances: Vec<ClipNodeInstance>,
mask_tiles: Vec<VisibleMaskImageTile>,
}
/// The main clipping public interface that other modules access.
#[derive(MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClipStore {
pub clip_node_instances: Vec<ClipNodeInstance>,
mask_tiles: Vec<VisibleMaskImageTile>,
active_clip_node_info: Vec<ClipNodeInfo>,
active_local_clip_rect: Option<LayoutRect>,
active_pic_coverage_rect: PictureRect,
}
// A clip chain instance is what gets built for a given clip
// chain id + local primitive region + positioning node.
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct 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,
}
impl ClipChainInstance {
pub fn empty() -> Self {
ClipChainInstance {
clips_range: ClipNodeRange {
first: 0,
count: 0,
},
local_clip_rect: LayoutRect::zero(),
has_non_local_clips: false,
needs_mask: false,
pic_coverage_rect: PictureRect::zero(),
pic_spatial_node_index: SpatialNodeIndex::INVALID,
}
}
}
impl ClipStore {
pub fn new() -> Self {
ClipStore {
clip_node_instances: Vec::new(),
mask_tiles: Vec::new(),
active_clip_node_info: Vec::new(),
active_local_clip_rect: None,
active_pic_coverage_rect: PictureRect::max_rect(),
}
}
pub fn reset(&mut self) {
self.clip_node_instances.clear();
self.mask_tiles.clear();
self.active_clip_node_info.clear();
self.active_local_clip_rect = None;
self.active_pic_coverage_rect = PictureRect::max_rect();
}
pub fn get_instance_from_range(
&self,
node_range: &ClipNodeRange,
index: u32,
) -> &ClipNodeInstance {
&self.clip_node_instances[(node_range.first + index) as usize]
}
/// Setup the active clip chains for building a clip chain instance.
pub fn set_active_clips(
&mut self,
prim_spatial_node_index: SpatialNodeIndex,
pic_spatial_node_index: SpatialNodeIndex,
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);
let mut local_clip_rect = clip_leaf.local_clip_rect;
let mut current = clip_leaf.node_id;
while current != clip_root {
let node = clip_tree.get_node(current);
if !add_clip_node_to_current_chain(
node.handle,
prim_spatial_node_index,
pic_spatial_node_index,
&mut local_clip_rect,
&mut self.active_clip_node_info,
&mut self.active_pic_coverage_rect,
clip_data_store,
spatial_tree,
) {
return;
}
current = node.parent;
}
self.active_local_clip_rect = Some(local_clip_rect);
}
/// Setup the active clip chains, based on an existing primitive clip chain instance.
pub fn set_active_clips_from_clip_chain(
&mut self,
prim_clip_chain: &ClipChainInstance,
prim_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
clip_data_store: &ClipDataStore,
) {
// 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.
self.active_clip_node_info.clear();
self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
self.active_pic_coverage_rect = prim_clip_chain.pic_coverage_rect;
let clip_instances = &self
.clip_node_instances[prim_clip_chain.clips_range.to_range()];
for clip_instance in clip_instances {
let clip = &clip_data_store[clip_instance.handle];
let conversion = ClipSpaceConversion::new(
prim_spatial_node_index,
clip.item.spatial_node_index,
spatial_tree,
);
self.active_clip_node_info.push(ClipNodeInfo {
handle: clip_instance.handle,
conversion,
});
}
}
/// 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.
pub fn get_inner_rect_for_clip_chain(
&self,
clip_chain: &ClipChainInstance,
clip_data_store: &ClipDataStore,
spatial_tree: &SpatialTree,
) -> Option<PictureRect> {
let mut 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::BoxShadow { .. } |
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, rect, radius } => {
// Get an inner rect for the rounded-rect clip
let local_inner_rect = match extract_inner_rect_safe(&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_node.item.spatial_node_index,
PictureRect::max_rect(),
spatial_tree,
);
// Accumulate in to the inner_rect, in case there are multiple rounded-rect clips
if let 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.
pub fn push_clip_instance(
&mut self,
handle: ClipDataHandle,
) -> ClipNodeRange {
let first = self.clip_node_instances.len() as u32;
self.clip_node_instances.push(ClipNodeInstance {
handle,
flags: ClipNodeFlags::SAME_COORD_SYSTEM | ClipNodeFlags::SAME_SPATIAL_NODE,
visible_tiles: None,
});
ClipNodeRange {
first,
count: 1,
}
}
/// The main interface external code uses. Given a local primitive, positioning
/// information, and a clip chain id, build an optimized clip chain instance.
pub fn build_clip_chain_instance(
&mut self,
local_prim_rect: LayoutRect,
prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
spatial_tree: &SpatialTree,
gpu_cache: &mut GpuCache,
resource_cache: &mut ResourceCache,
device_pixel_scale: DevicePixelScale,
world_rect: &WorldRect,
clip_data_store: &mut ClipDataStore,
rg_builder: &mut RenderTaskGraphBuilder,
request_resources: bool,
) -> Option<ClipChainInstance> {
let local_clip_rect = match self.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)?;
let mut pic_coverage_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
let world_clip_rect = pic_to_world_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;
let mut has_non_local_clips = false;
let mut needs_mask = false;
// For each potential clip node
for node_info in self.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)
}
ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
has_non_local_clips = true;
node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
}
ClipSpaceConversion::Transform(ref transform) => {
has_non_local_clips = true;
node.item.kind.get_clip_result_complex(
transform,
&world_clip_rect,
world_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
// TODO(gw): Ensure this only runs once on each node per frame?
node.update(device_pixel_scale);
// Create the clip node instance for this clip node
if let Some(instance) = node_info.create_instance(
node,
&local_bounding_rect,
gpu_cache,
resource_cache,
&mut self.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 { .. } |
ClipItemKind::BoxShadow { .. } => {
true
}
ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {
!instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
}
};
// 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)?;
}
// Return a valid clip chain instance
Some(ClipChainInstance {
clips_range,
has_non_local_clips,
local_clip_rect,
pic_coverage_rect,
pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index,
needs_mask,
})
}
pub fn begin_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
self.clip_node_instances.clear();
self.mask_tiles.clear();
}
pub fn end_frame(&mut self, scratch: &mut ClipStoreScratchBuffer) {
mem::swap(&mut self.clip_node_instances, &mut scratch.clip_node_instances);
mem::swap(&mut self.mask_tiles, &mut scratch.mask_tiles);
}
pub fn visible_mask_tiles(&self, instance: &ClipNodeInstance) -> &[VisibleMaskImageTile] {
if let Some(range) = &instance.visible_tiles {
&self.mask_tiles[range.clone()]
} else {
&[]
}
}
}
impl Default for ClipStore {
fn default() -> Self {
ClipStore::new()
}
}
// The ClipItemKey is a hashable representation of the contents
// 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.
// 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))]
pub enum ClipItemKeyKind {
Rectangle(RectangleKey, ClipMode),
RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode),
ImageMask(RectangleKey, ImageKey, Option<PolygonDataHandle>),
BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
}
impl ClipItemKeyKind {
pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
ClipItemKeyKind::Rectangle(rect.into(), mode)
}
pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
if radii.is_zero() {
ClipItemKeyKind::rectangle(rect, mode)
} else {
ensure_no_corner_overlap(&mut radii, rect.size());
ClipItemKeyKind::RoundedRectangle(
rect.into(),
radii.into(),
mode,
)
}
}
pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect,
polygon_handle: Option<PolygonDataHandle>) -> Self {
ClipItemKeyKind::ImageMask(
mask_rect.into(),
image_mask.image,
polygon_handle,
)
}
pub fn box_shadow(
shadow_rect: LayoutRect,
shadow_radius: BorderRadius,
prim_shadow_rect: LayoutRect,
blur_radius: f32,
clip_mode: BoxShadowClipMode,
) -> Self {
// Get the fractional offsets required to match the
// source rect with a minimal rect.
let fract_offset = LayoutPoint::new(
shadow_rect.min.x.fract().abs(),
shadow_rect.min.y.fract().abs(),
);
ClipItemKeyKind::BoxShadow(
fract_offset.into(),
shadow_rect.size().into(),
shadow_radius.into(),
prim_shadow_rect.into(),
Au::from_f32_px(blur_radius),
clip_mode,
)
}
pub fn node_kind(&self) -> ClipNodeKind {
match *self {
ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle,
ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
ClipItemKeyKind::RoundedRectangle(..) |
ClipItemKeyKind::ImageMask(..) |
ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex,
}
}
}
#[derive(Debug, Copy, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipItemKey {
pub kind: ClipItemKeyKind,
pub spatial_node_index: SpatialNodeIndex,
}
/// 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))]
pub struct 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;
}
#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum ClipItemKind {
Rectangle {
rect: LayoutRect,
mode: ClipMode,
},
RoundedRectangle {
rect: LayoutRect,
radius: BorderRadius,
mode: ClipMode,
},
Image {
image: ImageKey,
rect: LayoutRect,
polygon_handle: Option<PolygonDataHandle>,
},
BoxShadow {
source: BoxShadowClipSource,
},
}
#[derive(Debug, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipItem {
pub kind: ClipItemKind,
pub spatial_node_index: SpatialNodeIndex,
}
fn compute_box_shadow_parameters(
shadow_rect_fract_offset: LayoutPoint,
shadow_rect_size: LayoutSize,
mut shadow_radius: BorderRadius,
prim_shadow_rect: LayoutRect,
blur_radius: f32,
clip_mode: BoxShadowClipMode,
) -> BoxShadowClipSource {
// Make sure corners don't overlap.
ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
let fract_size = LayoutSize::new(
shadow_rect_size.width.fract().abs(),
shadow_rect_size.height.fract().abs(),
);
// Create a minimal size primitive mask to blur. In this
// case, we ensure the size of each corner is the same,
// to simplify the shader logic that stretches the blurred
// result across the primitive.
let max_corner_width = shadow_radius.top_left.width
.max(shadow_radius.bottom_left.width)
.max(shadow_radius.top_right.width)
.max(shadow_radius.bottom_right.width);
let max_corner_height = shadow_radius.top_left.height
.max(shadow_radius.bottom_left.height)
.max(shadow_radius.top_right.height)
.max(shadow_radius.bottom_right.height);
// Get maximum distance that can be affected by given blur radius.
let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
// If the largest corner is smaller than the blur radius, we need to ensure
// that it's big enough that the corners don't affect the middle segments.
let used_corner_width = max_corner_width.max(blur_region);
let used_corner_height = max_corner_height.max(blur_region);
// Minimal nine-patch size, corner + internal + corner.
let min_shadow_rect_size = LayoutSize::new(
2.0 * used_corner_width + blur_region,
2.0 * used_corner_height + blur_region,
);
// The minimal rect to blur.
let mut minimal_shadow_rect = LayoutRect::from_origin_and_size(
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet)
]
|
2026-04-06
|