/* 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/. */ use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags}; use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace}; use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData, SharedFontInstanceMap}; 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, ScrollFrameDisplayItem, ScrollSensitivity}; use api::{Shadow, SpaceAndClipInfo, SpatialId, StickyFrameDisplayItem, ImageMask, ItemTag}; use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData}; use api::{ReferenceTransformBinding, Rotation}; use api::units::*; use crate::image_tiling::simplify_repeated_primitive; use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind}; use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance, SceneClipInstance}; use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex}; use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig}; use crate::glyph_rasterizer::FontInstance; use crate::hit_test::HitTestingScene; use crate::intern::Interner; use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter}; use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions}; use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList}; use crate::prim_store::{PrimitiveInstance, register_prim_chase_id}; use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore}; use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex, PictureIndex}; use crate::prim_store::backdrop::Backdrop; use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientParams}; 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::{Scene, ScenePipeline, BuiltScene, SceneStats, StackingContextHelpers}; use crate::scene_builder_thread::Interners; use crate::space::SpaceSnapper; use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind}; 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::{MaxRect, VecHelper}; use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey}; use smallvec::SmallVec; /// The offset stack for a given reference frame. struct ReferenceFrameState { /// A stack of current offsets from the current reference frame scope. offsets: Vec, } /// Maps from stacking context layout coordinates into reference frame /// relative coordinates. struct ReferenceFrameMapper { /// A stack of reference frame scopes. frames: Vec, } impl ReferenceFrameMapper { fn new() -> Self { ReferenceFrameMapper { frames: vec![ ReferenceFrameState { offsets: vec![ LayoutVector2D::zero(), ], } ], } } /// Push a new scope. This resets the current offset to zero, and is /// used when a new reference frame or iframe is pushed. fn push_scope(&mut self) { self.frames.push(ReferenceFrameState { offsets: vec![ LayoutVector2D::zero(), ], }); } /// Pop a reference frame scope off the stack. fn pop_scope(&mut self) { self.frames.pop().unwrap(); } /// Push a new offset for the current scope. This is used when /// a new stacking context is pushed. fn push_offset(&mut self, offset: LayoutVector2D) { let frame = self.frames.last_mut().unwrap(); let current_offset = *frame.offsets.last().unwrap(); frame.offsets.push(current_offset + offset); } /// Pop a local stacking context offset from the current scope. fn pop_offset(&mut self) { let frame = self.frames.last_mut().unwrap(); frame.offsets.pop().unwrap(); } /// Retrieve the current offset to allow converting a stacking context /// relative coordinate to be relative to the owing reference frame. /// TODO(gw): We could perhaps have separate coordinate spaces for this, /// however that's going to either mean a lot of changes to /// public API code, or a lot of changes to internal code. /// Before doing that, we should revisit how Gecko would /// prefer to provide coordinates. /// TODO(gw): For now, this includes only the reference frame relative /// offset. Soon, we will expand this to include the initial /// scroll offsets that are now available on scroll nodes. This /// will allow normalizing the coordinates even between display /// lists where APZ has scrolled the content. fn current_offset(&self) -> LayoutVector2D { *self.frames.last().unwrap().offsets.last().unwrap() } } /// 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: &SpatialTree, ) -> LayoutVector2D { if spatial_node_index != self.current_spatial_node { self.current_spatial_node = spatial_node_index; self.current_offset = spatial_tree.external_scroll_offset(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, } impl NodeIdToIndexMapper { fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) { let _old_value = self.spatial_node_map.insert(id, index); debug_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, pub filter_datas: Vec, pub filter_primitives: Vec, // Requires two source textures (e.g. mix-blend-mode) pub mix_blend_mode: Option, } impl CompositeOps { pub fn new( filters: Vec, filter_datas: Vec, filter_primitives: Vec, mix_blend_mode: Option ) -> Self { CompositeOps { filters, filter_datas, filter_primitives, mix_blend_mode, } } pub fn is_empty(&self) -> bool { self.filters.is_empty() && self.filter_primitives.is_empty() && self.mix_blend_mode.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; } } _ => { 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, } 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, ) -> Self { PictureChainBuilder { current: PictureSource::PrimitiveList { prim_list, }, spatial_node_index, flags, } } /// Create a new picture chain builder, from a picture wrapper instance fn from_instance( instance: PrimitiveInstance, flags: PrimitiveFlags, spatial_node_index: SpatialNodeIndex, ) -> Self { PictureChainBuilder { current: PictureSource::WrappedPicture { instance, }, flags, spatial_node_index, } } /// Wrap the existing content with a new picture with the given parameters #[must_use] fn add_picture( self, composite_mode: PictureCompositeMode, context_3d: Picture3DContext, options: PictureOptions, interners: &mut Interners, prim_store: &mut PrimitiveStore, ) -> 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_list } }; let pic_index = PictureIndex(prim_store.pictures .alloc() .init(PicturePrimitive::new_image( Some(composite_mode.clone()), context_3d, true, self.flags, prim_list, self.spatial_node_index, options, )) ); let instance = create_prim_instance( pic_index, Some(composite_mode).into(), ClipChainId::NONE, interners, ); PictureChainBuilder { current: PictureSource::WrappedPicture { instance, }, spatial_node_index: self.spatial_node_index, flags: self.flags, } } /// Finish building this picture chain. Set the clip chain on the outermost picture fn finalize( self, clip_chain_id: ClipChainId, interners: &mut Interners, prim_store: &mut PrimitiveStore, ) -> PrimitiveInstance { match self.current { PictureSource::WrappedPicture { mut instance } => { instance.clip_set.clip_chain_id = clip_chain_id; instance } PictureSource::PrimitiveList { prim_list } => { // 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, true, self.flags, prim_list, self.spatial_node_index, PictureOptions::default(), )) ); create_prim_instance( pic_index, None.into(), clip_chain_id, interners, ) } } } } bitflags! { /// Slice flags pub struct SliceFlags : u8 { /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER const IS_SCROLLBAR = 1; } } /// 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. font_instances: SharedFontInstanceMap, /// The data structure that converts between ClipId/SpatialId and the various /// index types that the SpatialTree uses. id_to_index_mapper: NodeIdToIndexMapper, /// A stack of stacking context properties. sc_stack: Vec, /// Stack of spatial node indices forming containing block for 3d contexts containing_block_stack: Vec, /// Stack of requested raster spaces for stacking contexts raster_space_stack: Vec, /// Maintains state for any currently active shadows pending_shadow_items: VecDeque, /// The SpatialTree that we are currently building during building. pub spatial_tree: SpatialTree, /// 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. interners: &'a mut Interners, /// Helper struct to map stacking context coords <-> reference frame coords. rf_mapper: ReferenceFrameMapper, /// 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, /// 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, } impl<'a> SceneBuilder<'a> { pub fn build( scene: &Scene, font_instances: SharedFontInstanceMap, view: &SceneView, frame_builder_config: &FrameBuilderConfig, interners: &mut Interners, stats: &SceneStats, ) -> 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 background_color = root_pipeline .background_color .and_then(|color| if color.a > 0.0 { Some(color) } else { None }); let device_pixel_scale = view.accumulated_scale_factor_for_snapping(); let spatial_tree = SpatialTree::new(); let snap_to_device = SpaceSnapper::new( ROOT_SPATIAL_NODE_INDEX, device_pixel_scale, ); let mut builder = SceneBuilder { scene, spatial_tree, font_instances, config: *frame_builder_config, id_to_index_mapper: NodeIdToIndexMapper::default(), hit_testing_scene: HitTestingScene::new(&stats.hit_test_stats), pending_shadow_items: VecDeque::new(), sc_stack: Vec::new(), containing_block_stack: Vec::new(), raster_space_stack: vec![RasterSpace::Screen], prim_store: PrimitiveStore::new(&stats.prim_store_stats), clip_store: ClipStore::new(), interners, rf_mapper: ReferenceFrameMapper::new(), external_scroll_mapper: ScrollOffsetMapper::new(), iframe_size: Vec::new(), quality_settings: view.quality_settings, tile_cache_builder: TileCacheBuilder::new(), snap_to_device, }; builder.build_all(&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.clip_store, &mut builder.prim_store, ); BuiltScene { has_root_pipeline: scene.has_root_pipeline(), pipeline_epochs: scene.pipeline_epochs.clone(), output_rect: view.device_rect.size.into(), background_color, hit_testing_scene: Arc::new(builder.hit_testing_scene), spatial_tree: builder.spatial_tree, prim_store: builder.prim_store, clip_store: builder.clip_store, config: builder.config, tile_cache_config, tile_cache_pictures, } } /// Retrieve the current offset to allow converting a stacking context /// relative coordinate to be relative to the owing reference frame, /// also considering any external scroll offset on the provided /// spatial node. fn current_offset( &mut self, spatial_node_index: SpatialNodeIndex, ) -> LayoutVector2D { // Get the current offset from stacking context <-> reference frame space. let rf_offset = self.rf_mapper.current_offset(); // Get the external scroll offset, if applicable. let scroll_offset = self .external_scroll_mapper .external_scroll_offset( spatial_node_index, &self.spatial_tree, ); rf_offset + scroll_offset } fn build_all(&mut self, 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>, } let root_clip_id = ClipId::root(root_pipeline.pipeline_id); self.clip_store.register_clip_template(root_clip_id, root_clip_id, &[]); self.clip_store.push_clip_root(Some(root_clip_id), false); self.push_root( root_pipeline.pipeline_id, &root_pipeline.viewport_size, ); let mut stack = vec![BuildContext { pipeline_id: root_pipeline.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. if subtraversal.current_stacking_context_empty() { subtraversal.skip_current_stacking_context(); traversal = subtraversal; continue; } 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(), ); let sc_info = self.push_stacking_context( composition_operations, info.stacking_context.transform_style, info.prim_flags, spatial_node_index, info.stacking_context.clip_id, info.stacking_context.raster_space, info.stacking_context.flags, ); self.rf_mapper.push_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(ref info) => { profile_scope!("build_reference_frame"); let parent_space = self.get_space(info.parent_spatial_id); let mut subtraversal = item.sub_iter(); let current_offset = self.current_offset(parent_space); 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(); transform = transform .then_translate(LayoutVector3D::new(0.0, content_size.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) }, }; self.push_reference_frame( info.reference_frame.id, Some(parent_space), bc.pipeline_id, info.reference_frame.transform_style, transform, info.reference_frame.kind, current_offset + info.origin.to_vector(), ); self.rf_mapper.push_scope(); 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 (size, subtraversal) = match self.push_iframe(info, space) { Some(pair) => pair, None => continue, }; // 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() { self.tile_cache_builder.add_tile_cache_barrier(); } self.rf_mapper.push_scope(); self.iframe_size.push(size); 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, bc.pipeline_id); } }; } match bc.kind { ContextKind::Root => {} ContextKind::StackingContext { sc_info } => { self.rf_mapper.pop_offset(); self.pop_stacking_context(sc_info); } ContextKind::ReferenceFrame => { self.rf_mapper.pop_scope(); } ContextKind::Iframe { parent_traversal } => { self.iframe_size.pop(); self.rf_mapper.pop_scope(); self.clip_store.pop_clip_root(); if self.iframe_size.is_empty() { self.tile_cache_builder.add_tile_cache_barrier(); } 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(); println!("item, total count, total bytes, % of DL bytes, bytes per item"); for (label, stats) in stats { println!("{}, {}, {}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)); } println!(); } } self.clip_store.pop_clip_root(); debug_assert!(self.sc_stack.is_empty()); } fn build_sticky_frame( &mut self, info: &StickyFrameDisplayItem, parent_node_index: SpatialNodeIndex, ) { let current_offset = self.current_offset(parent_node_index); let frame_rect = info.bounds.translate(current_offset); let sticky_frame_info = StickyFrameInfo::new( frame_rect, info.margins, info.vertical_offset_bounds, info.horizontal_offset_bounds, info.previously_applied_offset, ); let index = self.spatial_tree.add_sticky_frame( parent_node_index, sticky_frame_info, info.id.pipeline_id(), ); self.id_to_index_mapper.add_spatial_node(info.id, index); } fn build_scroll_frame( &mut self, info: &ScrollFrameDisplayItem, parent_node_index: SpatialNodeIndex, pipeline_id: PipelineId, ) { let current_offset = self.current_offset(parent_node_index); let clip_region = ClipRegion::create_for_clip_node_with_local_clip( &info.clip_rect, ¤t_offset, ); // Just use clip rectangle as the frame rect for this scroll frame. // This is useful when calculating scroll extents for the // SpatialNode::scroll(..) API as well as for properly setting sticky // positioning offsets. let frame_rect = clip_region.main; let content_size = info.content_rect.size; self.add_clip_node(info.clip_id, &info.parent_space_and_clip, clip_region); self.add_scroll_frame( info.scroll_frame_id, parent_node_index, info.external_id, pipeline_id, &frame_rect, &content_size, info.scroll_sensitivity, ScrollFrameKind::Explicit, info.external_scroll_offset, ); } fn push_iframe( &mut self, info: &IframeDisplayItem, spatial_node_index: SpatialNodeIndex, ) -> Option<(LayoutSize, 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 }, }; let current_offset = self.current_offset(spatial_node_index); self.add_clip_node( ClipId::root(iframe_pipeline_id), &info.space_and_clip, ClipRegion::create_for_clip_node_with_local_clip( &info.clip_rect, ¤t_offset, ), ); self.clip_store.push_clip_root( Some(ClipId::root(iframe_pipeline_id)), true, ); let bounds = self.snap_rect( &info.bounds.translate(current_offset), spatial_node_index, ); let spatial_node_index = self.push_reference_frame( SpatialId::root_reference_frame(iframe_pipeline_id), Some(spatial_node_index), iframe_pipeline_id, TransformStyle::Flat, PropertyBinding::Value(LayoutTransform::identity()), ReferenceFrameKind::Transform, bounds.origin.to_vector(), ); let iframe_rect = LayoutRect::new(LayoutPoint::zero(), 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, ScrollSensitivity::ScriptAndInputEvents, ScrollFrameKind::PipelineRoot { is_root_pipeline, }, LayoutVector2D::zero(), ); Some((bounds.size, pipeline.display_list.iter())) } fn get_space( &self, spatial_id: SpatialId, ) -> SpatialNodeIndex { self.id_to_index_mapper.get_spatial_node_index(spatial_id) } fn get_clip_chain( &mut self, clip_id: ClipId, ) -> ClipChainId { self.clip_store.get_or_build_clip_chain_id(clip_id) } fn process_common_properties( &mut self, common: &CommonItemProperties, bounds: Option<&LayoutRect>, ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { let spatial_node_index = self.get_space(common.spatial_id); let clip_chain_id = self.get_clip_chain(common.clip_id); let current_offset = self.current_offset(spatial_node_index); let unsnapped_clip_rect = common.clip_rect.translate(current_offset); let clip_rect = self.snap_rect( &unsnapped_clip_rect, spatial_node_index, ); let unsnapped_rect = bounds.map(|bounds| { bounds.translate(current_offset) }); // If no bounds rect is given, default to clip rect. let rect = unsnapped_rect.map_or(clip_rect, |bounds| { self.snap_rect( &bounds, spatial_node_index, ) }); let layout = LayoutPrimitiveInfo { rect, clip_rect, flags: common.flags, }; (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id) } fn process_common_properties_with_bounds( &mut self, common: &CommonItemProperties, bounds: &LayoutRect, ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { 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, pipeline_id: PipelineId, ) { match *item.item() { DisplayItem::Image(ref info) => { profile_scope!("image"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_image( spatial_node_index, clip_chain_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_chain_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_chain_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_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_yuv_image( spatial_node_index, clip_chain_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_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_text( spatial_node_index, clip_chain_id, &layout, &info.font_key, &info.color, item.glyphs(), info.glyph_options, ); } DisplayItem::Rectangle(ref info) => { profile_scope!("rect"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_primitive( spatial_node_index, clip_chain_id, &layout, Vec::new(), PrimitiveKeyKind::Rectangle { color: info.color.into(), }, ); } DisplayItem::HitTest(ref info) => { profile_scope!("hit_test"); // TODO(gw): We could skip building the clip-chain here completely, as it's not used by // hit-test items. let (layout, _, spatial_node_index, _) = self.process_common_properties( &info.common, None, ); // Don't add transparent rectangles to the draw list, // but do consider them for hit testing. This allows // specifying invisible hit testing areas. self.add_primitive_to_hit_testing_list( &layout, spatial_node_index, info.common.clip_id, info.tag, ); } DisplayItem::ClearRectangle(ref info) => { profile_scope!("clear"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_clear_rectangle( spatial_node_index, clip_chain_id, &layout, ); } DisplayItem::Line(ref info) => { profile_scope!("line"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.area, ); self.add_line( spatial_node_index, clip_chain_id, &layout, info.wavy_line_thickness, info.orientation, info.color, info.style, ); } DisplayItem::Gradient(ref info) => { profile_scope!("gradient"); let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let tile_size = process_repeat_size( &layout.rect, &unsnapped_rect, info.tile_size, ); if let Some(prim_key_kind) = self.create_linear_gradient_prim( &layout, info.gradient.start_point, info.gradient.end_point, item.gradient_stops(), info.gradient.extend_mode, tile_size, info.tile_spacing, None, ) { self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, &layout, Vec::new(), prim_key_kind, ); } } DisplayItem::RadialGradient(ref info) => { profile_scope!("radial"); let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let tile_size = process_repeat_size( &layout.rect, &unsnapped_rect, info.tile_size, ); let prim_key_kind = self.create_radial_gradient_prim( &layout, info.gradient.center, info.gradient.start_offset * info.gradient.radius.width, info.gradient.end_offset * info.gradient.radius.width, info.gradient.radius.width / info.gradient.radius.height, item.gradient_stops(), info.gradient.extend_mode, tile_size, info.tile_spacing, None, ); self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, &layout, Vec::new(), prim_key_kind, ); } DisplayItem::ConicGradient(ref info) => { profile_scope!("conic"); let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let tile_size = process_repeat_size( &layout.rect, &unsnapped_rect, info.tile_size, ); let prim_key_kind = self.create_conic_gradient_prim( &layout, info.gradient.center, info.gradient.angle, info.gradient.start_offset, info.gradient.end_offset, item.gradient_stops(), info.gradient.extend_mode, tile_size, info.tile_spacing, None, ); self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, &layout, Vec::new(), prim_key_kind, ); } DisplayItem::BoxShadow(ref info) => { profile_scope!("box_shadow"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.box_bounds, ); self.add_box_shadow( spatial_node_index, clip_chain_id, &layout, &info.offset, info.color, info.blur_radius, info.spread_radius, info.border_radius, info.clip_mode, ); } DisplayItem::Border(ref info) => { profile_scope!("border"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); self.add_border( spatial_node_index, clip_chain_id, &layout, info, item.gradient_stops(), ); } DisplayItem::ImageMaskClip(ref info) => { profile_scope!("image_clip"); let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); let current_offset = self.current_offset(parent_space); let image_mask = ImageMask { rect: info.image_mask.rect.translate(current_offset), ..info.image_mask }; self.add_image_mask_clip_node( info.id, &info.parent_space_and_clip, &image_mask, ); } DisplayItem::RoundedRectClip(ref info) => { profile_scope!("rounded_clip"); let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); let current_offset = self.current_offset(parent_space); self.add_rounded_rect_clip_node( info.id, &info.parent_space_and_clip, &info.clip, current_offset, ); } DisplayItem::RectClip(ref info) => { profile_scope!("rect_clip"); let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); let current_offset = self.current_offset(parent_space); let clip_rect = info.clip_rect.translate(current_offset); self.add_rect_clip_node( info.id, &info.parent_space_and_clip, &clip_rect, ); } DisplayItem::Clip(ref info) => { profile_scope!("clip"); let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); let current_offset = self.current_offset(parent_space); let clip_region = ClipRegion::create_for_clip_node( info.clip_rect, item.complex_clip().iter(), ¤t_offset, ); self.add_clip_node(info.id, &info.parent_space_and_clip, clip_region); } DisplayItem::ClipChain(ref info) => { profile_scope!("clip_chain"); let parent = info.parent.map_or(ClipId::root(pipeline_id), |id| ClipId::ClipChain(id)); let mut clips: SmallVec<[SceneClipInstance; 4]> = SmallVec::new(); for clip_item in item.clip_chain_items() { let template = self.clip_store.get_template(clip_item); clips.extend_from_slice(&template.clips); } self.clip_store.register_clip_template( ClipId::ClipChain(info.id), parent, &clips, ); }, DisplayItem::ScrollFrame(ref info) => { profile_scope!("scrollframe"); let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); self.build_scroll_frame( info, parent_space, pipeline_id, ); } DisplayItem::StickyFrame(ref info) => { profile_scope!("stickyframe"); let parent_space = self.get_space(info.parent_spatial_id); self.build_sticky_frame( info, parent_space, ); } DisplayItem::BackdropFilter(ref info) => { profile_scope!("backdrop"); let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties( &info.common, None, ); let filters = filter_ops_for_compositing(item.filters()); let filter_datas = filter_datas_for_compositing(item.filter_datas()); let filter_primitives = filter_primitives_for_compositing(item.filter_primitives()); self.add_backdrop_filter( spatial_node_index, clip_chain_id, &layout, filters, filter_datas, filter_primitives, ); } // Do nothing; these are dummy items for the display list parser DisplayItem::SetGradientStops | DisplayItem::SetFilterOps | DisplayItem::SetFilterData | DisplayItem::SetFilterPrimitives => {} // Special items that are handled in the parent method DisplayItem::PushStackingContext(..) | DisplayItem::PushReferenceFrame(..) | DisplayItem::PopReferenceFrame | DisplayItem::PopStackingContext | DisplayItem::Iframe(_) => { unreachable!("Handled in `build_all`") } DisplayItem::ReuseItems(key) | DisplayItem::RetainedItems(key) => { unreachable!("Iterator logic error: {:?}", key); } DisplayItem::PushShadow(info) => { profile_scope!("push_shadow"); let spatial_node_index = self.get_space(info.space_and_clip.spatial_id); let clip_chain_id = self.get_clip_chain( info.space_and_clip.clip_id, ); self.push_shadow( info.shadow, spatial_node_index, clip_chain_id, info.should_inflate, ); } DisplayItem::PopAllShadows => { profile_scope!("pop_all_shadows"); self.pop_all_shadows(); } } } // Given a list of clip sources, a positioning node and // a parent clip chain, return a new clip chain entry. // If the supplied list of clip sources is empty, then // just return the parent clip chain id directly. fn build_clip_chain( &mut self, clip_items: Vec, spatial_node_index: SpatialNodeIndex, parent_clip_chain_id: ClipChainId, ) -> ClipChainId { if clip_items.is_empty() { parent_clip_chain_id } else { let mut clip_chain_id = parent_clip_chain_id; for item in clip_items { // Intern this clip item, and store the handle // in the clip chain node. let handle = self.interners .clip .intern(&item, || { ClipInternData { clip_node_kind: item.kind.node_kind(), } }); clip_chain_id = self.clip_store.add_clip_chain_node( handle, spatial_node_index, clip_chain_id, ); } clip_chain_id } } /// Create a primitive and add it to the prim store. This method doesn't /// add the primitive to the draw list, so can be used for creating /// sub-primitives. /// /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list` fn create_primitive

