|
|
|
|
Quelle scene_building.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/. */
//! # Scene building
//!
//! Scene building is the phase during which display lists, a representation built for
//! serialization, are turned into a scene, webrender's internal representation that is
//! suited for rendering frames.
//!
//! This phase is happening asynchronously on the scene builder thread.
//!
//! # General algorithm
//!
//! The important aspects of scene building are:
//! - Building up primitive lists (much of the cost of scene building goes here).
//! - Creating pictures for content that needs to be rendered into a surface, be it so that
//! filters can be applied or for caching purposes.
//! - Maintaining a temporary stack of stacking contexts to keep track of some of the
//! drawing states.
//! - Stitching multiple display lists which reference each other (without cycles) into
//! a single scene (see build_reference_frame).
//! - Interning, which detects when some of the retained state stays the same between display
//! lists.
//!
//! The scene builder linearly traverses the serialized display list which is naturally
//! ordered back-to-front, accumulating primitives in the top-most stacking context's
//! primitive list.
//! At the end of each stacking context (see pop_stacking_context), its primitive list is
//! either handed over to a picture if one is created, or it is concatenated into the parent
//! stacking context's primitive list.
//!
//! The flow of the algorithm is mostly linear except when handling:
//! - shadow stacks (see push_shadow and pop_all_shadows),
//! - backdrop filters (see add_backdrop_filter)
//!
use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, BuiltDisplay ListIter, PrimitiveFlags, SnapshotInfo};
use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
use api::{DebugFlags, DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData};
use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor};
use api::{APZScrollGeneration, HasScrollLinkedEffect, Shadow, SpatialId, StickyFrameDescriptor, ImageMask, ItemTag};
use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor};
use api::{FilterOpGraphPictureBufferId, SVGFE_GRAPH_MAX};
use api::channel::{unbounded_channel, Receiver, Sender};
use api::units::*;
use crate::image_tiling::simplify_repeated_primitive;
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipIntern, ClipItemKey, ClipItemKeyKind, ClipStore};
use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId};
use crate::clip::{PolygonDataHandle, ClipTreeBuilder};
use crate::segment::EdgeAaSegmentMask;
use crate::spatial_tree::{SceneSpatialTree, SpatialNodeContainer, SpatialNodeIndex, get_external_scroll_offset};
use crate::frame_builder::FrameBuilderConfig;
use glyph_rasterizer::{FontInstance, SharedFontResources};
use crate::hit_test::HitTestingScene;
use crate::intern::Interner;
use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, PlaneSplitterIndex, PipelineInstanceId};
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags};
use crate::picture_graph::PictureGraph;
use crate::prim_store::{PrimitiveInstance, PrimitiveStoreStats};
use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
use crate::prim_store::{InternablePrimitive, PictureIndex};
use crate::prim_store::PolygonKey;
use crate::prim_store::backdrop::{BackdropCapture, BackdropRender};
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
use crate::prim_store::gradient::{
GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient,
ConicGradientParams, optimize_radial_gradient, apply_gradient_local_clip,
optimize_linear_gradient, self,
};
use crate::prim_store::image::{Image, YuvImage};
use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size};
use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
use crate::prim_store::text_run::TextRun;
use crate::render_backend::SceneView;
use crate::resource_cache::ImageRequest;
use crate::scene::{BuiltScene, Scene, ScenePipeline, SceneStats, StackingContextHelpers};
use crate::scene_builder_thread::Interners;
use crate::space::SpaceSnapper;
use crate::spatial_node::{
ReferenceFrameInfo, StickyFrameInfo, ScrollFrameKind, SpatialNodeUid, SpatialNodeType
};
use crate::tile_cache::TileCacheBuilder;
use euclid::approxeq::ApproxEq;
use std::{f32, mem, usize};
use std::collections::vec_deque::VecDeque;
use std::sync::Arc;
use crate::util::{VecHelper, MaxRect};
use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
use log::Level;
/// Offsets primitives (and clips) by the external scroll offset
/// supplied to scroll nodes.
pub struct ScrollOffsetMapper {
pub current_spatial_node: SpatialNodeIndex,
pub current_offset: LayoutVector2D,
}
impl ScrollOffsetMapper {
fn new() -> Self {
ScrollOffsetMapper {
current_spatial_node: SpatialNodeIndex::INVALID,
current_offset: LayoutVector2D::zero(),
}
}
/// Return the accumulated external scroll offset for a spatial
/// node. This caches the last result, which is the common case,
/// or defers to the spatial tree to build the value.
fn external_scroll_offset(
&mut self,
spatial_node_index: SpatialNodeIndex,
spatial_tree: &SceneSpatialTree,
) -> LayoutVector2D {
if spatial_node_index != self.current_spatial_node {
self.current_spatial_node = spatial_node_index;
self.current_offset = get_external_scroll_offset(spatial_tree, spatial_node_index);
}
self.current_offset
}
}
/// A data structure that keeps track of mapping between API Ids for spatials and the indices
/// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives
/// and clips during frame building.
#[derive(Default)]
pub struct NodeIdToIndexMapper {
spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
}
impl NodeIdToIndexMapper {
fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
let _old_value = self.spatial_node_map.insert(id, index);
assert!(_old_value.is_none());
}
fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
self.spatial_node_map[&id]
}
}
#[derive(Debug, Clone, Default)]
pub struct CompositeOps {
// Requires only a single texture as input (e.g. most filters)
pub filters: Vec<Filter>,
pub filter_datas: Vec<FilterData>,
pub filter_primitives: Vec<FilterPrimitive>,
pub snapshot: Option<SnapshotInfo>,
// Requires two source textures (e.g. mix-blend-mode)
pub mix_blend_mode: Option<MixBlendMode>,
}
impl CompositeOps {
pub fn new(
filters: Vec<Filter>,
filter_datas: Vec<FilterData>,
filter_primitives: Vec<FilterPrimitive>,
mix_blend_mode: Option<MixBlendMode>,
snapshot: Option<SnapshotInfo>,
) -> Self {
CompositeOps {
filters,
filter_datas,
filter_primitives,
mix_blend_mode,
snapshot,
}
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty() &&
self.filter_primitives.is_empty() &&
self.mix_blend_mode.is_none() &&
self.snapshot.is_none()
}
/// Returns true if this CompositeOps contains any filters that affect
/// the content (false if no filters, or filters are all no-ops).
fn has_valid_filters(&self) -> bool {
// For each filter, create a new image with that composite mode.
let mut current_filter_data_index = 0;
for filter in &self.filters {
match filter {
Filter::ComponentTransfer => {
let filter_data =
&self.filter_datas[current_filter_data_index];
let filter_data = filter_data.sanitize();
current_filter_data_index = current_filter_data_index + 1;
if filter_data.is_identity() {
continue
} else {
return true;
}
}
Filter::SVGGraphNode(..) => {return true;}
_ => {
if filter.is_noop() {
continue;
} else {
return true;
}
}
}
}
if !self.filter_primitives.is_empty() {
return true;
}
false
}
}
/// Represents the current input for a picture chain builder (either a
/// prim list from the stacking context, or a wrapped picture instance).
enum PictureSource {
PrimitiveList {
prim_list: PrimitiveList,
},
WrappedPicture {
instance: PrimitiveInstance,
},
}
/// Helper struct to build picture chains during scene building from
/// a flattened stacking context struct.
struct PictureChainBuilder {
/// The current input source for the next picture
current: PictureSource,
/// Positioning node for this picture chain
spatial_node_index: SpatialNodeIndex,
/// Prim flags for any pictures in this chain
flags: PrimitiveFlags,
/// Requested raster space for enclosing stacking context
raster_space: RasterSpace,
/// If true, set first picture as a resolve target
set_resolve_target: bool,
/// If true, mark the last picture as a sub-graph
establishes_sub_graph: bool,
}
impl PictureChainBuilder {
/// Create a new picture chain builder, from a primitive list
fn from_prim_list(
prim_list: PrimitiveList,
flags: PrimitiveFlags,
spatial_node_index: SpatialNodeIndex,
raster_space: RasterSpace,
is_sub_graph: bool,
) -> Self {
PictureChainBuilder {
current: PictureSource::PrimitiveList {
prim_list,
},
spatial_node_index,
flags,
raster_space,
establishes_sub_graph: is_sub_graph,
set_resolve_target: is_sub_graph,
}
}
/// Create a new picture chain builder, from a picture wrapper instance
fn from_instance(
instance: PrimitiveInstance,
flags: PrimitiveFlags,
spatial_node_index: SpatialNodeIndex,
raster_space: RasterSpace,
) -> Self {
PictureChainBuilder {
current: PictureSource::WrappedPicture {
instance,
},
flags,
spatial_node_index,
raster_space,
establishes_sub_graph: false,
set_resolve_target: false,
}
}
/// Wrap the existing content with a new picture with the given parameters
#[must_use]
fn add_picture(
self,
composite_mode: PictureCompositeMode,
clip_node_id: ClipNodeId,
context_3d: Picture3DContext<OrderedPictureChild>,
interners: &mut Interners,
prim_store: &mut PrimitiveStore,
prim_instances: &mut Vec<PrimitiveInstance>,
clip_tree_builder: &mut ClipTreeBuilder,
) -> PictureChainBuilder {
let prim_list = match self.current {
PictureSource::PrimitiveList { prim_list } => {
prim_list
}
PictureSource::WrappedPicture { instance } => {
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
instance,
LayoutRect::zero(),
self.spatial_node_index,
self.flags,
prim_instances,
clip_tree_builder,
);
prim_list
}
};
let flags = if self.set_resolve_target {
PictureFlags::IS_RESOLVE_TARGET
} else {
PictureFlags::empty()
};
let pic_index = PictureIndex(prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
Some(composite_mode.clone()),
context_3d,
self.flags,
prim_list,
self.spatial_node_index,
self.raster_space,
flags,
None,
))
);
let instance = create_prim_instance(
pic_index,
Some(composite_mode).into(),
self.raster_space,
clip_node_id,
interners,
clip_tree_builder,
);
PictureChainBuilder {
current: PictureSource::WrappedPicture {
instance,
},
spatial_node_index: self.spatial_node_index,
flags: self.flags,
raster_space: self.raster_space,
// We are now on a subsequent picture, so set_resolve_target has been handled
set_resolve_target: false,
establishes_sub_graph: self.establishes_sub_graph,
}
}
/// Finish building this picture chain. Set the clip chain on the outermost picture
fn finalize(
self,
clip_node_id: ClipNodeId,
interners: &mut Interners,
prim_store: &mut PrimitiveStore,
clip_tree_builder: &mut ClipTreeBuilder,
snapshot: Option<SnapshotInfo>,
) -> PrimitiveInstance {
let mut flags = PictureFlags::empty();
if self.establishes_sub_graph {
flags |= PictureFlags::IS_SUB_GRAPH;
}
match self.current {
PictureSource::WrappedPicture { instance } => {
let pic_index = instance.kind.as_pic();
let picture = &mut prim_store.pictures[pic_index.0];
picture.flags |= flags;
picture.snapshot = snapshot;
instance
}
PictureSource::PrimitiveList { prim_list } => {
if self.set_resolve_target {
flags |= PictureFlags::IS_RESOLVE_TARGET;
}
// If no picture was created for this stacking context, create a
// pass-through wrapper now. This is only needed in 1-2 edge cases
// now, and will be removed as a follow up.
let pic_index = PictureIndex(prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
None,
Picture3DContext::Out,
self.flags,
prim_list,
self.spatial_node_index,
self.raster_space,
flags,
snapshot,
))
);
create_prim_instance(
pic_index,
None.into(),
self.raster_space,
clip_node_id,
interners,
clip_tree_builder,
)
}
}
}
/// Returns true if this builder wraps a picture
#[allow(dead_code)]
fn has_picture(&self) -> bool {
match self.current {
PictureSource::WrappedPicture { .. } => true,
PictureSource::PrimitiveList { .. } => false,
}
}
}
bitflags! {
/// Slice flags
#[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct SliceFlags : u8 {
/// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER
const IS_SCROLLBAR = 1;
/// Represents an atomic container (can't split out compositor surfaces in this slice)
const IS_ATOMIC = 2;
}
}
/// A structure that converts a serialized display list into a form that WebRender
/// can use to later build a frame. This structure produces a BuiltScene. Public
/// members are typically those that are destructured into the BuiltScene.
pub struct SceneBuilder<'a> {
/// The scene that we are currently building.
scene: &'a Scene,
/// The map of all font instances.
fonts: SharedFontResources,
/// The data structure that converts between ClipId/SpatialId and the various
/// index types that the SpatialTree uses.
id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>,
/// A stack of stacking context properties.
sc_stack: Vec<FlattenedStackingContext>,
/// Stack of spatial node indices forming containing block for 3d contexts
containing_block_stack: Vec<SpatialNodeIndex>,
/// Stack of requested raster spaces for stacking contexts
raster_space_stack: Vec<RasterSpace>,
/// Maintains state for any currently active shadows
pending_shadow_items: VecDeque<ShadowItem>,
/// The SpatialTree that we are currently building during building.
pub spatial_tree: &'a mut SceneSpatialTree,
/// The store of primitives.
pub prim_store: PrimitiveStore,
/// Information about all primitives involved in hit testing.
pub hit_testing_scene: HitTestingScene,
/// The store which holds all complex clipping information.
pub clip_store: ClipStore,
/// The configuration to use for the FrameBuilder. We consult this in
/// order to determine the default font.
pub config: FrameBuilderConfig,
/// Reference to the set of data that is interned across display lists.
pub interners: &'a mut Interners,
/// Helper struct to map spatial nodes to external scroll offsets.
external_scroll_mapper: ScrollOffsetMapper,
/// The current recursion depth of iframes encountered. Used to restrict picture
/// caching slices to only the top-level content frame.
iframe_size: Vec<LayoutSize>,
/// Clip-chain for root iframes applied to any tile caches created within this iframe
root_iframe_clip: Option<ClipId>,
/// The current quality / performance settings for this scene.
quality_settings: QualitySettings,
/// Maintains state about the list of tile caches being built for this scene.
tile_cache_builder: TileCacheBuilder,
/// A helper struct to snap local rects in device space. During frame
/// building we may establish new raster roots, however typically that is in
/// cases where we won't be applying snapping (e.g. has perspective), or in
/// edge cases (e.g. SVG filter) where we can accept slightly incorrect
/// behaviour in favour of getting the common case right.
snap_to_device: SpaceSnapper,
/// A DAG that represents dependencies between picture primitives. This builds
/// a set of passes to run various picture processing passes in during frame
/// building, in a way that pictures are processed before (or after) their
/// dependencies, without relying on recursion for those passes.
picture_graph: PictureGraph,
/// Keep track of snapshot pictures to ensure that they are rendered even if they
/// are off-screen and the visibility traversal does not reach them.
snapshot_pictures: Vec<PictureIndex>,
/// Keep track of allocated plane splitters for this scene. A plane
/// splitter is allocated whenever we encounter a new 3d rendering context.
/// They are stored outside the picture since it makes it easier for them
/// to be referenced by both the owning 3d rendering context and the child
/// pictures that contribute to the splitter.
/// During scene building "allocating" a splitter is just incrementing an index.
/// Splitter objects themselves are allocated and recycled in the frame builder.
next_plane_splitter_index: usize,
/// A list of all primitive instances in the scene. We store them as a single
/// array so that multiple different systems (e.g. tile-cache, visibility, property
/// animation bindings) can store index buffers to prim instances.
prim_instances: Vec<PrimitiveInstance>,
/// A map of pipeline ids encountered during scene build - used to create unique
/// pipeline instance ids as they are encountered.
pipeline_instance_ids: FastHashMap<PipelineId, u32>,
/// A list of surfaces (backing textures) that are relevant for this scene.
/// Every picture is assigned to a surface (either a new surface if the picture
/// has a composite mode, or the parent surface if it's a pass-through).
surfaces: Vec<SurfaceInfo>,
/// Used to build a ClipTree from the clip-chains, clips and state during scene building.
clip_tree_builder: ClipTreeBuilder,
/// Some primitives need to nest two stacking contexts instead of one
/// (see push_stacking_context). We keep track of the extra stacking context info
/// here and set a boolean on the inner stacking context info to remember to
/// pop from this stack (see StackingContextInfo::needs_extra_stacking_context)
extra_stacking_context_stack: Vec<StackingContextInfo>,
}
impl<'a> SceneBuilder<'a> {
pub fn build(
scene: &Scene,
fonts: SharedFontResources,
view: &SceneView,
frame_builder_config: &FrameBuilderConfig,
interners: &mut Interners,
spatial_tree: &mut SceneSpatialTree,
recycler: &mut SceneRecycler,
stats: &SceneStats,
debug_flags: DebugFlags,
) -> BuiltScene {
profile_scope!("build_scene");
// We checked that the root pipeline is available on the render backend.
let root_pipeline_id = scene.root_pipeline_id.unwrap();
let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
let root_reference_frame_index = spatial_tree.root_reference_frame_index();
// During scene building, we assume a 1:1 picture -> raster pixel scale
let snap_to_device = SpaceSnapper::new(
root_reference_frame_index,
RasterPixelScale::new(1.0),
);
let mut builder = SceneBuilder {
scene,
spatial_tree,
fonts,
config: *frame_builder_config,
id_to_index_mapper_stack: mem::take(&mut recycler.id_to_index_mapper_stack),
hit_testing_scene: recycler.hit_testing_scene.take().unwrap_or_else(|| HitTestingScene::new(&stats.hit_test_stats)),
pending_shadow_items: mem::take(&mut recycler.pending_shadow_items),
sc_stack: mem::take(&mut recycler.sc_stack),
containing_block_stack: mem::take(&mut recycler.containing_block_stack),
raster_space_stack: mem::take(&mut recycler.raster_space_stack),
prim_store: mem::take(&mut recycler.prim_store),
clip_store: mem::take(&mut recycler.clip_store),
interners,
external_scroll_mapper: ScrollOffsetMapper::new(),
iframe_size: mem::take(&mut recycler.iframe_size),
root_iframe_clip: None,
quality_settings: view.quality_settings,
tile_cache_builder: TileCacheBuilder::new(
root_reference_frame_index,
frame_builder_config.background_color,
debug_flags,
),
snap_to_device,
picture_graph: mem::take(&mut recycler.picture_graph),
// This vector is empty most of the time, don't bother with recycling it for now.
snapshot_pictures: Vec::new(),
next_plane_splitter_index: 0,
prim_instances: mem::take(&mut recycler.prim_instances),
pipeline_instance_ids: FastHashMap::default(),
surfaces: mem::take(&mut recycler.surfaces),
clip_tree_builder: recycler.clip_tree_builder.take().unwrap_or_else(|| ClipTreeBuilder::new()),
extra_stacking_context_stack: Vec::new(),
};
// Reset
builder.hit_testing_scene.reset();
builder.prim_store.reset();
builder.clip_store.reset();
builder.picture_graph.reset();
builder.prim_instances.clear();
builder.surfaces.clear();
builder.sc_stack.clear();
builder.containing_block_stack.clear();
builder.id_to_index_mapper_stack.clear();
builder.pending_shadow_items.clear();
builder.iframe_size.clear();
builder.raster_space_stack.clear();
builder.raster_space_stack.push(RasterSpace::Screen);
builder.clip_tree_builder.begin();
builder.build_all(
root_pipeline_id,
&root_pipeline,
);
// Construct the picture cache primitive instance(s) from the tile cache builder
let (tile_cache_config, tile_cache_pictures) = builder.tile_cache_builder.build(
&builder.config,
&mut builder.prim_store,
&builder.spatial_tree,
&builder.prim_instances,
&mut builder.clip_tree_builder,
);
// Add all the tile cache pictures as roots of the picture graph
for pic_index in &tile_cache_pictures {
builder.picture_graph.add_root(*pic_index);
SceneBuilder::finalize_picture(
*pic_index,
None,
&mut builder.prim_store.pictures,
None,
&builder.clip_tree_builder,
&builder.prim_instances,
&builder.interners.clip,
);
}
let clip_tree = builder.clip_tree_builder.finalize();
recycler.clip_tree_builder = Some(builder.clip_tree_builder);
recycler.sc_stack = builder.sc_stack;
recycler.id_to_index_mapper_stack = builder.id_to_index_mapper_stack;
recycler.containing_block_stack = builder.containing_block_stack;
recycler.raster_space_stack = builder.raster_space_stack;
recycler.pending_shadow_items = builder.pending_shadow_items;
recycler.iframe_size = builder.iframe_size;
BuiltScene {
has_root_pipeline: scene.has_root_pipeline(),
pipeline_epochs: scene.pipeline_epochs.clone(),
output_rect: view.device_rect.size().into(),
hit_testing_scene: Arc::new(builder.hit_testing_scene),
prim_store: builder.prim_store,
clip_store: builder.clip_store,
config: builder.config,
tile_cache_config,
snapshot_pictures: builder.snapshot_pictures,
tile_cache_pictures,
picture_graph: builder.picture_graph,
num_plane_splitters: builder.next_plane_splitter_index,
prim_instances: builder.prim_instances,
surfaces: builder.surfaces,
clip_tree,
recycler_tx: Some(recycler.tx.clone()),
}
}
/// Traverse the picture prim list and update any late-set spatial nodes.
/// Also, for each picture primitive, store the lowest-common-ancestor
/// of all of the contained primitives' clips.
// TODO(gw): This is somewhat hacky - it's unfortunate we need to do this, but it's
// because we can't determine the scroll root until we have checked all the
// primitives in the slice. Perhaps we could simplify this by doing some
// work earlier in the DL builder, so we know what scroll root will be picked?
fn finalize_picture(
pic_index: PictureIndex,
prim_index: Option<usize>,
pictures: &mut [PicturePrimitive],
parent_spatial_node_index: Option<SpatialNodeIndex>,
clip_tree_builder: &ClipTreeBuilder,
prim_instances: &[PrimitiveInstance],
clip_interner: &Interner<ClipIntern>,
) {
// Extract the prim_list (borrow check) and select the spatial node to
// assign to unknown clusters
let (mut prim_list, spatial_node_index) = {
let pic = &mut pictures[pic_index.0];
assert_ne!(pic.spatial_node_index, SpatialNodeIndex::UNKNOWN);
if pic.flags.contains(PictureFlags::IS_RESOLVE_TARGET) {
pic.flags |= PictureFlags::DISABLE_SNAPPING;
}
// If we're a surface, use that spatial node, otherwise the parent
let spatial_node_index = match pic.composite_mode {
Some(_) => pic.spatial_node_index,
None => parent_spatial_node_index.expect("bug: no parent"),
};
(
mem::replace(&mut pic.prim_list, PrimitiveList::empty()),
spatial_node_index,
)
};
// Update the spatial node of any unknown clusters
for cluster in &mut prim_list.clusters {
if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
cluster.spatial_node_index = spatial_node_index;
}
}
// Work out the lowest common clip which is shared by all the
// primitives in this picture. If it is the same as the picture clip
// then store it as the clip tree root for the picture so that it is
// applied later as part of picture compositing. Gecko gives every
// primitive a viewport clip which, if applied within the picture,
// will mess up tile caching and mean we have to redraw on every
// scroll event (for tile caching to work usefully we specifically
// want to draw things even if they are outside the viewport).
let mut shared_clip_node_id = None;
for cluster in &prim_list.clusters {
for prim_instance in &prim_instances[cluster.prim_range()] {
let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
shared_clip_node_id = match shared_clip_node_id {
Some(current) => {
Some(clip_tree_builder.find_lowest_common_ancestor(
current,
leaf.node_id,
))
}
None => Some(leaf.node_id)
};
}
}
let lca_tree_node = shared_clip_node_id
.and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id))
.map(|node_id| clip_tree_builder.get_node(node_id));
let lca_node = lca_tree_node
.map(|tree_node| &clip_interner[tree_node.handle]);
let pic_node_id = prim_index
.map(|prim_index| clip_tree_builder.get_leaf(prim_instances[prim_index].clip_leaf_id).node_id)
.and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id));
let pic_node = pic_node_id
.map(|node_id| clip_tree_builder.get_node(node_id))
.map(|tree_node| &clip_interner[tree_node.handle]);
// The logic behind this optimisation is that there's no need to clip
// the contents of a picture when the crop will be applied anyway as
// part of compositing the picture. However, this is not true if the
// picture includes a blur filter as the blur result depends on the
// offscreen pixels which may or may not be cropped away.
let has_blur = match &pictures[pic_index.0].composite_mode {
Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true,
Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true,
Some(PictureCompositeMode::SvgFilter( .. )) => true,
Some(PictureCompositeMode::SVGFEGraph( .. )) => true,
_ => false,
};
// It is only safe to apply this optimisation if the old pic clip node
// is the direct parent of the new LCA node. If this is not the case
// then there could be other more restrictive clips in between the two
// which we would ignore by changing the clip root. See Bug 1854062
// for an example of this.
let direct_parent = lca_tree_node
.zip(pic_node_id)
.map(|(lca_tree_node, pic_node_id)| lca_tree_node.parent == pic_node_id)
.unwrap_or(false);
if let Some((lca_node, pic_node)) = lca_node.zip(pic_node) {
// It is only safe to ignore the LCA clip (by making it the clip
// root) if it is equal to or larger than the picture clip. But
// this comparison also needs to take into account spatial nodes
// as the two clips may in general be on different spatial nodes.
// For this specific Gecko optimisation we expect the the two
// clips to be identical and have the same spatial node so it's
// simplest to just test for ClipItemKey equality (which includes
// both spatial node and the actual clip).
if lca_node.key == pic_node.key && !has_blur && direct_parent {
pictures[pic_index.0].clip_root = shared_clip_node_id;
}
}
// Update the spatial node of any child pictures
for cluster in &prim_list.clusters {
for prim_instance_index in cluster.prim_range() {
if let PrimitiveInstanceKind::Picture { pic_index: child_pic_index, .. } = prim_instances[prim_instance_index].kind {
let child_pic = &mut pictures[child_pic_index.0];
if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN {
child_pic.spatial_node_index = spatial_node_index;
}
// Recurse into child pictures which may also have unknown spatial nodes
SceneBuilder::finalize_picture(
child_pic_index,
Some(prim_instance_index),
pictures,
Some(spatial_node_index),
clip_tree_builder,
prim_instances,
clip_interner,
);
if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) {
pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING;
}
}
}
}
// Restore the prim_list
pictures[pic_index.0].prim_list = prim_list;
}
/// Retrieve the current external scroll offset on the provided spatial node.
fn current_external_scroll_offset(
&mut self,
spatial_node_index: SpatialNodeIndex,
) -> LayoutVector2D {
// Get the external scroll offset, if applicable.
self.external_scroll_mapper
.external_scroll_offset(
spatial_node_index,
self.spatial_tree,
)
}
fn build_spatial_tree_for_display_list(
&mut self,
dl: &BuiltDisplayList,
pipeline_id: PipelineId,
instance_id: PipelineInstanceId,
) {
dl.iter_spatial_tree(|item| {
match item {
SpatialTreeItem::ScrollFrame(descriptor) => {
let parent_space = self.get_space(descriptor.parent_space);
self.build_scroll_frame(
descriptor,
parent_space,
pipeline_id,
instance_id,
);
}
SpatialTreeItem::ReferenceFrame(descriptor) => {
let parent_space = self.get_space(descriptor.parent_spatial_id);
self.build_reference_frame(
descriptor,
parent_space,
pipeline_id,
instance_id,
);
}
SpatialTreeItem::StickyFrame(descriptor) => {
let parent_space = self.get_space(descriptor.parent_spatial_id);
self.build_sticky_frame(
descriptor,
parent_space,
instance_id,
);
}
SpatialTreeItem::Invalid => {
unreachable!();
}
}
});
}
fn build_all(
&mut self,
root_pipeline_id: PipelineId,
root_pipeline: &ScenePipeline,
) {
enum ContextKind<'a> {
Root,
StackingContext {
sc_info: StackingContextInfo,
},
ReferenceFrame,
Iframe {
parent_traversal: BuiltDisplayListIter<'a>,
}
}
struct BuildContext<'a> {
pipeline_id: PipelineId,
kind: ContextKind<'a>,
}
self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());
let instance_id = self.get_next_instance_id_for_pipeline(root_pipeline_id);
self.push_root(
root_pipeline_id,
instance_id,
);
self.build_spatial_tree_for_display_list(
&root_pipeline.display_list.display_list,
root_pipeline_id,
instance_id,
);
let mut stack = vec![BuildContext {
pipeline_id: root_pipeline_id,
kind: ContextKind::Root,
}];
let mut traversal = root_pipeline.display_list.iter();
'outer: while let Some(bc) = stack.pop() {
loop {
let item = match traversal.next() {
Some(item) => item,
None => break,
};
match item.item() {
DisplayItem::PushStackingContext(ref info) => {
profile_scope!("build_stacking_context");
let spatial_node_index = self.get_space(info.spatial_id);
let mut subtraversal = item.sub_iter();
// Avoid doing unnecessary work for empty stacking contexts.
// We still have to process it if it has filters, they
// may be things like SVGFEFlood or various specific
// ways to use ComponentTransfer, ColorMatrix, Composite
// which are still visible on an empty stacking context
if subtraversal.current_stacking_context_empty() && item.filters().is_empty() {
subtraversal.skip_current_stacking_context();
traversal = subtraversal;
continue;
}
let snapshot = info.snapshot.map(|snapshot| {
// Offset the snapshot area by the stacking context origin
// so that the area is expressed in the same coordinate space
// as the items in the stacking context.
SnapshotInfo {
area: snapshot.area.translate(info.origin.to_vector()),
.. snapshot
}
});
let composition_operations = CompositeOps::new(
filter_ops_for_compositing(item.filters()),
filter_datas_for_compositing(item.filter_datas()),
filter_primitives_for_compositing(item.filter_primitives()),
info.stacking_context.mix_blend_mode_for_compositing(),
snapshot,
);
let sc_info = self.push_stacking_context(
composition_operations,
info.stacking_context.transform_style,
info.prim_flags,
spatial_node_index,
info.stacking_context.clip_chain_id,
info.stacking_context.raster_space,
info.stacking_context.flags,
info.ref_frame_offset + info.origin.to_vector(),
);
let new_context = BuildContext {
pipeline_id: bc.pipeline_id,
kind: ContextKind::StackingContext {
sc_info,
},
};
stack.push(bc);
stack.push(new_context);
subtraversal.merge_debug_stats_from(&mut traversal);
traversal = subtraversal;
continue 'outer;
}
DisplayItem::PushReferenceFrame(..) => {
profile_scope!("build_reference_frame");
let mut subtraversal = item.sub_iter();
let new_context = BuildContext {
pipeline_id: bc.pipeline_id,
kind: ContextKind::ReferenceFrame,
};
stack.push(bc);
stack.push(new_context);
subtraversal.merge_debug_stats_from(&mut traversal);
traversal = subtraversal;
continue 'outer;
}
DisplayItem::PopReferenceFrame |
DisplayItem::PopStackingContext => break,
DisplayItem::Iframe(ref info) => {
profile_scope!("iframe");
let space = self.get_space(info.space_and_clip.spatial_id);
let subtraversal = match self.push_iframe(info, space) {
Some(pair) => pair,
None => continue,
};
let new_context = BuildContext {
pipeline_id: info.pipeline_id,
kind: ContextKind::Iframe {
parent_traversal: mem::replace(&mut traversal, subtraversal),
},
};
stack.push(bc);
stack.push(new_context);
continue 'outer;
}
_ => {
self.build_item(item);
}
};
}
match bc.kind {
ContextKind::Root => {}
ContextKind::StackingContext { sc_info } => {
self.pop_stacking_context(sc_info);
}
ContextKind::ReferenceFrame => {
}
ContextKind::Iframe { parent_traversal } => {
self.iframe_size.pop();
self.clip_tree_builder.pop_clip();
self.clip_tree_builder.pop_clip();
if self.iframe_size.is_empty() {
assert!(self.root_iframe_clip.is_some());
self.root_iframe_clip = None;
self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
}
self.id_to_index_mapper_stack.pop().unwrap();
traversal = parent_traversal;
}
}
// TODO: factor this out to be part of capture
if cfg!(feature = "display_list_stats") {
let stats = traversal.debug_stats();
let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
debug!("item, total count, total bytes, % of DL bytes, bytes per item");
for (label, stats) in stats {
debug!("{}, {}, {}kb, {}%, {}",
label,
stats.total_count,
stats.num_bytes / 1000,
((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
stats.num_bytes / stats.total_count.max(1));
}
debug!("");
}
}
debug_assert!(self.sc_stack.is_empty());
self.id_to_index_mapper_stack.pop().unwrap();
assert!(self.id_to_index_mapper_stack.is_empty());
}
fn build_sticky_frame(
&mut self,
info: &StickyFrameDescriptor,
parent_node_index: SpatialNodeIndex,
instance_id: PipelineInstanceId,
) {
let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);
let sticky_frame_info = StickyFrameInfo::new(
info.bounds.translate(external_scroll_offset),
info.margins,
info.vertical_offset_bounds,
info.horizontal_offset_bounds,
info.previously_applied_offset,
info.transform,
);
let index = self.spatial_tree.add_sticky_frame(
parent_node_index,
sticky_frame_info,
info.id.pipeline_id(),
info.key,
instance_id,
);
self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(info.id, index);
}
fn build_reference_frame(
&mut self,
info: &ReferenceFrameDescriptor,
parent_space: SpatialNodeIndex,
pipeline_id: PipelineId,
instance_id: PipelineInstanceId,
) {
let transform = match info.reference_frame.transform {
ReferenceTransformBinding::Static { binding } => binding,
ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => {
let content_size = &self.iframe_size.last().unwrap();
let mut transform = if let Some(scale_from) = scale_from {
// If we have a 90/270 degree rotation, then scale_from
// and content_size are in different coordinate spaces and
// we need to swap width/height for them to be correct.
match rotation {
Rotation::Degree0 |
Rotation::Degree180 => {
LayoutTransform::scale(
content_size.width / scale_from.width,
content_size.height / scale_from.height,
1.0
)
},
Rotation::Degree90 |
Rotation::Degree270 => {
LayoutTransform::scale(
content_size.height / scale_from.width,
content_size.width / scale_from.height,
1.0
)
}
}
} else {
LayoutTransform::identity()
};
if vertical_flip {
let content_size = &self.iframe_size.last().unwrap();
let content_height = match rotation {
Rotation::Degree0 | Rotation::Degree180 => content_size.height,
Rotation::Degree90 | Rotation::Degree270 => content_size.width,
};
transform = transform
.then_translate(LayoutVector3D::new(0.0, content_height, 0.0))
.pre_scale(1.0, -1.0, 1.0);
}
let rotate = rotation.to_matrix(**content_size);
let transform = transform.then(&rotate);
PropertyBinding::Value(transform)
},
};
let external_scroll_offset = self.current_external_scroll_offset(parent_space);
self.push_reference_frame(
info.reference_frame.id,
parent_space,
pipeline_id,
info.reference_frame.transform_style,
transform,
info.reference_frame.kind,
(info.origin + external_scroll_offset).to_vector(),
SpatialNodeUid::external(info.reference_frame.key, pipeline_id, instance_id),
);
}
fn build_scroll_frame(
&mut self,
info: &ScrollFrameDescriptor,
parent_node_index: SpatialNodeIndex,
pipeline_id: PipelineId,
instance_id: PipelineInstanceId,
) {
// This is useful when calculating scroll extents for the
// SpatialNode::scroll(..) API as well as for properly setting sticky
// positioning offsets.
let content_size = info.content_rect.size();
let external_scroll_offset = self.current_external_scroll_offset(parent_node_index);
self.add_scroll_frame(
info.scroll_frame_id,
parent_node_index,
info.external_id,
pipeline_id,
&info.frame_rect.translate(external_scroll_offset),
&content_size,
ScrollFrameKind::Explicit,
info.external_scroll_offset,
info.scroll_offset_generation,
info.has_scroll_linked_effect,
SpatialNodeUid::external(info.key, pipeline_id, instance_id),
);
}
/// Advance and return the next instance id for a given pipeline id
fn get_next_instance_id_for_pipeline(
&mut self,
pipeline_id: PipelineId,
) -> PipelineInstanceId {
let next_instance = self.pipeline_instance_ids
.entry(pipeline_id)
.or_insert(0);
let instance_id = PipelineInstanceId::new(*next_instance);
*next_instance += 1;
instance_id
}
fn push_iframe(
&mut self,
info: &IframeDisplayItem,
spatial_node_index: SpatialNodeIndex,
) -> Option<BuiltDisplayListIter<'a>> {
let iframe_pipeline_id = info.pipeline_id;
let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
Some(pipeline) => pipeline,
None => {
debug_assert!(info.ignore_missing_pipeline);
return None
},
};
self.clip_tree_builder.push_clip_chain(Some(info.space_and_clip.clip_chain_id), false);
let external_scroll_offset = self.current_external_scroll_offset(spatial_node_index);
// TODO(gw): This is the only remaining call site that relies on ClipId parenting, remove me!
self.add_rect_clip_node(
ClipId::root(iframe_pipeline_id),
info.space_and_clip.spatial_id,
&info.clip_rect,
);
self.clip_tree_builder.push_clip_id(ClipId::root(iframe_pipeline_id));
let instance_id = self.get_next_instance_id_for_pipeline(iframe_pipeline_id);
self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default());
let mut bounds = self.snap_rect(
&info.bounds,
spatial_node_index,
);
bounds = bounds.translate(external_scroll_offset);
let spatial_node_index = self.push_reference_frame(
SpatialId::root_reference_frame(iframe_pipeline_id),
spatial_node_index,
iframe_pipeline_id,
TransformStyle::Flat,
PropertyBinding::Value(LayoutTransform::identity()),
ReferenceFrameKind::Transform {
is_2d_scale_translation: true,
should_snap: true,
paired_with_perspective: false,
},
bounds.min.to_vector(),
SpatialNodeUid::root_reference_frame(iframe_pipeline_id, instance_id),
);
let iframe_rect = LayoutRect::from_size(bounds.size());
let is_root_pipeline = self.iframe_size.is_empty();
self.add_scroll_frame(
SpatialId::root_scroll_node(iframe_pipeline_id),
spatial_node_index,
ExternalScrollId(0, iframe_pipeline_id),
iframe_pipeline_id,
&iframe_rect,
&bounds.size(),
ScrollFrameKind::PipelineRoot {
is_root_pipeline,
},
LayoutVector2D::zero(),
APZScrollGeneration::default(),
HasScrollLinkedEffect::No,
SpatialNodeUid::root_scroll_frame(iframe_pipeline_id, instance_id),
);
// If this is a root iframe, force a new tile cache both before and after
// adding primitives for this iframe.
if self.iframe_size.is_empty() {
assert!(self.root_iframe_clip.is_none());
self.root_iframe_clip = Some(ClipId::root(iframe_pipeline_id));
self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
}
self.iframe_size.push(bounds.size());
self.build_spatial_tree_for_display_list(
&pipeline.display_list.display_list,
iframe_pipeline_id,
instance_id,
);
Some(pipeline.display_list.iter())
}
fn get_space(
&self,
spatial_id: SpatialId,
) -> SpatialNodeIndex {
self.id_to_index_mapper_stack.last().unwrap().get_spatial_node_index(spatial_id)
}
fn get_clip_node(
&mut self,
clip_chain_id: api::ClipChainId,
) -> ClipNodeId {
self.clip_tree_builder.build_clip_set(
clip_chain_id,
)
}
fn process_common_properties(
&mut self,
common: &CommonItemProperties,
bounds: Option<LayoutRect>,
) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
let spatial_node_index = self.get_space(common.spatial_id);
// If no bounds rect is given, default to clip rect.
let (rect, clip_rect) = if common.flags.contains(PrimitiveFlags::ANTIALISED) {
(bounds.unwrap_or(common.clip_rect), common.clip_rect)
} else {
let clip_rect = self.snap_rect(
&common.clip_rect,
spatial_node_index,
);
let rect = bounds.map_or(clip_rect, |bounds| {
self.snap_rect(
&bounds,
spatial_node_index,
)
});
(rect, clip_rect)
};
let current_offset = self.current_external_scroll_offset(spatial_node_index);
let rect = rect.translate(current_offset);
let clip_rect = clip_rect.translate(current_offset);
let unsnapped_rect = bounds.unwrap_or(common.clip_rect).translate(current_offset);
let clip_node_id = self.get_clip_node(
common.clip_chain_id,
);
let layout = LayoutPrimitiveInfo {
rect,
clip_rect,
flags: common.flags,
};
(layout, unsnapped_rect, spatial_node_index, clip_node_id)
}
fn process_common_properties_with_bounds(
&mut self,
common: &CommonItemProperties,
bounds: LayoutRect,
) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) {
self.process_common_properties(
common,
Some(bounds),
)
}
pub fn snap_rect(
&mut self,
rect: &LayoutRect,
target_spatial_node: SpatialNodeIndex,
) -> LayoutRect {
self.snap_to_device.set_target_spatial_node(
target_spatial_node,
self.spatial_tree,
);
self.snap_to_device.snap_rect(&rect)
}
fn build_item<'b>(
&'b mut self,
item: DisplayItemRef,
) {
match *item.item() {
DisplayItem::Image(ref info) => {
profile_scope!("image");
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
self.add_image(
spatial_node_index,
clip_node_id,
&layout,
layout.rect.size(),
LayoutSize::zero(),
info.image_key,
info.image_rendering,
info.alpha_type,
info.color,
);
}
DisplayItem::RepeatingImage(ref info) => {
profile_scope!("repeating_image");
let (layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
let stretch_size = process_repeat_size(
&layout.rect,
&unsnapped_rect,
info.stretch_size,
);
self.add_image(
spatial_node_index,
clip_node_id,
&layout,
stretch_size,
info.tile_spacing,
info.image_key,
info.image_rendering,
info.alpha_type,
info.color,
);
}
DisplayItem::YuvImage(ref info) => {
profile_scope!("yuv_image");
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
self.add_yuv_image(
spatial_node_index,
clip_node_id,
&layout,
info.yuv_data,
info.color_depth,
info.color_space,
info.color_range,
info.image_rendering,
);
}
DisplayItem::Text(ref info) => {
profile_scope!("text");
// TODO(aosmond): Snapping text primitives does not make much sense, given the
// primitive bounds and clip are supposed to be conservative, not definitive.
// E.g. they should be able to grow and not impact the output. However there
// are subtle interactions between the primitive origin and the glyph offset
// which appear to be significant (presumably due to some sort of accumulated
// error throughout the layers). We should fix this at some point.
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
self.add_text(
spatial_node_index,
clip_node_id,
&layout,
&info.font_key,
&info.color,
item.glyphs(),
info.glyph_options,
info.ref_frame_offset,
);
}
DisplayItem::Rectangle(ref info) => {
profile_scope!("rect");
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
self.add_primitive(
spatial_node_index,
clip_node_id,
&layout,
Vec::new(),
PrimitiveKeyKind::Rectangle {
color: info.color.into(),
},
);
if info.common.flags.contains(PrimitiveFlags::CHECKERBOARD_BACKGROUND) {
self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
}
}
DisplayItem::HitTest(ref info) => {
profile_scope!("hit_test");
let spatial_node_index = self.get_space(info.spatial_id);
let current_offset = self.current_external_scroll_offset(spatial_node_index);
let mut rect = self.snap_rect(
&info.rect,
spatial_node_index,
);
rect = rect.translate(current_offset);
let layout = LayoutPrimitiveInfo {
rect,
clip_rect: rect,
flags: info.flags,
};
let spatial_node = self.spatial_tree.get_node_info(spatial_node_index);
let anim_id: u64 = match spatial_node.node_type {
SpatialNodeType::ReferenceFrame(ReferenceFrameInfo {
source_transform: PropertyBinding::Binding(key, _),
..
}) => key.clone().into(),
SpatialNodeType::StickyFrame(StickyFrameInfo {
transform: Some(PropertyBinding::Binding(key, _)),
..
}) => key.clone().into(),
_ => 0,
};
let clip_node_id = self.get_clip_node(info.clip_chain_id);
self.add_primitive_to_hit_testing_list(
&layout,
spatial_node_index,
clip_node_id,
info.tag,
anim_id,
);
}
DisplayItem::ClearRectangle(ref info) => {
profile_scope!("clear");
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
self.add_clear_rectangle(
spatial_node_index,
clip_node_id,
&layout,
);
}
DisplayItem::Line(ref info) => {
profile_scope!("line");
let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.area,
);
self.add_line(
spatial_node_index,
clip_node_id,
&layout,
info.wavy_line_thickness,
info.orientation,
info.color,
info.style,
);
}
DisplayItem::Gradient(ref info) => {
profile_scope!("gradient");
if !info.gradient.is_valid() {
return;
}
let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds(
&info.common,
info.bounds,
);
let mut tile_size = process_repeat_size(
&layout.rect,
&unsnapped_rect,
info.tile_size,
);
let mut stops = read_gradient_stops(item.gradient_stops());
let mut start = info.gradient.start_point;
let mut end = info.gradient.end_point;
let flags = layout.flags;
let optimized = optimize_linear_gradient(
&mut layout.rect,
&mut tile_size,
info.tile_spacing,
&layout.clip_rect,
&mut start,
&mut end,
info.gradient.extend_mode,
&mut stops,
&mut |rect, start, end, stops, edge_aa_mask| {
let layout = LayoutPrimitiveInfo { rect: *rect, clip_rect: *rect, flags };
if let Some(prim_key_kind) = self.create_linear_gradient_prim(
&layout,
start,
end,
stops.to_vec(),
ExtendMode::Clamp,
rect.size(),
LayoutSize::zero(),
None,
edge_aa_mask,
) {
self.add_nonshadowable_primitive(
spatial_node_index,
clip_node_id,
&layout,
Vec::new(),
prim_key_kind,
);
}
}
);
if !optimized && !tile_size.ceil().is_empty() {
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet)
]
|
2026-04-06
|
|
|
|
|