( &mut self, info: &LayoutPrimitiveInfo, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, prim: P, ) -> PrimitiveInstance where P: InternablePrimitive, Interners: AsMut>, { // Build a primitive key. let prim_key = prim.into_key(info); let current_offset = self.current_offset(spatial_node_index); let interner = self.interners.as_mut(); let prim_data_handle = interner .intern(&prim_key, || ()); let instance_kind = P::make_instance_kind( prim_key, prim_data_handle, &mut self.prim_store, current_offset, ); PrimitiveInstance::new( info.clip_rect, instance_kind, clip_chain_id, ) } pub fn add_primitive_to_hit_testing_list( &mut self, info: &LayoutPrimitiveInfo, spatial_node_index: SpatialNodeIndex, clip_id: ClipId, tag: ItemTag, ) { self.hit_testing_scene.add_item( tag, info, spatial_node_index, clip_id, &self.clip_store, ); } /// Add an already created primitive to the draw lists. pub fn add_primitive_to_draw_list( &mut self, prim_instance: PrimitiveInstance, prim_rect: LayoutRect, spatial_node_index: SpatialNodeIndex, flags: PrimitiveFlags, ) { // Add primitive to the top-most stacking context on the stack. if prim_instance.is_chased() { println!("\tadded to stacking context at {}", self.sc_stack.len()); } // If we have a valid stacking context, the primitive gets added to that. // Otherwise, it gets added to a top-level picture cache slice. match self.sc_stack.last_mut() { Some(stacking_context) => { stacking_context.prim_list.add_prim( prim_instance, prim_rect, spatial_node_index, flags, ); } None => { self.tile_cache_builder.add_prim( prim_instance, prim_rect, spatial_node_index, flags, &self.spatial_tree, &self.clip_store, self.interners, &self.config, &self.quality_settings, ); } } } /// Convenience interface that creates a primitive entry and adds it /// to the draw list. fn add_nonshadowable_primitive

( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, clip_items: Vec, prim: P, ) where P: InternablePrimitive + IsVisible, Interners: AsMut>, { if prim.is_visible() { let clip_chain_id = self.build_clip_chain( clip_items, spatial_node_index, clip_chain_id, ); self.add_prim_to_draw_list( info, spatial_node_index, clip_chain_id, prim, ); } } pub fn add_primitive

( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, clip_items: Vec, prim: P, ) where P: InternablePrimitive + IsVisible, Interners: AsMut>, ShadowItem: From> { // If a shadow context is not active, then add the primitive // directly to the parent picture. if self.pending_shadow_items.is_empty() { self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, clip_items, prim, ); } else { debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives"); // There is an active shadow context. Store as a pending primitive // for processing during pop_all_shadows. self.pending_shadow_items.push_back(PendingPrimitive { spatial_node_index, clip_chain_id, info: *info, prim, }.into()); } } fn add_prim_to_draw_list

( &mut self, info: &LayoutPrimitiveInfo, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, prim: P, ) where P: InternablePrimitive, Interners: AsMut>, { let prim_instance = self.create_primitive( info, spatial_node_index, clip_chain_id, prim, ); self.register_chase_primitive_by_rect( &info.rect, &prim_instance, ); self.add_primitive_to_draw_list( prim_instance, info.rect, spatial_node_index, info.flags, ); } /// Push a new stacking context. Returns context that must be passed to pop_stacking_context(). fn push_stacking_context( &mut self, composite_ops: CompositeOps, transform_style: TransformStyle, prim_flags: PrimitiveFlags, spatial_node_index: SpatialNodeIndex, clip_id: Option, requested_raster_space: RasterSpace, flags: StackingContextFlags, ) -> StackingContextInfo { profile_scope!("push_stacking_context"); // Push current requested raster space on stack for prims to access self.raster_space_stack.push(requested_raster_space); // Get the transform-style of the parent stacking context, // which determines if we *might* need to draw this on // an intermediate surface for plane splitting purposes. let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() { Some(ref mut sc) if sc.is_3d() => { let flat_items_context_3d = match sc.context_3d { Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In { root_data: None, ancestor_index, }, Picture3DContext::Out => panic!("Unexpected out of 3D context"), }; // Cut the sequence of flat children before starting a child stacking context, // so that the relative order between them and our current SC is preserved. let extra_instance = sc.cut_item_sequence( &mut self.prim_store, &mut self.interners, Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)), flat_items_context_3d, ); let extra_instance = extra_instance.map(|(_, instance)| { ExtendedPrimitiveInstance { instance, spatial_node_index: sc.spatial_node_index, flags: sc.prim_flags, } }); (true, extra_instance) }, _ => (false, None), }; if let Some(instance) = extra_3d_instance { self.add_primitive_instance_to_3d_root(instance); } // If this is preserve-3d *or* the parent is, then this stacking // context is participating in the 3d rendering context. In that // case, hoist the picture up to the 3d rendering context // container, so that it's rendered as a sibling with other // elements in this context. let participating_in_3d_context = composite_ops.is_empty() && (parent_is_3d || transform_style == TransformStyle::Preserve3D); let context_3d = if participating_in_3d_context { // Get the spatial node index of the containing block, which // defines the context of backface-visibility. let ancestor_index = self.containing_block_stack .last() .cloned() .unwrap_or(ROOT_SPATIAL_NODE_INDEX); Picture3DContext::In { root_data: if parent_is_3d { None } else { Some(Vec::new()) }, ancestor_index, } } else { Picture3DContext::Out }; // Force an intermediate surface if the stacking context has a // complex clip node. In the future, we may decide during // prepare step to skip the intermediate surface if the // clip node doesn't affect the stacking context rect. let mut blit_reason = BlitReason::empty(); if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) { blit_reason |= BlitReason::ISOLATE; } // If backface visibility is explicitly set, force this stacking // context to be an off-screen surface. If part of a 3d context // (common case) it will already be an off-screen surface. If // the backface-vis is used while outside a 3d rendering context, // this is an edge case. if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) { blit_reason |= BlitReason::ISOLATE; } // If this stacking context has any complex clips, we need to draw it // to an off-screen surface. if let Some(clip_id) = clip_id { if self.clip_store.has_complex_clips(clip_id) { blit_reason |= BlitReason::CLIP; } } let is_redundant = FlattenedStackingContext::is_redundant( flags, &context_3d, &composite_ops, prim_flags, blit_reason, self.sc_stack.last(), ); let mut sc_info = StackingContextInfo { pop_clip_root: false, pop_stacking_context: false, pop_containing_block: false, }; // If this is not 3d, then it establishes an ancestor root for child 3d contexts. if !participating_in_3d_context { sc_info.pop_containing_block = true; self.containing_block_stack.push(spatial_node_index); } // This muct be built before the push_clip_root logic below let clip_chain_id = match clip_id { Some(clip_id) => self.clip_store.get_or_build_clip_chain_id(clip_id), None => ClipChainId::NONE, }; // If this has a valid clip, it will create a new clip root if let Some(clip_id) = clip_id { sc_info.pop_clip_root = true; // If this stacking context is redundant (prims will be pushed into // the parent during pop) but it has a valid clip, then we need to // add that clip to the current clip chain builder, so it's correctly // applied to any primitives within this redundant stacking context. // For the normal case, we start a new clip root, knowing that the // clip on this stacking context will be pushed onto the stack during // frame building. if is_redundant { self.clip_store.push_clip_root(Some(clip_id), true); } else { self.clip_store.push_clip_root(None, false); } // Push this clip id into the hit-testing scene for child primitives self.hit_testing_scene.push_clip(clip_id); } // If not redundant, create a stacking context to hold primitive clusters if !is_redundant { sc_info.pop_stacking_context = true; // Push the SC onto the stack, so we know how to handle things in // pop_stacking_context. self.sc_stack.push(FlattenedStackingContext { prim_list: PrimitiveList::empty(), prim_flags, spatial_node_index, clip_chain_id, composite_ops, blit_reason, transform_style, context_3d, is_redundant, is_backdrop_root: flags.contains(StackingContextFlags::IS_BACKDROP_ROOT), }); } sc_info } fn pop_stacking_context( &mut self, info: StackingContextInfo, ) { profile_scope!("pop_stacking_context"); // Pop off current raster space (pushed unconditionally in push_stacking_context) self.raster_space_stack.pop().unwrap(); // If the stacking context formed a containing block, pop off the stack if info.pop_containing_block { self.containing_block_stack.pop().unwrap(); } // If the stacking context established a clip root, pop off the stack if info.pop_clip_root { self.clip_store.pop_clip_root(); self.hit_testing_scene.pop_clip(); } // If the stacking context was otherwise redundant, early exit if !info.pop_stacking_context { return; } let stacking_context = self.sc_stack.pop().unwrap(); let parent_is_empty = match self.sc_stack.last() { Some(parent_sc) => { assert!(!stacking_context.is_redundant); parent_sc.prim_list.is_empty() }, None => true, }; let mut source = match stacking_context.context_3d { // TODO(gw): For now, as soon as this picture is in // a 3D context, we draw it to an intermediate // surface and apply plane splitting. However, // there is a large optimization opportunity here. // During culling, we can check if there is actually // perspective present, and skip the plane splitting // completely when that is not the case. Picture3DContext::In { ancestor_index, .. } => { let composite_mode = Some( PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason) ); // Add picture for this actual stacking context contents to render into. let pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( composite_mode.clone(), Picture3DContext::In { root_data: None, ancestor_index }, true, stacking_context.prim_flags, stacking_context.prim_list, stacking_context.spatial_node_index, PictureOptions::default(), )) ); let instance = create_prim_instance( pic_index, composite_mode.into(), ClipChainId::NONE, &mut self.interners, ); PictureChainBuilder::from_instance( instance, stacking_context.prim_flags, stacking_context.spatial_node_index, ) } Picture3DContext::Out => { if stacking_context.blit_reason.is_empty() { PictureChainBuilder::from_prim_list( stacking_context.prim_list, stacking_context.prim_flags, stacking_context.spatial_node_index, ) } else { let composite_mode = Some( PictureCompositeMode::Blit(stacking_context.blit_reason) ); // Add picture for this actual stacking context contents to render into. let pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( composite_mode.clone(), Picture3DContext::Out, true, stacking_context.prim_flags, stacking_context.prim_list, stacking_context.spatial_node_index, PictureOptions::default(), )) ); let instance = create_prim_instance( pic_index, composite_mode.into(), ClipChainId::NONE, &mut self.interners, ); PictureChainBuilder::from_instance( instance, stacking_context.prim_flags, stacking_context.spatial_node_index, ) } } }; // If establishing a 3d context, the `cur_instance` represents // a picture with all the *trailing* immediate children elements. // We append this to the preserve-3D picture set and make a container picture of them. if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d { let instance = source.finalize( ClipChainId::NONE, &mut self.interners, &mut self.prim_store, ); prims.push(ExtendedPrimitiveInstance { instance, spatial_node_index: stacking_context.spatial_node_index, flags: stacking_context.prim_flags, }); let mut prim_list = PrimitiveList::empty(); for ext_prim in prims.drain(..) { prim_list.add_prim( ext_prim.instance, LayoutRect::zero(), ext_prim.spatial_node_index, ext_prim.flags, ); } // This is the acttual picture representing our 3D hierarchy root. let pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( None, Picture3DContext::In { root_data: Some(Vec::new()), ancestor_index, }, true, stacking_context.prim_flags, prim_list, stacking_context.spatial_node_index, PictureOptions::default(), )) ); let instance = create_prim_instance( pic_index, PictureCompositeKey::Identity, ClipChainId::NONE, &mut self.interners, ); source = PictureChainBuilder::from_instance( instance, stacking_context.prim_flags, stacking_context.spatial_node_index, ); } let has_filters = stacking_context.composite_ops.has_valid_filters(); source = self.wrap_prim_with_filters( source, stacking_context.composite_ops.filters, stacking_context.composite_ops.filter_primitives, stacking_context.composite_ops.filter_datas, true, ); // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent // stacking context. // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is: // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs) // where // Cs = Source color // ab = Backdrop alpha // Cb = Backdrop color // // If we're the first primitive within a stacking context, then we can guarantee that the // backdrop alpha will be 0, and then the blend equation collapses to just // Cs = Cs, and the blend mode isn't taken into account at all. if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) { let parent_is_isolated = match self.sc_stack.last() { Some(parent_sc) => parent_sc.blit_reason.contains(BlitReason::ISOLATE), None => false, }; if parent_is_isolated { let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode); source = source.add_picture( composite_mode, Picture3DContext::Out, PictureOptions::default(), &mut self.interners, &mut self.prim_store, ); } else { // If we have a mix-blend-mode, the stacking context needs to be isolated // to blend correctly as per the CSS spec. // If not already isolated, we can't correctly blend. warn!("found a mix-blend-mode outside a blend container, ignoring"); } } // Set the stacking context clip on the outermost picture in the chain, // unless we already set it on the leaf picture. let cur_instance = source.finalize( stacking_context.clip_chain_id, &mut self.interners, &mut self.prim_store, ); // The primitive instance for the remainder of flat children of this SC // if it's a part of 3D hierarchy but not the root of it. let trailing_children_instance = match self.sc_stack.last_mut() { // Preserve3D path (only relevant if there are no filters/mix-blend modes) Some(ref parent_sc) if !has_filters && parent_sc.is_3d() => { Some(cur_instance) } // Regular parenting path Some(ref mut parent_sc) => { parent_sc.prim_list.add_prim( cur_instance, LayoutRect::zero(), stacking_context.spatial_node_index, stacking_context.prim_flags, ); None } // This must be the root stacking context None => { self.add_primitive_to_draw_list( cur_instance, LayoutRect::zero(), stacking_context.spatial_node_index, stacking_context.prim_flags, ); None } }; // finally, if there any outstanding 3D primitive instances, // find the 3D hierarchy root and add them there. if let Some(instance) = trailing_children_instance { self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance { instance, spatial_node_index: stacking_context.spatial_node_index, flags: stacking_context.prim_flags, }); } assert!( self.pending_shadow_items.is_empty(), "Found unpopped shadows when popping stacking context!" ); } pub fn push_reference_frame( &mut self, reference_frame_id: SpatialId, parent_index: Option, pipeline_id: PipelineId, transform_style: TransformStyle, source_transform: PropertyBinding, kind: ReferenceFrameKind, origin_in_parent_reference_frame: LayoutVector2D, ) -> SpatialNodeIndex { let index = self.spatial_tree.add_reference_frame( parent_index, transform_style, source_transform, kind, origin_in_parent_reference_frame, pipeline_id, ); self.id_to_index_mapper.add_spatial_node(reference_frame_id, index); index } pub fn push_root( &mut self, pipeline_id: PipelineId, viewport_size: &LayoutSize, ) { if let ChasePrimitive::Id(id) = self.config.chase_primitive { println!("Chasing {:?} by index", id); register_prim_chase_id(id); } let spatial_node_index = self.push_reference_frame( SpatialId::root_reference_frame(pipeline_id), None, pipeline_id, TransformStyle::Flat, PropertyBinding::Value(LayoutTransform::identity()), ReferenceFrameKind::Transform, LayoutVector2D::zero(), ); let viewport_rect = self.snap_rect( &LayoutRect::new(LayoutPoint::zero(), *viewport_size), spatial_node_index, ); self.add_scroll_frame( SpatialId::root_scroll_node(pipeline_id), spatial_node_index, ExternalScrollId(0, pipeline_id), pipeline_id, &viewport_rect, &viewport_rect.size, ScrollSensitivity::ScriptAndInputEvents, ScrollFrameKind::PipelineRoot { is_root_pipeline: true, }, LayoutVector2D::zero(), ); } fn add_image_mask_clip_node( &mut self, new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, image_mask: &ImageMask, ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); let snapped_mask_rect = self.snap_rect( &image_mask.rect, spatial_node_index, ); let item = ClipItemKey { kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect), }; let handle = self .interners .clip .intern(&item, || { ClipInternData { clip_node_kind: ClipNodeKind::Complex, } }); let instance = SceneClipInstance { key: item, clip: ClipInstance::new(handle, spatial_node_index), }; self.clip_store.register_clip_template( new_node_id, space_and_clip.clip_id, &[instance], ); } /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`. pub fn add_rect_clip_node( &mut self, new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, clip_rect: &LayoutRect, ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); let snapped_clip_rect = self.snap_rect( clip_rect, spatial_node_index, ); let item = ClipItemKey { kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), }; let handle = self .interners .clip .intern(&item, || { ClipInternData { clip_node_kind: ClipNodeKind::Rectangle, } }); let instance = SceneClipInstance { key: item, clip: ClipInstance::new(handle, spatial_node_index), }; self.clip_store.register_clip_template( new_node_id, space_and_clip.clip_id, &[instance], ); } pub fn add_rounded_rect_clip_node( &mut self, new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, clip: &ComplexClipRegion, current_offset: LayoutVector2D, ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); let snapped_region_rect = self.snap_rect( &clip.rect.translate(current_offset), spatial_node_index, ); let item = ClipItemKey { kind: ClipItemKeyKind::rounded_rect( snapped_region_rect, clip.radii, clip.mode, ), }; let handle = self .interners .clip .intern(&item, || { ClipInternData { clip_node_kind: ClipNodeKind::Complex, } }); let instance = SceneClipInstance { key: item, clip: ClipInstance::new(handle, spatial_node_index), }; self.clip_store.register_clip_template( new_node_id, space_and_clip.clip_id, &[instance], ); } pub fn add_clip_node( &mut self, new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, clip_region: ClipRegion, ) where I: IntoIterator { // Map the ClipId for the positioning node to a spatial node index. let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); let snapped_clip_rect = self.snap_rect( &clip_region.main, spatial_node_index, ); let mut instances: SmallVec<[SceneClipInstance; 4]> = SmallVec::new(); // Intern each clip item in this clip node, and add the interned // handle to a clip chain node, parented to form a chain. // TODO(gw): We could re-structure this to share some of the // interning and chaining code. // Build the clip sources from the supplied region. let item = ClipItemKey { kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), }; let handle = self .interners .clip .intern(&item, || { ClipInternData { clip_node_kind: ClipNodeKind::Rectangle, } }); instances.push( SceneClipInstance { key: item, clip: ClipInstance::new(handle, spatial_node_index), }, ); for region in clip_region.complex_clips { let snapped_region_rect = self.snap_rect(®ion.rect, spatial_node_index); let item = ClipItemKey { kind: ClipItemKeyKind::rounded_rect( snapped_region_rect, region.radii, region.mode, ), }; let handle = self .interners .clip .intern(&item, || { ClipInternData { clip_node_kind: ClipNodeKind::Complex, } }); instances.push( SceneClipInstance { key: item, clip: ClipInstance::new(handle, spatial_node_index), }, ); } self.clip_store.register_clip_template( new_node_id, space_and_clip.clip_id, &instances, ); } pub fn add_scroll_frame( &mut self, new_node_id: SpatialId, parent_node_index: SpatialNodeIndex, external_id: ExternalScrollId, pipeline_id: PipelineId, frame_rect: &LayoutRect, content_size: &LayoutSize, scroll_sensitivity: ScrollSensitivity, frame_kind: ScrollFrameKind, external_scroll_offset: LayoutVector2D, ) -> SpatialNodeIndex { let node_index = self.spatial_tree.add_scroll_frame( parent_node_index, external_id, pipeline_id, frame_rect, content_size, scroll_sensitivity, frame_kind, external_scroll_offset, ); self.id_to_index_mapper.add_spatial_node(new_node_id, node_index); node_index } pub fn push_shadow( &mut self, shadow: Shadow, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, should_inflate: bool, ) { // Store this shadow in the pending list, for processing // during pop_all_shadows. self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow { shadow, spatial_node_index, clip_chain_id, should_inflate, })); } pub fn pop_all_shadows( &mut self, ) { assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present"); let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new()); // // The pending_shadow_items queue contains a list of shadows and primitives // that were pushed during the active shadow context. To process these, we: // // Iterate the list, popping an item from the front each iteration. // // If the item is a shadow: // - Create a shadow picture primitive. // - Add *any* primitives that remain in the item list to this shadow. // If the item is a primitive: // - Add that primitive as a normal item (if alpha > 0) // while let Some(item) = items.pop_front() { match item { ShadowItem::Shadow(pending_shadow) => { // 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 std_deviation = pending_shadow.shadow.blur_radius * 0.5; // Add any primitives that come after this shadow in the item // list to this shadow. let mut prim_list = PrimitiveList::empty(); let blur_filter = Filter::Blur(std_deviation, std_deviation); let blur_is_noop = blur_filter.is_noop(); for item in &items { let (instance, info, spatial_node_index) = match item { ShadowItem::Image(ref pending_image) => { self.create_shadow_prim( &pending_shadow, pending_image, blur_is_noop, ) } ShadowItem::LineDecoration(ref pending_line_dec) => { self.create_shadow_prim( &pending_shadow, pending_line_dec, blur_is_noop, ) } ShadowItem::NormalBorder(ref pending_border) => { self.create_shadow_prim( &pending_shadow, pending_border, blur_is_noop, ) } ShadowItem::Primitive(ref pending_primitive) => { self.create_shadow_prim( &pending_shadow, pending_primitive, blur_is_noop, ) } ShadowItem::TextRun(ref pending_text_run) => { self.create_shadow_prim( &pending_shadow, pending_text_run, blur_is_noop, ) } _ => { continue; } }; if blur_is_noop { self.add_primitive_to_draw_list( instance, info.rect, spatial_node_index, info.flags, ); } else { prim_list.add_prim( instance, info.rect, spatial_node_index, info.flags, ); } } // No point in adding a shadow here if there were no primitives // added to the shadow. if !prim_list.is_empty() { // Create a picture that the shadow primitives will be added to. If the // blur radius is 0, the code in Picture::prepare_for_render will // detect this and mark the picture to be drawn directly into the // parent picture, which avoids an intermediate surface and blur. let blur_filter = Filter::Blur(std_deviation, std_deviation); assert!(!blur_filter.is_noop()); let composite_mode = Some(PictureCompositeMode::Filter(blur_filter)); let composite_mode_key = composite_mode.clone().into(); // Pass through configuration information about whether WR should // do the bounding rect inflation for text shadows. let options = PictureOptions { inflate_if_required: pending_shadow.should_inflate, }; // Create the primitive to draw the shadow picture into the scene. let shadow_pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( composite_mode, Picture3DContext::Out, false, PrimitiveFlags::IS_BACKFACE_VISIBLE, prim_list, pending_shadow.spatial_node_index, options, )) ); let shadow_pic_key = PictureKey::new( Picture { composite_mode_key }, ); let shadow_prim_data_handle = self.interners .picture .intern(&shadow_pic_key, || ()); let shadow_prim_instance = PrimitiveInstance::new( LayoutRect::max_rect(), PrimitiveInstanceKind::Picture { data_handle: shadow_prim_data_handle, pic_index: shadow_pic_index, segment_instance_index: SegmentInstanceIndex::INVALID, }, pending_shadow.clip_chain_id, ); // Add the shadow primitive. This must be done before pushing this // picture on to the shadow stack, to avoid infinite recursion! self.add_primitive_to_draw_list( shadow_prim_instance, LayoutRect::zero(), pending_shadow.spatial_node_index, PrimitiveFlags::IS_BACKFACE_VISIBLE, ); } } ShadowItem::Image(pending_image) => { self.add_shadow_prim_to_draw_list( pending_image, ) }, ShadowItem::LineDecoration(pending_line_dec) => { self.add_shadow_prim_to_draw_list( pending_line_dec, ) }, ShadowItem::NormalBorder(pending_border) => { self.add_shadow_prim_to_draw_list( pending_border, ) }, ShadowItem::Primitive(pending_primitive) => { self.add_shadow_prim_to_draw_list( pending_primitive, ) }, ShadowItem::TextRun(pending_text_run) => { self.add_shadow_prim_to_draw_list( pending_text_run, ) }, } } debug_assert!(items.is_empty()); self.pending_shadow_items = items; } fn create_shadow_prim

( &mut self, pending_shadow: &PendingShadow, pending_primitive: &PendingPrimitive

, blur_is_noop: bool, ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex) where P: InternablePrimitive + CreateShadow, Interners: AsMut>, { // Offset the local rect and clip rect by the shadow offset. The pending // primitive has already been snapped, but we will need to snap the // shadow after translation. We don't need to worry about the size // changing because the shadow has the same raster space as the // primitive, and thus we know the size is already rounded. let mut info = pending_primitive.info.clone(); info.rect = self.snap_rect( &info.rect.translate(pending_shadow.shadow.offset), pending_primitive.spatial_node_index, ); info.clip_rect = self.snap_rect( &info.clip_rect.translate(pending_shadow.shadow.offset), pending_primitive.spatial_node_index, ); // Construct and add a primitive for the given shadow. let shadow_prim_instance = self.create_primitive( &info, pending_primitive.spatial_node_index, pending_primitive.clip_chain_id, pending_primitive.prim.create_shadow( &pending_shadow.shadow, blur_is_noop, self.raster_space_stack.last().cloned().unwrap(), ), ); (shadow_prim_instance, info, pending_primitive.spatial_node_index) } fn add_shadow_prim_to_draw_list

( &mut self, pending_primitive: PendingPrimitive

, ) where P: InternablePrimitive + IsVisible, Interners: AsMut>, { // For a normal primitive, if it has alpha > 0, then we add this // as a normal primitive to the parent picture. if pending_primitive.prim.is_visible() { self.add_prim_to_draw_list( &pending_primitive.info, pending_primitive.spatial_node_index, pending_primitive.clip_chain_id, pending_primitive.prim, ); } } #[cfg(debug_assertions)] fn register_chase_primitive_by_rect( &mut self, rect: &LayoutRect, prim_instance: &PrimitiveInstance, ) { if ChasePrimitive::LocalRect(*rect) == self.config.chase_primitive { println!("Chasing {:?} by local rect", prim_instance.id); register_prim_chase_id(prim_instance.id); } } #[cfg(not(debug_assertions))] fn register_chase_primitive_by_rect( &mut self, _rect: &LayoutRect, _prim_instance: &PrimitiveInstance, ) { } pub fn add_clear_rectangle( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, ) { self.add_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), PrimitiveKeyKind::Clear, ); } pub fn add_line( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, wavy_line_thickness: f32, orientation: LineOrientation, color: ColorF, style: LineStyle, ) { // For line decorations, we can construct the render task cache key // here during scene building, since it doesn't depend on device // pixel ratio or transform. let mut info = info.clone(); let size = get_line_decoration_size( &info.rect.size, orientation, style, wavy_line_thickness, ); let cache_key = size.map(|size| { // If dotted, adjust the clip rect to ensure we don't draw a final // partial dot. if style == LineStyle::Dotted { let clip_size = match orientation { LineOrientation::Horizontal => { LayoutSize::new( size.width * (info.rect.size.width / size.width).floor(), info.rect.size.height, ) } LineOrientation::Vertical => { LayoutSize::new( info.rect.size.width, size.height * (info.rect.size.height / size.height).floor(), ) } }; let clip_rect = LayoutRect::new( info.rect.origin, clip_size, ); info.clip_rect = clip_rect .intersection(&info.clip_rect) .unwrap_or_else(LayoutRect::zero); } LineDecorationCacheKey { style, orientation, wavy_line_thickness: Au::from_f32_px(wavy_line_thickness), size: size.to_au(), } }); self.add_primitive( spatial_node_index, clip_chain_id, &info, Vec::new(), LineDecoration { cache_key, color: color.into(), }, ); } pub fn add_border( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, border_item: &BorderDisplayItem, gradient_stops: ItemRange, ) { match border_item.details { BorderDetails::NinePatch(ref border) => { let nine_patch = NinePatchDescriptor { width: border.width, height: border.height, slice: border.slice, fill: border.fill, repeat_horizontal: border.repeat_horizontal, repeat_vertical: border.repeat_vertical, outset: border.outset.into(), widths: border_item.widths.into(), }; match border.source { NinePatchBorderSource::Image(image_key) => { let prim = ImageBorder { request: ImageRequest { key: image_key, rendering: ImageRendering::Auto, tile: None, }, nine_patch, }; self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), prim, ); } NinePatchBorderSource::Gradient(gradient) => { let prim = match self.create_linear_gradient_prim( &info, gradient.start_point, gradient.end_point, gradient_stops, gradient.extend_mode, LayoutSize::new(border.height as f32, border.width as f32), LayoutSize::zero(), Some(Box::new(nine_patch)), ) { Some(prim) => prim, None => return, }; self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), prim, ); } NinePatchBorderSource::RadialGradient(gradient) => { let prim = self.create_radial_gradient_prim( &info, gradient.center, gradient.start_offset * gradient.radius.width, gradient.end_offset * gradient.radius.width, gradient.radius.width / gradient.radius.height, gradient_stops, gradient.extend_mode, LayoutSize::new(border.height as f32, border.width as f32), LayoutSize::zero(), Some(Box::new(nine_patch)), ); self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), prim, ); } NinePatchBorderSource::ConicGradient(gradient) => { let prim = self.create_conic_gradient_prim( &info, gradient.center, gradient.angle, gradient.start_offset, gradient.end_offset, gradient_stops, gradient.extend_mode, LayoutSize::new(border.height as f32, border.width as f32), LayoutSize::zero(), Some(Box::new(nine_patch)), ); self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), prim, ); } }; } BorderDetails::Normal(ref border) => { self.add_normal_border( info, border, border_item.widths, spatial_node_index, clip_chain_id, ); } } } pub fn create_linear_gradient_prim( &mut self, info: &LayoutPrimitiveInfo, start_point: LayoutPoint, end_point: LayoutPoint, stops: ItemRange, extend_mode: ExtendMode, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, nine_patch: Option>, ) -> Option { let mut prim_rect = info.rect; simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); let mut max_alpha: f32 = 0.0; let stops = stops.iter().map(|stop| { max_alpha = max_alpha.max(stop.color.a); GradientStopKey { offset: stop.offset, color: stop.color.into(), } }).collect(); // If all the stops have no alpha, then this // gradient can't contribute to the scene. if max_alpha <= 0.0 { return None; } // Try to ensure that if the gradient is specified in reverse, then so long as the stops // are also supplied in reverse that the rendered result will be equivalent. To do this, // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so // just designate the reference orientation as start < end. Aligned gradient rendering // manages to produce the same result regardless of orientation, so don't worry about // reversing in that case. let reverse_stops = start_point.x > end_point.x || (start_point.x == end_point.x && start_point.y > end_point.y); // To get reftests exactly matching with reverse start/end // points, it's necessary to reverse the gradient // line in some cases. let (sp, ep) = if reverse_stops { (end_point, start_point) } else { (start_point, end_point) }; Some(LinearGradient { extend_mode, start_point: sp.into(), end_point: ep.into(), stretch_size: stretch_size.into(), tile_spacing: tile_spacing.into(), stops, reverse_stops, nine_patch, }) } pub fn create_radial_gradient_prim( &mut self, info: &LayoutPrimitiveInfo, center: LayoutPoint, start_radius: f32, end_radius: f32, ratio_xy: f32, stops: ItemRange, extend_mode: ExtendMode, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, nine_patch: Option>, ) -> RadialGradient { let mut prim_rect = info.rect; simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); let params = RadialGradientParams { start_radius, end_radius, ratio_xy, }; let stops = stops.iter().map(|stop| { GradientStopKey { offset: stop.offset, color: stop.color.into(), } }).collect(); RadialGradient { extend_mode, center: center.into(), params, stretch_size: stretch_size.into(), tile_spacing: tile_spacing.into(), nine_patch, stops, } } pub fn create_conic_gradient_prim( &mut self, info: &LayoutPrimitiveInfo, center: LayoutPoint, angle: f32, start_offset: f32, end_offset: f32, stops: ItemRange, extend_mode: ExtendMode, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, nine_patch: Option>, ) -> ConicGradient { let mut prim_rect = info.rect; simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); let stops = stops.iter().map(|stop| { GradientStopKey { offset: stop.offset, color: stop.color.into(), } }).collect(); ConicGradient { extend_mode, center: center.into(), params: ConicGradientParams { angle, start_offset, end_offset }, stretch_size: stretch_size.into(), tile_spacing: tile_spacing.into(), nine_patch, stops, } } pub fn add_text( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, prim_info: &LayoutPrimitiveInfo, font_instance_key: &FontInstanceKey, text_color: &ColorF, glyph_range: ItemRange, glyph_options: Option, ) { let offset = self.current_offset(spatial_node_index); let text_run = { let instance_map = self.font_instances.lock().unwrap(); let font_instance = match instance_map.get(font_instance_key) { Some(instance) => instance, None => { warn!("Unknown font instance key"); debug!("key={:?}", font_instance_key); return; } }; // Trivial early out checks if font_instance.size <= FontSize::zero() { return; } // TODO(gw): Use a proper algorithm to select // whether this item should be rendered with // subpixel AA! let mut render_mode = self.config .default_font_render_mode .limit_by(font_instance.render_mode); let mut flags = font_instance.flags; if let Some(options) = glyph_options { render_mode = render_mode.limit_by(options.render_mode); flags |= options.flags; } let font = FontInstance::new( Arc::clone(font_instance), (*text_color).into(), render_mode, flags, ); // TODO(gw): It'd be nice not to have to allocate here for creating // the primitive key, when the common case is that the // hash will match and we won't end up creating a new // primitive template. let prim_offset = prim_info.rect.origin.to_vector() - offset; let glyphs = glyph_range .iter() .map(|glyph| { GlyphInstance { index: glyph.index, point: glyph.point - prim_offset, } }) .collect(); // Query the current requested raster space (stack handled by push/pop // stacking context). let requested_raster_space = self.raster_space_stack .last() .cloned() .unwrap(); TextRun { glyphs: Arc::new(glyphs), font, shadow: false, requested_raster_space, } }; self.add_primitive( spatial_node_index, clip_chain_id, prim_info, Vec::new(), text_run, ); } pub fn add_image( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, image_key: ImageKey, image_rendering: ImageRendering, alpha_type: AlphaType, color: ColorF, ) { let mut prim_rect = info.rect; simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); let info = LayoutPrimitiveInfo { rect: prim_rect, .. *info }; self.add_primitive( spatial_node_index, clip_chain_id, &info, Vec::new(), Image { key: image_key, tile_spacing: tile_spacing.into(), stretch_size: stretch_size.into(), color: color.into(), image_rendering, alpha_type, }, ); } pub fn add_yuv_image( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, yuv_data: YuvData, color_depth: ColorDepth, color_space: YuvColorSpace, color_range: ColorRange, image_rendering: ImageRendering, ) { let format = yuv_data.get_format(); let yuv_key = match yuv_data { YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY], YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2], YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY], }; self.add_nonshadowable_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), YuvImage { color_depth, yuv_key, format, color_space, color_range, image_rendering, }, ); } fn add_primitive_instance_to_3d_root( &mut self, prim: ExtendedPrimitiveInstance, ) { // find the 3D root and append to the children list for sc in self.sc_stack.iter_mut().rev() { match sc.context_3d { Picture3DContext::In { root_data: Some(ref mut prims), .. } => { prims.push(prim); break; } Picture3DContext::In { .. } => {} Picture3DContext::Out => panic!("Unable to find 3D root"), } } } pub fn add_backdrop_filter( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, filters: Vec, filter_datas: Vec, filter_primitives: Vec, ) { let mut backdrop_pic_index = match self.cut_backdrop_picture() { // Backdrop contains no content, so no need to add backdrop-filter None => return, Some(backdrop_pic_index) => backdrop_pic_index, }; let backdrop_spatial_node_index = self.prim_store.pictures[backdrop_pic_index.0].spatial_node_index; let mut instance = self.create_primitive( info, // TODO(cbrewster): This is a bit of a hack to help figure out the correct sizing of the backdrop // region. By makings sure to include this, the clip chain instance computes the correct clip rect, // but we don't actually apply the filtered backdrop clip yet (this is done to the last instance in // the filter chain below). backdrop_spatial_node_index, clip_chain_id, Backdrop { pic_index: backdrop_pic_index, spatial_node_index, border_rect: info.rect.into(), }, ); // We will append the filtered backdrop to the backdrop root, but we need to // make sure all clips between the current stacking context and backdrop root // are taken into account. So we wrap the backdrop filter instance with a picture with // a clip for each stacking context. for stacking_context in self.sc_stack.iter().rev().take_while(|sc| !sc.is_backdrop_root) { let clip_chain_id = stacking_context.clip_chain_id; let prim_flags = stacking_context.prim_flags; let composite_mode = None; let mut prim_list = PrimitiveList::empty(); prim_list.add_prim( instance, LayoutRect::zero(), backdrop_spatial_node_index, prim_flags, ); backdrop_pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( composite_mode.clone(), Picture3DContext::Out, true, prim_flags, prim_list, backdrop_spatial_node_index, PictureOptions { inflate_if_required: false, }, )) ); instance = create_prim_instance( backdrop_pic_index, composite_mode.into(), clip_chain_id, &mut self.interners, ); } let mut source = PictureChainBuilder::from_instance( instance, info.flags, backdrop_spatial_node_index, ); source = self.wrap_prim_with_filters( source, filters, filter_primitives, filter_datas, false, ); // Apply filters from all stacking contexts up to, but not including the backdrop root. // Gecko pushes separate stacking contexts for filters and opacity, // so we must iterate through multiple stacking contexts to find all effects // that need to be applied to the filtered backdrop. let backdrop_root_pos = self.sc_stack.iter().rposition(|sc| sc.is_backdrop_root).expect("no backdrop root?"); for i in ((backdrop_root_pos + 1)..self.sc_stack.len()).rev() { let stacking_context = &self.sc_stack[i]; let filters = stacking_context.composite_ops.filters.clone(); let filter_primitives = stacking_context.composite_ops.filter_primitives.clone(); let filter_datas = stacking_context.composite_ops.filter_datas.clone(); source = self.wrap_prim_with_filters( source, filters, filter_primitives, filter_datas, false, ); } let filtered_instance = source.finalize( clip_chain_id, &mut self.interners, &mut self.prim_store, ); self.sc_stack .iter_mut() .rev() .find(|sc| sc.is_backdrop_root) .unwrap() .prim_list .add_prim( filtered_instance, LayoutRect::zero(), backdrop_spatial_node_index, info.flags, ); } pub fn cut_backdrop_picture(&mut self) -> Option { let mut flattened_items = None; let mut backdrop_root = None; let mut spatial_node_index = SpatialNodeIndex::INVALID; let mut prim_flags = PrimitiveFlags::default(); for sc in self.sc_stack.iter_mut().rev() { // Add child contents to parent stacking context if let Some((_, flattened_instance)) = flattened_items.take() { sc.prim_list.add_prim( flattened_instance, LayoutRect::zero(), spatial_node_index, prim_flags, ); } flattened_items = sc.cut_item_sequence( &mut self.prim_store, &mut self.interners, None, Picture3DContext::Out, ); spatial_node_index = sc.spatial_node_index; prim_flags = sc.prim_flags; if sc.is_backdrop_root { backdrop_root = Some(sc); break; } } let (pic_index, instance) = flattened_items?; self.prim_store.pictures[pic_index.0].requested_composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP)); backdrop_root.expect("no backdrop root found") .prim_list .add_prim( instance, LayoutRect::zero(), spatial_node_index, prim_flags, ); Some(pic_index) } #[must_use] fn wrap_prim_with_filters( &mut self, mut source: PictureChainBuilder, mut filter_ops: Vec, mut filter_primitives: Vec, filter_datas: Vec, inflate_if_required: bool, ) -> PictureChainBuilder { // TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will // happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives. // At some point, these two should be combined and CSS filters should be expressed in terms of SVG filters. assert!(filter_ops.is_empty() || filter_primitives.is_empty(), "Filter ops and filter primitives are not allowed on the same stacking context."); // For each filter, create a new image with that composite mode. let mut current_filter_data_index = 0; for filter in &mut filter_ops { let composite_mode = match filter { Filter::ComponentTransfer => { let filter_data = &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 { let filter_data_key = SFilterDataKey { data: SFilterData { r_func: SFilterDataComponent::from_functype_values( filter_data.func_r_type, &filter_data.r_values), g_func: SFilterDataComponent::from_functype_values( filter_data.func_g_type, &filter_data.g_values), b_func: SFilterDataComponent::from_functype_values( filter_data.func_b_type, &filter_data.b_values), a_func: SFilterDataComponent::from_functype_values( filter_data.func_a_type, &filter_data.a_values), }, }; let handle = self.interners .filter_data .intern(&filter_data_key, || ()); PictureCompositeMode::ComponentTransferFilter(handle) } } _ => { if filter.is_noop() { continue; } else { PictureCompositeMode::Filter(filter.clone()) } } }; source = source.add_picture( composite_mode, Picture3DContext::Out, PictureOptions { inflate_if_required }, &mut self.interners, &mut self.prim_store, ); } if !filter_primitives.is_empty() { let filter_datas = filter_datas.iter() .map(|filter_data| filter_data.sanitize()) .map(|filter_data| { SFilterData { r_func: SFilterDataComponent::from_functype_values( filter_data.func_r_type, &filter_data.r_values), g_func: SFilterDataComponent::from_functype_values( filter_data.func_g_type, &filter_data.g_values), b_func: SFilterDataComponent::from_functype_values( filter_data.func_b_type, &filter_data.b_values), a_func: SFilterDataComponent::from_functype_values( filter_data.func_a_type, &filter_data.a_values), } }) .collect(); // Sanitize filter inputs for primitive in &mut filter_primitives { primitive.sanitize(); } let composite_mode = PictureCompositeMode::SvgFilter( filter_primitives, filter_datas, ); source = source.add_picture( composite_mode, Picture3DContext::Out, PictureOptions { inflate_if_required }, &mut self.interners, &mut self.prim_store, ); } source } } pub trait CreateShadow { fn create_shadow( &self, shadow: &Shadow, blur_is_noop: bool, current_raster_space: RasterSpace, ) -> Self; } pub trait IsVisible { fn is_visible(&self) -> bool; } /// A primitive instance + some extra information about the primitive. This is /// stored when constructing 3d rendering contexts, which involve cutting /// primitive lists. struct ExtendedPrimitiveInstance { instance: PrimitiveInstance, spatial_node_index: SpatialNodeIndex, flags: PrimitiveFlags, } /// Internal tracking information about the currently pushed stacking context. /// Used to track what operations need to happen when a stacking context is popped. struct StackingContextInfo { /// If true, pop an entry from the clip root stack. pop_clip_root: bool, /// If true, pop and entry from the containing block stack. pop_containing_block: bool, /// If true, pop an entry from the flattened stacking context stack. pop_stacking_context: bool, } /// Properties of a stacking context that are maintained /// during creation of the scene. These structures are /// not persisted after the initial scene build. struct FlattenedStackingContext { /// The list of primitive instances added to this stacking context. prim_list: PrimitiveList, /// Primitive instance flags for compositing this stacking context prim_flags: PrimitiveFlags, /// The positioning node for this stacking context spatial_node_index: SpatialNodeIndex, /// The clip chain for this stacking context clip_chain_id: ClipChainId, /// The list of filters / mix-blend-mode for this /// stacking context. composite_ops: CompositeOps, /// Bitfield of reasons this stacking context needs to /// be an offscreen surface. blit_reason: BlitReason, /// CSS transform-style property. transform_style: TransformStyle, /// Defines the relationship to a preserve-3D hiearachy. context_3d: Picture3DContext, /// True if this stacking context is a backdrop root. is_backdrop_root: bool, /// True if this stacking context is redundant (i.e. doesn't require a surface) is_redundant: bool, } impl FlattenedStackingContext { /// Return true if the stacking context has a valid preserve-3d property pub fn is_3d(&self) -> bool { self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty() } /// Return true if the stacking context isn't needed. pub fn is_redundant( sc_flags: StackingContextFlags, context_3d: &Picture3DContext, composite_ops: &CompositeOps, prim_flags: PrimitiveFlags, blit_reason: BlitReason, parent: Option<&FlattenedStackingContext>, ) -> bool { // If this is a backdrop or blend container, it's needed if sc_flags.intersects(StackingContextFlags::IS_BACKDROP_ROOT | StackingContextFlags::IS_BLEND_CONTAINER) { return false; } // Any 3d context is required if let Picture3DContext::In { .. } = context_3d { return false; } // If any filters are present that affect the output if composite_ops.has_valid_filters() { return false; } // We can skip mix-blend modes if they are the first primitive in a stacking context, // see pop_stacking_context for a full explanation. if composite_ops.mix_blend_mode.is_some() { if let Some(parent) = parent { if !parent.prim_list.is_empty() { return false; } } } // If need to isolate in surface due to clipping / mix-blend-mode if !blit_reason.is_empty() { return false; } // If this stacking context is a scrollbar, retain it so it can form a picture cache slice if prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER) { return false; } // It is redundant! true } /// Cut the sequence of the immediate children recorded so far and generate a picture from them. pub fn cut_item_sequence( &mut self, prim_store: &mut PrimitiveStore, interners: &mut Interners, composite_mode: Option, flat_items_context_3d: Picture3DContext, ) -> Option<(PictureIndex, PrimitiveInstance)> { if self.prim_list.is_empty() { return None } let pic_index = PictureIndex(prim_store.pictures .alloc() .init(PicturePrimitive::new_image( composite_mode.clone(), flat_items_context_3d, true, self.prim_flags, mem::replace(&mut self.prim_list, PrimitiveList::empty()), self.spatial_node_index, PictureOptions::default(), )) ); let prim_instance = create_prim_instance( pic_index, composite_mode.into(), self.clip_chain_id, interners, ); Some((pic_index, prim_instance)) } } /// A primitive that is added while a shadow context is /// active is stored as a pending primitive and only /// added to pictures during pop_all_shadows. pub struct PendingPrimitive { spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: LayoutPrimitiveInfo, prim: T, } /// As shadows are pushed, they are stored as pending /// shadows, and handled at once during pop_all_shadows. pub struct PendingShadow { shadow: Shadow, should_inflate: bool, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, } pub enum ShadowItem { Shadow(PendingShadow), Image(PendingPrimitive), LineDecoration(PendingPrimitive), NormalBorder(PendingPrimitive), Primitive(PendingPrimitive), TextRun(PendingPrimitive), } impl From> for ShadowItem { fn from(image: PendingPrimitive) -> Self { ShadowItem::Image(image) } } impl From> for ShadowItem { fn from(line_dec: PendingPrimitive) -> Self { ShadowItem::LineDecoration(line_dec) } } impl From> for ShadowItem { fn from(border: PendingPrimitive) -> Self { ShadowItem::NormalBorder(border) } } impl From> for ShadowItem { fn from(container: PendingPrimitive) -> Self { ShadowItem::Primitive(container) } } impl From> for ShadowItem { fn from(text_run: PendingPrimitive) -> Self { ShadowItem::TextRun(text_run) } } fn create_prim_instance( pic_index: PictureIndex, composite_mode_key: PictureCompositeKey, clip_chain_id: ClipChainId, interners: &mut Interners, ) -> PrimitiveInstance { let pic_key = PictureKey::new( Picture { composite_mode_key }, ); let data_handle = interners .picture .intern(&pic_key, || ()); PrimitiveInstance::new( LayoutRect::max_rect(), PrimitiveInstanceKind::Picture { data_handle, pic_index, segment_instance_index: SegmentInstanceIndex::INVALID, }, clip_chain_id, ) } fn filter_ops_for_compositing( input_filters: ItemRange, ) -> Vec { // TODO(gw): Now that we resolve these later on, // we could probably make it a bit // more efficient than cloning these here. input_filters.iter().map(|filter| filter.into()).collect() } fn filter_datas_for_compositing( input_filter_datas: &[TempFilterData], ) -> Vec { // TODO(gw): Now that we resolve these later on, // we could probably make it a bit // more efficient than cloning these here. let mut filter_datas = vec![]; for temp_filter_data in input_filter_datas { let func_types : Vec = temp_filter_data.func_types.iter().collect(); debug_assert!(func_types.len() == 4); filter_datas.push( FilterData { func_r_type: func_types[0], r_values: temp_filter_data.r_values.iter().collect(), func_g_type: func_types[1], g_values: temp_filter_data.g_values.iter().collect(), func_b_type: func_types[2], b_values: temp_filter_data.b_values.iter().collect(), func_a_type: func_types[3], a_values: temp_filter_data.a_values.iter().collect(), }); } filter_datas } fn filter_primitives_for_compositing( input_filter_primitives: ItemRange, ) -> Vec { // Resolve these in the flattener? // TODO(gw): Now that we resolve these later on, // we could probably make it a bit // more efficient than cloning these here. input_filter_primitives.iter().map(|primitive| primitive).collect() } fn process_repeat_size( snapped_rect: &LayoutRect, unsnapped_rect: &LayoutRect, repeat_size: LayoutSize, ) -> LayoutSize { // FIXME(aosmond): The tile size is calculated based on several parameters // during display list building. It may produce a slightly different result // than the bounds due to floating point error accumulation, even though in // theory they should be the same. We do a fuzzy check here to paper over // that. It may make more sense to push the original parameters into scene // building and let it do a saner calculation with more information (e.g. // the snapped values). const EPSILON: f32 = 0.001; LayoutSize::new( if repeat_size.width.approx_eq_eps(&unsnapped_rect.size.width, &EPSILON) { snapped_rect.size.width } else { repeat_size.width }, if repeat_size.height.approx_eq_eps(&unsnapped_rect.size.height, &EPSILON) { snapped_rect.size.height } else { repeat_size.height }, ) }