diff options
Diffstat (limited to 'third_party/webrender/webrender/src/scene_building.rs')
-rw-r--r-- | third_party/webrender/webrender/src/scene_building.rs | 2657 |
1 files changed, 1347 insertions, 1310 deletions
diff --git a/third_party/webrender/webrender/src/scene_building.rs b/third_party/webrender/webrender/src/scene_building.rs index 44b75e6d847..bfc466640bc 100644 --- a/third_party/webrender/webrender/src/scene_building.rs +++ b/third_party/webrender/webrender/src/scene_building.rs @@ -2,86 +2,46 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//! # Scene building -//! -//! Scene building is the phase during which display lists, a representation built for -//! serialization, are turned into a scene, webrender's internal representation that is -//! suited for rendering frames. -//! -//! This phase is happening asynchronously on the scene builder thread. -//! -//! # General algorithm -//! -//! The important aspects of scene building are: -//! - Building up primitive lists (much of the cost of scene building goes here). -//! - Creating pictures for content that needs to be rendered into a surface, be it so that -//! filters can be applied or for caching purposes. -//! - Maintaining a temporary stack of stacking contexts to keep track of some of the -//! drawing states. -//! - Stitching multiple display lists which reference each other (without cycles) into -//! a single scene (see build_reference_frame). -//! - Interning, which detects when some of the retained state stays the same between display -//! lists. -//! -//! The scene builder linearly traverses the serialized display list which is naturally -//! ordered back-to-front, accumulating primitives in the top-most stacking context's -//! primitive list. -//! At the end of each stacking context (see pop_stacking_context), its primitive list is -//! either handed over to a picture if one is created, or it is concatenated into the parent -//! stacking context's primitive list. -//! -//! The flow of the algorithm is mostly linear except when handling: -//! - shadow stacks (see push_shadow and pop_all_shadows), -//! - backdrop filters (see add_backdrop_filter) -//! - use api::{AlphaType, BorderDetails, BorderDisplayItem, 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::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity}; +use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem, ImageMask}; use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData}; -use api::{ReferenceTransformBinding, Rotation, FillRule}; +use api::image_tiling::simplify_repeated_primitive; 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::clip::{PolygonDataHandle}; -use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex, StaticCoordinateSystemId}; +use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance}; +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::hit_test::{HitTestingItem, HitTestingScene}; use crate::intern::Interner; -use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter}; +use crate::internal_types::{FastHashMap, FastHashSet, 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::picture::{BlitReason, OrderedPictureChild, PrimitiveList, TileCacheInstance, ClusterFlags}; +use crate::prim_store::PrimitiveInstance; use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore}; use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex, PictureIndex}; -use crate::prim_store::PolygonKey; +use crate::prim_store::{register_prim_chase_id, get_line_decoration_size}; +use crate::prim_store::{SpaceSnapper}; use crate::prim_store::backdrop::Backdrop; use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; -use crate::prim_store::gradient::{ - GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, - ConicGradientParams, optimize_radial_gradient, apply_gradient_local_clip, - optimize_linear_gradient, -}; +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::line_dec::{LineDecoration, LineDecorationCacheKey}; 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::{Scene, 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::{f32, mem, usize, ops}; use std::collections::vec_deque::VecDeque; use std::sync::Arc; use crate::util::{MaxRect, VecHelper}; @@ -242,204 +202,31 @@ impl CompositeOps { 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<OrderedPictureChild>, - 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 + /// Slice created by a cluster that has ClusterFlags::SCROLLBAR_CONTAINER const IS_SCROLLBAR = 1; - /// Represents a mix-blend container (can't split out compositor surfaces in this slice) - const IS_BLEND_CONTAINER = 2; } } +/// Information about a set of primitive clusters that will form a picture cache slice. +struct Slice { + /// The spatial node root of the picture cache. If this is None, the slice + /// will not be cached and instead drawn directly to the parent surface. This + /// is a temporary measure until we enable caching all slices. + cache_scroll_root: Option<SpatialNodeIndex>, + /// List of primitive clusters that make up this slice + prim_list: PrimitiveList, + /// A list of clips that are shared by all primitives in the slice. These can be + /// filtered out and applied when the tile cache is composited rather than per-item. + shared_clips: Option<Vec<ClipInstance>>, + /// Various flags describing properties of this slice + pub flags: SliceFlags, +} + /// 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. @@ -450,6 +237,10 @@ pub struct SceneBuilder<'a> { /// The map of all font instances. font_instances: SharedFontInstanceMap, + /// A set of pipelines that the caller has requested be made available as + /// output textures. + output_pipelines: &'a FastHashSet<PipelineId>, + /// The data structure that converts between ClipId/SpatialId and the various /// index types that the SpatialTree uses. id_to_index_mapper: NodeIdToIndexMapper, @@ -457,12 +248,6 @@ pub struct SceneBuilder<'a> { /// A stack of stacking context properties. sc_stack: Vec<FlattenedStackingContext>, - /// Stack of spatial node indices forming containing block for 3d contexts - containing_block_stack: Vec<SpatialNodeIndex>, - - /// Stack of requested raster spaces for stacking contexts - raster_space_stack: Vec<RasterSpace>, - /// Maintains state for any currently active shadows pending_shadow_items: VecDeque<ShadowItem>, @@ -485,31 +270,36 @@ pub struct SceneBuilder<'a> { /// Reference to the set of data that is interned across display lists. interners: &'a mut Interners, + /// The root picture index for this builder. This is the picture + /// to start the culling phase from. + pub root_pic_index: PictureIndex, + /// 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, + /// If true, picture caching setup has already been completed. + picture_caching_initialized: bool, + /// The current recursion depth of iframes encountered. Used to restrict picture /// caching slices to only the top-level content frame. - iframe_size: Vec<LayoutSize>, + iframe_depth: usize, - /// Clip-chain for root iframes applied to any tile caches created within this iframe - root_iframe_clip: Option<ClipChainId>, + /// The number of picture cache slices that were created for content. + content_slice_count: usize, + + /// A set of any spatial nodes that are attached to either a picture cache + /// root, or a clip node on the picture cache primitive. These are used + /// to detect cases where picture caching must be disabled. This is mostly + /// a temporary workaround for some existing wrench tests. I don't think + /// Gecko ever produces picture cache slices with complex transforms, so + /// in future we should prevent this in the public API and remove this hack. + picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>, /// 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> { @@ -517,6 +307,7 @@ impl<'a> SceneBuilder<'a> { scene: &Scene, font_instances: SharedFontInstanceMap, view: &SceneView, + output_pipelines: &FastHashSet<PipelineId>, frame_builder_config: &FrameBuilderConfig, interners: &mut Interners, stats: &SceneStats, @@ -531,47 +322,82 @@ impl<'a> SceneBuilder<'a> { .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, + spatial_tree: SpatialTree::new(), font_instances, config: *frame_builder_config, + output_pipelines, 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(&stats.clip_store_stats), + clip_store: ClipStore::new(), interners, + root_pic_index: PictureIndex(0), rf_mapper: ReferenceFrameMapper::new(), external_scroll_mapper: ScrollOffsetMapper::new(), - iframe_size: Vec::new(), - root_iframe_clip: None, + picture_caching_initialized: false, + iframe_depth: 0, + content_slice_count: 0, + picture_cache_spatial_nodes: FastHashSet::default(), quality_settings: view.quality_settings, - tile_cache_builder: TileCacheBuilder::new(), - snap_to_device, }; - builder.build_all(&root_pipeline); + let device_pixel_scale = view.accumulated_scale_factor_for_snapping(); - // 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, - builder.interners, + builder.clip_store.register_clip_template( + ClipId::root(root_pipeline_id), + ClipId::root(root_pipeline_id), + &[], ); + builder.clip_store.push_clip_root( + Some(ClipId::root(root_pipeline_id)), + false, + ); + + builder.push_root( + root_pipeline_id, + &root_pipeline.viewport_size, + &root_pipeline.content_size, + device_pixel_scale, + ); + + // In order to ensure we have a single root stacking context for the + // entire display list, we push one here. Gecko _almost_ wraps its + // entire display list within a single stacking context, but sometimes + // appends a few extra items in AddWindowOverlayWebRenderCommands. We + // could fix it there, but it's easier and more robust for WebRender + // to just ensure there's a context on the stack whenever we append + // primitives (since otherwise we'd panic). + // + // Note that we don't do this for iframes, even if they're pipeline + // roots, because they should be entirely contained within a stacking + // context, and we probably wouldn't crash if they weren't. + builder.push_stacking_context( + root_pipeline.pipeline_id, + CompositeOps::default(), + TransformStyle::Flat, + /* prim_flags = */ PrimitiveFlags::IS_BACKFACE_VISIBLE, + ROOT_SPATIAL_NODE_INDEX, + None, + RasterSpace::Screen, + StackingContextFlags::IS_BACKDROP_ROOT, + device_pixel_scale, + ); + + builder.build_items( + &mut root_pipeline.display_list.iter(), + root_pipeline.pipeline_id, + ); + + builder.pop_stacking_context(); + builder.clip_store.pop_clip_root(); + + debug_assert!(builder.sc_stack.is_empty()); + BuiltScene { has_root_pipeline: scene.has_root_pipeline(), pipeline_epochs: scene.pipeline_epochs.clone(), @@ -581,9 +407,10 @@ impl<'a> SceneBuilder<'a> { spatial_tree: builder.spatial_tree, prim_store: builder.prim_store, clip_store: builder.clip_store, + root_pic_index: builder.root_pic_index, config: builder.config, - tile_cache_config, - tile_cache_pictures, + content_slice_count: builder.content_slice_count, + picture_cache_spatial_nodes: builder.picture_cache_spatial_nodes, } } @@ -609,250 +436,219 @@ impl<'a> SceneBuilder<'a> { 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>, + /// Figure out the shape of the display list, and wrap various primitive clusters + /// into tile cache primitive instances. + fn setup_picture_caching( + &mut self, + main_prim_list: &mut PrimitiveList, + ) { + if !self.config.global_enable_picture_caching { + return; } - 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(); + // Ensure that setup_picture_caching has executed + debug_assert!(self.picture_caching_initialized); - 'outer: while let Some(bc) = stack.pop() { - loop { - let item = match traversal.next() { - Some(item) => item, - None => break, - }; + // Unconditionally insert a marker to create a picture cache slice on the + // first cluster. This handles implicit picture caches, and also the common + // case, by allowing the root / background primitives to be cached in a slice. + if let Some(cluster) = main_prim_list.clusters.first_mut() { + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + } - 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; - } + // List of slices that have been found + let mut slices: Vec<Slice> = Vec::new(); + // Tracker for whether a new slice should be created + let mut create_slice = true; + // The clips found the last time we traversed a set of clip chains. Stored and cleared + // here to avoid constant allocations. + let mut prim_clips = Vec::new(); + // If true, the cache is out of date and needs to be rebuilt. + let mut update_shared_clips = true; + // The last prim clip chain we build prim_clips for. + let mut last_prim_clip_chain_id = ClipChainId::NONE; + + // Walk the supplied top level of clusters, slicing into slices as appropriate + for cluster in main_prim_list.clusters.drain(..) { + // Check if this cluster requires a new slice + create_slice |= cluster.flags.intersects( + ClusterFlags::CREATE_PICTURE_CACHE_PRE | ClusterFlags::IS_CLEAR_PRIMITIVE + ); - 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(), - ); + if create_slice { + let slice_flags = if cluster.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) { + SliceFlags::IS_SCROLLBAR + } else { + SliceFlags::empty() + }; + let slice = Slice { + cache_scroll_root: cluster.cache_scroll_root, + prim_list: PrimitiveList::empty(), + shared_clips: None, + flags: slice_flags + }; - 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, - bc.pipeline_id, - ); + // Open up clip chains on the stack on the new slice + slices.push(slice); + create_slice = false; + } - 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); + // Step through each prim instance, in order to collect shared clips for the slice. + for instance in &cluster.prim_instances { + // If the primitive clip chain is different, then we need to rebuild prim_clips. + update_shared_clips |= last_prim_clip_chain_id != instance.clip_chain_id; + last_prim_clip_chain_id = instance.clip_chain_id; + + if update_shared_clips { + prim_clips.clear(); + // Update the list of clips that apply to this primitive instance + add_clips( + instance.clip_chain_id, + &mut prim_clips, + &self.clip_store, + &self.interners, + ); + } - subtraversal.merge_debug_stats_from(&mut traversal); - traversal = subtraversal; - continue 'outer; + // If there are no shared clips set for this slice, the shared clips are just + // the current clips set. Otherwise, the shared clips are those that are + // in both the current shared list and the clips list for this primitive. + match slices.last_mut().unwrap().shared_clips { + Some(ref mut shared_clips) => { + if update_shared_clips { + shared_clips.retain(|h1: &ClipInstance| { + let uid = h1.handle.uid(); + prim_clips.iter().any(|h2| { + uid == h2.handle.uid() && + h1.spatial_node_index == h2.spatial_node_index + }) + }); + } } - 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); + ref mut shared_clips @ None => { + *shared_clips = Some(prim_clips.clone()); + } + } - PropertyBinding::Value(transform) - }, - }; + update_shared_clips = false; + } - 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(), - ); + // If this cluster creates a slice after, then note that for next cluster + create_slice |= cluster.flags.intersects( + ClusterFlags::CREATE_PICTURE_CACHE_POST | ClusterFlags::IS_CLEAR_PRIMITIVE + ); - self.rf_mapper.push_scope(); - let new_context = BuildContext { - pipeline_id: bc.pipeline_id, - kind: ContextKind::ReferenceFrame, - }; - stack.push(bc); - stack.push(new_context); + // Finally, add this cluster to the current slice + slices.last_mut().unwrap().prim_list.add_cluster(cluster); + } - 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, - }; + // Step through the slices, creating picture cache wrapper instances. + for (slice_index, slice) in slices.drain(..).enumerate() { + let background_color = if slice_index == 0 { + self.config.background_color + } else { + None + }; - // Get a clip-chain id for the root clip for this pipeline. We will - // add that as an unconditional clip to any tile cache created within - // this iframe. This ensures these clips are handled by the tile cache - // compositing code, which is more efficient and accurate than applying - // these clips individually to each primitive. - let clip_id = ClipId::root(info.pipeline_id); - let clip_chain_id = self.get_clip_chain(clip_id); - - // If this is a root iframe, force a new tile cache both before and after - // adding primitives for this iframe. - if self.iframe_size.is_empty() { - self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); - assert!(self.root_iframe_clip.is_none()); - self.root_iframe_clip = Some(clip_chain_id); - } + // If the cluster specifies a scroll root, use it. Otherwise, + // just cache assuming no scrolling takes place. Even if that's + // not true, we still get caching benefits for any changes that + // occur while not scrolling (such as animation, video etc); + let scroll_root = slice.cache_scroll_root.unwrap_or(ROOT_SPATIAL_NODE_INDEX); + + let instance = create_tile_cache( + slice_index, + slice.flags, + scroll_root, + slice.prim_list, + background_color, + slice.shared_clips.unwrap_or_else(Vec::new), + &mut self.interners, + &mut self.prim_store, + &mut self.clip_store, + &mut self.picture_cache_spatial_nodes, + &self.config, + ); - self.rf_mapper.push_scope(); - self.iframe_size.push(size); + main_prim_list.add_prim( + instance, + LayoutRect::zero(), + scroll_root, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + } + } - 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); - } - }; - } + fn build_items( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + ) { + loop { + let item = match traversal.next() { + Some(item) => item, + None => break, + }; - match bc.kind { - ContextKind::Root => {} - ContextKind::StackingContext { sc_info } => { - self.rf_mapper.pop_offset(); - self.pop_stacking_context(sc_info); + let subtraversal = match item.item() { + DisplayItem::PushStackingContext(ref info) => { + let space = self.get_space(info.spatial_id); + let mut subtraversal = item.sub_iter(); + self.build_stacking_context( + &mut subtraversal, + pipeline_id, + &info.stacking_context, + space, + info.origin, + item.filters(), + &item.filter_datas(), + item.filter_primitives(), + info.prim_flags, + ); + Some(subtraversal) } - ContextKind::ReferenceFrame => { - self.rf_mapper.pop_scope(); + DisplayItem::PushReferenceFrame(ref info) => { + let parent_space = self.get_space(info.parent_spatial_id); + let mut subtraversal = item.sub_iter(); + self.build_reference_frame( + &mut subtraversal, + pipeline_id, + parent_space, + info.origin, + &info.reference_frame, + ); + Some(subtraversal) } - 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() { - assert!(self.root_iframe_clip.is_some()); - self.root_iframe_clip = None; - self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); - } + DisplayItem::PopReferenceFrame | + DisplayItem::PopStackingContext => return, + _ => None, + }; - traversal = parent_traversal; - } + // If build_item created a sub-traversal, we need `traversal` to have the + // same state as the completed subtraversal, so we reinitialize it here. + if let Some(mut subtraversal) = subtraversal { + subtraversal.merge_debug_stats_from(traversal); + *traversal = subtraversal; + } else { + self.build_item(item, pipeline_id); } + } - // 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!(); + // 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( @@ -885,20 +681,18 @@ impl<'a> SceneBuilder<'a> { pipeline_id: PipelineId, ) { let current_offset = self.current_offset(parent_node_index); - let clip_rect = info.clip_rect.translate(current_offset); - + 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_rect; + let frame_rect = clip_region.main; let content_size = info.content_rect.size; - self.add_rect_clip_node( - info.clip_id, - &info.parent_space_and_clip, - &clip_rect, - ); + self.add_clip_node(info.clip_id, &info.parent_space_and_clip, clip_region); self.add_scroll_frame( info.scroll_frame_id, @@ -913,27 +707,107 @@ impl<'a> SceneBuilder<'a> { ); } - fn push_iframe( + fn build_reference_frame( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + parent_spatial_node: SpatialNodeIndex, + origin: LayoutPoint, + reference_frame: &ReferenceFrame, + ) { + profile_scope!("build_reference_frame"); + let current_offset = self.current_offset(parent_spatial_node); + self.push_reference_frame( + reference_frame.id, + Some(parent_spatial_node), + pipeline_id, + reference_frame.transform_style, + reference_frame.transform, + reference_frame.kind, + current_offset + origin.to_vector(), + ); + + self.rf_mapper.push_scope(); + self.build_items( + traversal, + pipeline_id, + ); + self.rf_mapper.pop_scope(); + } + + + fn build_stacking_context( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + stacking_context: &StackingContext, + spatial_node_index: SpatialNodeIndex, + origin: LayoutPoint, + filters: ItemRange<FilterOp>, + filter_datas: &[TempFilterData], + filter_primitives: ItemRange<FilterPrimitive>, + prim_flags: PrimitiveFlags, + ) { + profile_scope!("build_stacking_context"); + // Avoid doing unnecessary work for empty stacking contexts. + if traversal.current_stacking_context_empty() { + traversal.skip_current_stacking_context(); + return; + } + + let composition_operations = { + CompositeOps::new( + filter_ops_for_compositing(filters), + filter_datas_for_compositing(filter_datas), + filter_primitives_for_compositing(filter_primitives), + stacking_context.mix_blend_mode_for_compositing(), + ) + }; + + self.push_stacking_context( + pipeline_id, + composition_operations, + stacking_context.transform_style, + prim_flags, + spatial_node_index, + stacking_context.clip_id, + stacking_context.raster_space, + stacking_context.flags, + self.sc_stack.last().unwrap().snap_to_device.device_pixel_scale, + ); + + self.rf_mapper.push_offset(origin.to_vector()); + self.build_items( + traversal, + pipeline_id, + ); + self.rf_mapper.pop_offset(); + + self.pop_stacking_context(); + } + + fn build_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 + return }, }; let current_offset = self.current_offset(spatial_node_index); - let clip_rect = info.clip_rect.translate(current_offset); - - self.add_rect_clip_node( + self.add_clip_node( ClipId::root(iframe_pipeline_id), &info.space_and_clip, - &clip_rect, + ClipRegion::create_for_clip_node_with_local_clip( + &info.clip_rect, + ¤t_offset, + ), ); self.clip_store.push_clip_root( @@ -941,42 +815,52 @@ impl<'a> SceneBuilder<'a> { true, ); - let bounds = self.snap_rect( - &info.bounds.translate(current_offset), + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree, ); + let bounds = snap_to_device.snap_rect( + &info.bounds.translate(current_offset), + ); + + let content_size = snap_to_device.snap_size(&pipeline.content_size); + 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 { - is_2d_scale_translation: false, - should_snap: false - }, + 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), + Some(ExternalScrollId(0, iframe_pipeline_id)), iframe_pipeline_id, &iframe_rect, - &bounds.size, + &content_size, ScrollSensitivity::ScriptAndInputEvents, - ScrollFrameKind::PipelineRoot { - is_root_pipeline, - }, + ScrollFrameKind::PipelineRoot, LayoutVector2D::zero(), ); - Some((bounds.size, pipeline.display_list.iter())) + self.rf_mapper.push_scope(); + self.iframe_depth += 1; + + self.build_items( + &mut pipeline.display_list.iter(), + pipeline.pipeline_id, + ); + self.iframe_depth -= 1; + self.rf_mapper.pop_scope(); + + self.clip_store.pop_clip_root(); } fn get_space( @@ -1003,28 +887,29 @@ impl<'a> SceneBuilder<'a> { 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, + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree ); + let unsnapped_clip_rect = common.clip_rect.translate(current_offset); + let clip_rect = snap_to_device.snap_rect(&unsnapped_clip_rect); + 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, - ) + snap_to_device.snap_rect(&bounds) }); let layout = LayoutPrimitiveInfo { rect, clip_rect, flags: common.flags, + hit_info: common.hit_info, }; (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id) @@ -1046,11 +931,12 @@ impl<'a> SceneBuilder<'a> { rect: &LayoutRect, target_spatial_node: SpatialNodeIndex, ) -> LayoutRect { - self.snap_to_device.set_target_spatial_node( + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( target_spatial_node, &self.spatial_tree ); - self.snap_to_device.snap_rect(rect) + snap_to_device.snap_rect(rect) } fn build_item<'b>( @@ -1060,8 +946,6 @@ impl<'a> SceneBuilder<'a> { ) { 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, @@ -1080,8 +964,6 @@ impl<'a> SceneBuilder<'a> { ); } 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, @@ -1106,8 +988,6 @@ impl<'a> SceneBuilder<'a> { ); } 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, @@ -1125,8 +1005,6 @@ impl<'a> SceneBuilder<'a> { ); } 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 @@ -1149,46 +1027,32 @@ impl<'a> SceneBuilder<'a> { ); } 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( + self.add_solid_rectangle( spatial_node_index, clip_chain_id, &layout, - Vec::new(), - PrimitiveKeyKind::Rectangle { - color: info.color.into(), - }, + info.color, ); } 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( + let (layout, _, spatial_node_index, clip_chain_id) = 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, + self.add_solid_rectangle( spatial_node_index, - info.common.clip_id, - info.tag, + clip_chain_id, + &layout, + PropertyBinding::Value(ColorF::TRANSPARENT), ); } 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, @@ -1201,8 +1065,6 @@ impl<'a> SceneBuilder<'a> { ); } 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, @@ -1219,167 +1081,71 @@ impl<'a> SceneBuilder<'a> { ); } DisplayItem::Gradient(ref info) => { - profile_scope!("gradient"); - - if !info.gradient.is_valid() { - return; - } - - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); - let mut tile_size = process_repeat_size( + let tile_size = process_repeat_size( &layout.rect, &unsnapped_rect, info.tile_size, ); - let mut stops = read_gradient_stops(item.gradient_stops()); - let mut start = info.gradient.start_point; - let mut end = info.gradient.end_point; - let flags = layout.flags; - - let optimized = optimize_linear_gradient( - &mut layout.rect, - &mut tile_size, - info.tile_spacing, - &layout.clip_rect, - &mut start, - &mut end, + 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, - &mut stops, - &mut |rect, start, end, stops| { - let layout = LayoutPrimitiveInfo { rect: *rect, clip_rect: *rect, flags }; - if let Some(prim_key_kind) = self.create_linear_gradient_prim( - &layout, - start, - end, - stops.to_vec(), - ExtendMode::Clamp, - rect.size, - LayoutSize::zero(), - None, - ) { - self.add_nonshadowable_primitive( - spatial_node_index, - clip_chain_id, - &layout, - Vec::new(), - prim_key_kind, - ); - } - } - ); - - if !optimized && !tile_size.ceil().is_empty() { - if let Some(prim_key_kind) = self.create_linear_gradient_prim( + tile_size, + info.tile_spacing, + None, + ) { + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, &layout, - start, - end, - 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, - ); - } + Vec::new(), + prim_key_kind, + ); } } DisplayItem::RadialGradient(ref info) => { - profile_scope!("radial"); - - if !info.gradient.is_valid() { - return; - } - - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); - let mut center = info.gradient.center; - - let stops = read_gradient_stops(item.gradient_stops()); - - let mut tile_size = process_repeat_size( + let tile_size = process_repeat_size( &layout.rect, &unsnapped_rect, info.tile_size, ); - let mut prim_rect = layout.rect; - let mut tile_spacing = info.tile_spacing; - optimize_radial_gradient( - &mut prim_rect, - &mut tile_size, - &mut center, - &mut tile_spacing, - &layout.clip_rect, - info.gradient.radius, - info.gradient.end_offset, + 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, - &stops, - &mut |solid_rect, color| { - self.add_nonshadowable_primitive( - spatial_node_index, - clip_chain_id, - &LayoutPrimitiveInfo { - rect: *solid_rect, - .. layout - }, - Vec::new(), - PrimitiveKeyKind::Rectangle { color: PropertyBinding::Value(color) }, - ); - } + tile_size, + info.tile_spacing, + None, ); - // TODO: create_radial_gradient_prim already calls - // this, but it leaves the info variable that is - // passed to add_nonshadowable_primitive unmodified - // which can cause issues. - simplify_repeated_primitive(&tile_size, &mut tile_spacing, &mut prim_rect); - - if !tile_size.ceil().is_empty() { - layout.rect = prim_rect; - let prim_key_kind = self.create_radial_gradient_prim( - &layout, - 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, - stops, - info.gradient.extend_mode, - tile_size, - tile_spacing, - None, - ); - - self.add_nonshadowable_primitive( - spatial_node_index, - clip_chain_id, - &layout, - Vec::new(), - prim_key_kind, - ); - } + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + &layout, + Vec::new(), + prim_key_kind, + ); } DisplayItem::ConicGradient(ref info) => { - profile_scope!("conic"); - - if !info.gradient.is_valid() { - return; - } - - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1390,40 +1156,28 @@ impl<'a> SceneBuilder<'a> { info.tile_size, ); - let offset = apply_gradient_local_clip( - &mut layout.rect, - &tile_size, - &info.tile_spacing, - &layout.clip_rect, + 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, ); - let center = info.gradient.center + offset; - if !tile_size.ceil().is_empty() { - let prim_key_kind = self.create_conic_gradient_prim( - &layout, - 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, - ); - } + 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, @@ -1442,8 +1196,6 @@ impl<'a> SceneBuilder<'a> { ); } 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, @@ -1457,9 +1209,14 @@ impl<'a> SceneBuilder<'a> { item.gradient_stops(), ); } + DisplayItem::Iframe(ref info) => { + let space = self.get_space(info.space_and_clip.spatial_id); + self.build_iframe( + info, + space, + ); + } 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); @@ -1472,13 +1229,9 @@ impl<'a> SceneBuilder<'a> { info.id, &info.parent_space_and_clip, &image_mask, - info.fill_rule, - item.points(), ); } 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); @@ -1490,8 +1243,6 @@ impl<'a> SceneBuilder<'a> { ); } 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); @@ -1503,8 +1254,6 @@ impl<'a> SceneBuilder<'a> { ); } 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( @@ -1515,26 +1264,21 @@ impl<'a> SceneBuilder<'a> { 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(); + let mut instances: SmallVec<[ClipInstance; 4]> = SmallVec::new(); for clip_item in item.clip_chain_items() { let template = self.clip_store.get_template(clip_item); - let instances = &self.clip_store.instances[template.clips.start as usize .. template.clips.end as usize]; - clips.extend_from_slice(instances); + instances.extend_from_slice(&template.instances); } self.clip_store.register_clip_template( ClipId::ClipChain(info.id), parent, - &clips, + &instances, ); }, 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, @@ -1543,8 +1287,6 @@ impl<'a> SceneBuilder<'a> { ); } DisplayItem::StickyFrame(ref info) => { - profile_scope!("stickyframe"); - let parent_space = self.get_space(info.parent_spatial_id); self.build_sticky_frame( info, @@ -1552,8 +1294,6 @@ impl<'a> SceneBuilder<'a> { ); } DisplayItem::BackdropFilter(ref info) => { - profile_scope!("backdrop"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties( &info.common, None, @@ -1577,16 +1317,14 @@ impl<'a> SceneBuilder<'a> { DisplayItem::SetGradientStops | DisplayItem::SetFilterOps | DisplayItem::SetFilterData | - DisplayItem::SetFilterPrimitives | - DisplayItem::SetPoints => {} + 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::PopStackingContext => { + unreachable!("Should have returned in parent method.") } DisplayItem::ReuseItems(key) | @@ -1595,8 +1333,6 @@ impl<'a> SceneBuilder<'a> { } 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, @@ -1610,8 +1346,6 @@ impl<'a> SceneBuilder<'a> { ); } DisplayItem::PopAllShadows => { - profile_scope!("pop_all_shadows"); - self.pop_all_shadows(); } } @@ -1696,17 +1430,41 @@ impl<'a> SceneBuilder<'a> { &mut self, info: &LayoutPrimitiveInfo, spatial_node_index: SpatialNodeIndex, - clip_id: ClipId, - tag: ItemTag, + clip_chain_id: ClipChainId, ) { - self.hit_testing_scene.add_item( + let tag = match info.hit_info { + Some(tag) => tag, + None => return, + }; + + // We want to get a range of clip chain roots that apply to this + // hit testing primitive. + + // Get the start index for the clip chain root range for this primitive. + let start = self.hit_testing_scene.next_clip_chain_index(); + + // Add the clip chain root for the primitive itself. + self.hit_testing_scene.add_clip_chain(clip_chain_id); + + // Append any clip chain roots from enclosing stacking contexts. + for sc in &self.sc_stack { + self.hit_testing_scene.add_clip_chain(sc.clip_chain_id); + } + + // Construct a clip chain roots range to be stored with the item. + let clip_chain_range = ops::Range { + start, + end: self.hit_testing_scene.next_clip_chain_index(), + }; + + // Create and store the hit testing primitive itself. + let new_item = HitTestingItem::new( tag, info, spatial_node_index, - clip_id, - &self.clip_store, - self.interners, + clip_chain_range, ); + self.hit_testing_scene.add_item(new_item); } /// Add an already created primitive to the draw lists. @@ -1722,33 +1480,13 @@ impl<'a> SceneBuilder<'a> { 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, - self.root_iframe_clip, - ); - } - } + let stacking_context = self.sc_stack.last_mut().unwrap(); + stacking_context.prim_list.add_prim( + prim_instance, + prim_rect, + spatial_node_index, + flags, + ); } /// Convenience interface that creates a primitive entry and adds it @@ -1838,6 +1576,11 @@ impl<'a> SceneBuilder<'a> { &info.rect, &prim_instance, ); + self.add_primitive_to_hit_testing_list( + info, + spatial_node_index, + clip_chain_id, + ); self.add_primitive_to_draw_list( prim_instance, info.rect, @@ -1846,23 +1589,9 @@ impl<'a> SceneBuilder<'a> { ); } - /// If no stacking contexts are present (i.e. we are adding prims to a tile - /// cache), set a barrier to force creation of a slice before the next prim - fn add_tile_cache_barrier_if_needed( - &mut self, - slice_flags: SliceFlags, - ) { - if self.sc_stack.is_empty() { - // Shadows can only exist within a stacking context - assert!(self.pending_shadow_items.is_empty()); - - self.tile_cache_builder.add_tile_cache_barrier(slice_flags); - } - } - - /// Push a new stacking context. Returns context that must be passed to pop_stacking_context(). - fn push_stacking_context( + pub fn push_stacking_context( &mut self, + pipeline_id: PipelineId, composite_ops: CompositeOps, transform_style: TransformStyle, prim_flags: PrimitiveFlags, @@ -1870,12 +1599,22 @@ impl<'a> SceneBuilder<'a> { clip_id: Option<ClipId>, requested_raster_space: RasterSpace, flags: StackingContextFlags, - pipeline_id: PipelineId, - ) -> StackingContextInfo { - profile_scope!("push_stacking_context"); + device_pixel_scale: DevicePixelScale, + ) { + // Check if this stacking context is the root of a pipeline, and the caller + // has requested it as an output frame. + let is_pipeline_root = + self.sc_stack.last().map_or(true, |sc| sc.pipeline_id != pipeline_id); + let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) { + Some(pipeline_id) + } else { + None + }; - // Push current requested raster space on stack for prims to access - self.raster_space_stack.push(requested_raster_space); + let clip_chain_id = match clip_id { + Some(clip_id) => self.clip_store.get_or_build_clip_chain_id(clip_id), + None => ClipChainId::NONE, + }; // Get the transform-style of the parent stacking context, // which determines if we *might* need to draw this on @@ -1923,20 +1662,21 @@ impl<'a> SceneBuilder<'a> { (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 + // Find 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); - + let ancestor_context = self.sc_stack + .iter() + .rfind(|sc| !sc.is_3d()); Picture3DContext::In { root_data: if parent_is_3d { None } else { Some(Vec::new()) }, - ancestor_index, + ancestor_index: match ancestor_context { + Some(sc) => sc.spatial_node_index, + None => ROOT_SPATIAL_NODE_INDEX, + }, } } else { Picture3DContext::Out @@ -1947,171 +1687,165 @@ impl<'a> SceneBuilder<'a> { // prepare step to skip the intermediate surface if the // clip node doesn't affect the stacking context rect. let mut blit_reason = BlitReason::empty(); + let mut current_clip_chain_id = clip_chain_id; if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) { 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; - } - } + // Walk each clip in this chain, to see whether any of the clips + // require that we draw this to an intermediate surface. + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &self + .clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; - let is_redundant = FlattenedStackingContext::is_redundant( - flags, - &context_3d, - &composite_ops, - blit_reason, - self.sc_stack.last(), - prim_flags, - ); + let clip_node_data = &self.interners.clip[clip_chain_node.handle]; - // If stacking context is a scrollbar, force a new slice for the primitives - // within. The stacking context will be redundant and removed by above check. - let set_tile_cache_barrier = prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER); + if let ClipNodeKind::Complex = clip_node_data.clip_node_kind { + blit_reason = BlitReason::CLIP; + break; + } - if set_tile_cache_barrier { - self.add_tile_cache_barrier_if_needed(SliceFlags::IS_SCROLLBAR); + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; } - let mut sc_info = StackingContextInfo { - pop_hit_testing_clip: false, - pop_stacking_context: false, - pop_containing_block: false, - set_tile_cache_barrier, - }; - - // 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); - } + let snap_to_device = self.sc_stack.last().map_or( + SpaceSnapper::new( + ROOT_SPATIAL_NODE_INDEX, + device_pixel_scale, + ), + |sc| sc.snap_to_device.clone(), + ); - // If this stacking context is redundant, we don't care about getting a clip-chain for it. - // However, if we _do_ have a clip, we must build it here before the `push_clip_root` - // calls below, to ensure we get the clips for drawing this stacking context itself. - let clip_chain_id = if is_redundant { - ClipChainId::NONE - } else { - // Get a clip-chain for this stacking context - even if the stacking context - // itself has no clips, it's possible that there are clips to collect from - // the previous clip-chain builder. - let clip_id = clip_id.unwrap_or(ClipId::root(pipeline_id)); - self.clip_store.get_or_build_clip_chain_id(clip_id) + let is_redundant = match self.sc_stack.last() { + Some(parent) => { + FlattenedStackingContext::is_redundant( + &context_3d, + &composite_ops, + prim_flags, + blit_reason, + requested_raster_space, + parent, + ) + } + None => { + false + } }; - // If this has a valid clip, register with the hit-testing scene if let Some(clip_id) = clip_id { - self.hit_testing_scene.push_clip(clip_id); - sc_info.pop_hit_testing_clip = 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(clip_id, true); - } else { - self.clip_store.push_clip_root(None, false); - } - - // 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), - flags, - }); + // 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); + } } - sc_info + // 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(), + pipeline_id, + prim_flags, + requested_raster_space, + spatial_node_index, + clip_id, + clip_chain_id, + frame_output_pipeline_id, + composite_ops, + blit_reason, + transform_style, + context_3d, + is_redundant, + is_backdrop_root: flags.contains(StackingContextFlags::IS_BACKDROP_ROOT), + snap_to_device, + }); } - 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(); - - // Pop off clip builder root (pushed unconditionally in push_stacking_context) - self.clip_store.pop_clip_root(); - - // If the stacking context formed a containing block, pop off the stack - if info.pop_containing_block { - self.containing_block_stack.pop().unwrap(); - } - - if info.set_tile_cache_barrier { - self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); - } + pub fn pop_stacking_context(&mut self) { + let mut stacking_context = self.sc_stack.pop().unwrap(); - // If the stacking context established a clip root, pop off the stack - if info.pop_hit_testing_clip { - self.hit_testing_scene.pop_clip(); + if stacking_context.clip_id.is_some() { + self.clip_store.pop_clip_root(); } - // If the stacking context was otherwise redundant, early exit - if !info.pop_stacking_context { - return; - } + // If we encounter a stacking context that is effectively a no-op, then instead + // of creating a picture, just append the primitive list to the parent stacking + // context as a short cut. This serves two purposes: + // (a) It's an optimization to reduce picture count and allocations, as display lists + // often contain a lot of these stacking contexts that don't require pictures or + // off-screen surfaces. + // (b) It's useful for the initial version of picture caching in gecko, by enabling + // is to just look for interesting scroll roots on the root stacking context, + // without having to consider cuts at stacking context boundaries. + let parent_is_empty = match self.sc_stack.last_mut() { + Some(parent_sc) => { + if stacking_context.is_redundant { + if !stacking_context.prim_list.is_empty() { + // If popping a redundant stacking context that is from a different pipeline, + // we want to insert flags where the picture cache slices should be created + // for this iframe. For now, we want to match existing behavior, that is: + // - Only cache content that is within the main scroll root, and: + // - Skip caching fixed position content before / after the scroll root. + // This means that we don't add scrollbars, which cause lots of extra + // invalidations. There is ongoing work to add tags to primitives that + // are scrollbars. Once this lands, we can simplify this logic considerably + // (and add a separate picture cache slice / OS layer for scroll bars). + if parent_sc.pipeline_id != stacking_context.pipeline_id && self.iframe_depth == 1 { + self.content_slice_count = stacking_context.init_picture_caching( + &self.spatial_tree, + &self.clip_store, + &self.quality_settings, + ); - let stacking_context = self.sc_stack.pop().unwrap(); - - // If the stacking context is a blend container, and if we're at the top level - // of the stacking context tree, we can make this blend container into a tile - // cache. This means that we get caching and correct scrolling invalidation for - // root level blend containers. For these cases, the readbacks of the backdrop - // are handled by doing partial reads of the picture cache tiles during rendering. - if stacking_context.flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) && - self.sc_stack.is_empty() && - self.tile_cache_builder.can_add_container_tile_cache() && - self.spatial_tree.get_static_coordinate_system_id(stacking_context.spatial_node_index) == StaticCoordinateSystemId::ROOT - { - self.tile_cache_builder.add_tile_cache( - stacking_context.prim_list, - stacking_context.clip_chain_id, - &self.spatial_tree, - &self.clip_store, - self.interners, - &self.config, - self.root_iframe_clip, - SliceFlags::IS_BLEND_CONTAINER, - ); + // Mark that a user supplied tile cache was specified. + self.picture_caching_initialized = true; + } - return; - } + // If the parent context primitives list is empty, it's faster + // to assign the storage of the popped context instead of paying + // the copying cost for extend. + if parent_sc.prim_list.is_empty() { + parent_sc.prim_list = stacking_context.prim_list; + } else { + parent_sc.prim_list.extend(stacking_context.prim_list); + } + } - let parent_is_empty = match self.sc_stack.last() { - Some(parent_sc) => { - assert!(!stacking_context.is_redundant); + return; + } parent_sc.prim_list.is_empty() }, None => true, }; - let mut source = match stacking_context.context_3d { + if self.sc_stack.is_empty() { + // If we didn't encounter a content iframe, then set up picture caching slice markers + // on the root stacking context. This can happen in Gecko when the parent process + // provides the content display list (e.g. about:support, about:config etc). + if !self.picture_caching_initialized { + self.content_slice_count = stacking_context.init_picture_caching( + &self.spatial_tree, + &self.clip_store, + &self.quality_settings, + ); + self.picture_caching_initialized = true; + } + + self.setup_picture_caching( + &mut stacking_context.prim_list, + ); + } + + let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = 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, @@ -2119,92 +1853,63 @@ impl<'a> SceneBuilder<'a> { // 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 => { + Picture3DContext::In { ancestor_index, .. } => ( + Picture3DContext::In { root_data: None, ancestor_index }, + Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)), + None, + ), + Picture3DContext::Out => ( + 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, - ) + // By default, this picture will be collapsed into + // the owning target. + None } else { - let composite_mode = Some( - PictureCompositeMode::Blit(stacking_context.blit_reason) - ); + // Add a dummy composite filter if the SC has to be isolated. + Some(PictureCompositeMode::Blit(stacking_context.blit_reason)) + }, + stacking_context.frame_output_pipeline_id + ), + }; - // 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(), - )) - ); + // Add picture for this actual stacking context contents to render into. + let leaf_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + leaf_composite_mode.clone(), + leaf_context_3d, + leaf_output_pipeline_id, + true, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + stacking_context.prim_list, + stacking_context.spatial_node_index, + None, + PictureOptions::default(), + )) + ); - let instance = create_prim_instance( - pic_index, - composite_mode.into(), - ClipChainId::NONE, - &mut self.interners, - ); + // Create a chain of pictures based on presence of filters, + // mix-blend-mode and/or 3d rendering context containers. - PictureChainBuilder::from_instance( - instance, - stacking_context.prim_flags, - stacking_context.spatial_node_index, - ) - } - } - }; + let mut current_pic_index = leaf_pic_index; + let mut cur_instance = create_prim_instance( + leaf_pic_index, + leaf_composite_mode.into(), + ClipChainId::NONE, + &mut self.interners, + ); + + if cur_instance.is_chased() { + println!("\tis a leaf primitive for a stacking context"); + } // 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, + instance: cur_instance, spatial_node_index: stacking_context.spatial_node_index, flags: stacking_context.prim_flags, }); @@ -2220,7 +1925,7 @@ impl<'a> SceneBuilder<'a> { } // This is the acttual picture representing our 3D hierarchy root. - let pic_index = PictureIndex(self.prim_store.pictures + current_pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( None, @@ -2228,38 +1933,42 @@ impl<'a> SceneBuilder<'a> { root_data: Some(Vec::new()), ancestor_index, }, + stacking_context.frame_output_pipeline_id, true, stacking_context.prim_flags, + stacking_context.requested_raster_space, prim_list, stacking_context.spatial_node_index, + None, PictureOptions::default(), )) ); - let instance = create_prim_instance( - pic_index, + cur_instance = create_prim_instance( + current_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, + let (filtered_pic_index, filtered_instance) = self.wrap_prim_with_filters( + cur_instance, + current_pic_index, stacking_context.composite_ops.filters, stacking_context.composite_ops.filter_primitives, stacking_context.composite_ops.filter_datas, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + stacking_context.spatial_node_index, true, ); + let has_filters = current_pic_index != filtered_pic_index; + + current_pic_index = filtered_pic_index; + cur_instance = filtered_instance; + // 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: @@ -2273,20 +1982,44 @@ impl<'a> SceneBuilder<'a> { // 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); + if self.sc_stack.last().unwrap().blit_reason.contains(BlitReason::ISOLATE) { + let composite_mode = Some(PictureCompositeMode::MixBlend(mix_blend_mode)); - source = source.add_picture( - composite_mode, - Picture3DContext::Out, - PictureOptions::default(), + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + stacking_context.spatial_node_index, + stacking_context.prim_flags, + ); + + let blend_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + Picture3DContext::Out, + None, + true, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + prim_list, + stacking_context.spatial_node_index, + None, + PictureOptions::default(), + )) + ); + + current_pic_index = blend_pic_index; + cur_instance = create_prim_instance( + blend_pic_index, + composite_mode.into(), + ClipChainId::NONE, &mut self.interners, - &mut self.prim_store, ); + + if cur_instance.is_chased() { + println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode); + } } else { // If we have a mix-blend-mode, the stacking context needs to be isolated // to blend correctly as per the CSS spec. @@ -2297,11 +2030,7 @@ impl<'a> SceneBuilder<'a> { // 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, - ); + cur_instance.clip_chain_id = stacking_context.clip_chain_id; // 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. @@ -2322,13 +2051,7 @@ impl<'a> SceneBuilder<'a> { } // 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, - ); - + self.root_pic_index = current_pic_index; None } }; @@ -2376,6 +2099,8 @@ impl<'a> SceneBuilder<'a> { &mut self, pipeline_id: PipelineId, viewport_size: &LayoutSize, + content_size: &LayoutSize, + device_pixel_scale: DevicePixelScale, ) { if let ChasePrimitive::Id(id) = self.config.chase_primitive { println!("Chasing {:?} by index", id); @@ -2388,29 +2113,33 @@ impl<'a> SceneBuilder<'a> { pipeline_id, TransformStyle::Flat, PropertyBinding::Value(LayoutTransform::identity()), - ReferenceFrameKind::Transform { - is_2d_scale_translation: false, - should_snap: false, - }, + ReferenceFrameKind::Transform, LayoutVector2D::zero(), ); - let viewport_rect = self.snap_rect( - &LayoutRect::new(LayoutPoint::zero(), *viewport_size), + // We can't use this with the stacking context because it does not exist + // yet. Just create a dedicated snapper for the root. + let snap_to_device = SpaceSnapper::new_with_target( spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + device_pixel_scale, + &self.spatial_tree, + ); + + let content_size = snap_to_device.snap_size(content_size); + let viewport_rect = snap_to_device.snap_rect( + &LayoutRect::new(LayoutPoint::zero(), *viewport_size), ); self.add_scroll_frame( SpatialId::root_scroll_node(pipeline_id), spatial_node_index, - ExternalScrollId(0, pipeline_id), + Some(ExternalScrollId(0, pipeline_id)), pipeline_id, &viewport_rect, - &viewport_rect.size, + &content_size, ScrollSensitivity::ScriptAndInputEvents, - ScrollFrameKind::PipelineRoot { - is_root_pipeline: true, - }, + ScrollFrameKind::PipelineRoot, LayoutVector2D::zero(), ); } @@ -2420,31 +2149,18 @@ impl<'a> SceneBuilder<'a> { new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, image_mask: &ImageMask, - fill_rule: FillRule, - points_range: ItemRange<LayoutPoint>, ) { 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, + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree, ); - let points: Vec<LayoutPoint> = points_range.iter().collect(); - - // If any points are provided, then intern a polygon with the points and fill rule. - let mut polygon_handle: Option<PolygonDataHandle> = None; - if points.len() > 0 { - let item = PolygonKey::new(&points, fill_rule); - - let handle = self - .interners - .polygon - .intern(&item, || item); - polygon_handle = Some(handle); - } + let snapped_mask_rect = snap_to_device.snap_rect(&image_mask.rect); let item = ClipItemKey { - kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect, polygon_handle), + kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect), }; let handle = self @@ -2456,10 +2172,7 @@ impl<'a> SceneBuilder<'a> { } }); - let instance = SceneClipInstance { - key: item, - clip: ClipInstance::new(handle, spatial_node_index), - }; + let instance = ClipInstance::new(handle, spatial_node_index); self.clip_store.register_clip_template( new_node_id, @@ -2477,11 +2190,14 @@ impl<'a> SceneBuilder<'a> { ) { 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, + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree, ); + let snapped_clip_rect = snap_to_device.snap_rect(clip_rect); + let item = ClipItemKey { kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), }; @@ -2494,10 +2210,7 @@ impl<'a> SceneBuilder<'a> { } }); - let instance = SceneClipInstance { - key: item, - clip: ClipInstance::new(handle, spatial_node_index), - }; + let instance = ClipInstance::new(handle, spatial_node_index); self.clip_store.register_clip_template( new_node_id, @@ -2515,10 +2228,13 @@ impl<'a> SceneBuilder<'a> { ) { 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), + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree, ); + + let snapped_region_rect = snap_to_device.snap_rect(&clip.rect.translate(current_offset)); let item = ClipItemKey { kind: ClipItemKeyKind::rounded_rect( snapped_region_rect, @@ -2536,10 +2252,7 @@ impl<'a> SceneBuilder<'a> { } }); - let instance = SceneClipInstance { - key: item, - clip: ClipInstance::new(handle, spatial_node_index), - }; + let instance = ClipInstance::new(handle, spatial_node_index); self.clip_store.register_clip_template( new_node_id, @@ -2560,11 +2273,14 @@ impl<'a> SceneBuilder<'a> { // 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, + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( spatial_node_index, + &self.spatial_tree, ); - let mut instances: SmallVec<[SceneClipInstance; 4]> = SmallVec::new(); + + let snapped_clip_rect = snap_to_device.snap_rect(&clip_region.main); + let mut instances: SmallVec<[ClipInstance; 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. @@ -2583,15 +2299,10 @@ impl<'a> SceneBuilder<'a> { clip_node_kind: ClipNodeKind::Rectangle, } }); - instances.push( - SceneClipInstance { - key: item, - clip: ClipInstance::new(handle, spatial_node_index), - }, - ); + instances.push(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 snapped_region_rect = snap_to_device.snap_rect(®ion.rect); let item = ClipItemKey { kind: ClipItemKeyKind::rounded_rect( snapped_region_rect, @@ -2609,12 +2320,7 @@ impl<'a> SceneBuilder<'a> { } }); - instances.push( - SceneClipInstance { - key: item, - clip: ClipInstance::new(handle, spatial_node_index), - }, - ); + instances.push(ClipInstance::new(handle, spatial_node_index)); } self.clip_store.register_clip_template( @@ -2628,7 +2334,7 @@ impl<'a> SceneBuilder<'a> { &mut self, new_node_id: SpatialId, parent_node_index: SpatialNodeIndex, - external_id: ExternalScrollId, + external_id: Option<ExternalScrollId>, pipeline_id: PipelineId, frame_rect: &LayoutRect, content_size: &LayoutSize, @@ -2695,68 +2401,62 @@ impl<'a> SceneBuilder<'a> { // Gaussian blur with a standard deviation equal to half the blur radius." let std_deviation = pending_shadow.shadow.blur_radius * 0.5; + // If the shadow has no blur, any elements will get directly rendered + // into the parent picture surface, instead of allocating and drawing + // into an intermediate surface. In this case, we will need to apply + // the local clip rect to primitives. + let is_passthrough = pending_shadow.shadow.blur_radius == 0.0; + + // shadows always rasterize in local space. + // TODO(gw): expose API for clients to specify a raster scale + let raster_space = if is_passthrough { + self.sc_stack.last().unwrap().requested_raster_space + } else { + RasterSpace::Local(1.0) + }; + // 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 { + match item { ShadowItem::Image(ref pending_image) => { - self.create_shadow_prim( + self.add_shadow_prim( &pending_shadow, pending_image, - blur_is_noop, + &mut prim_list, ) } ShadowItem::LineDecoration(ref pending_line_dec) => { - self.create_shadow_prim( + self.add_shadow_prim( &pending_shadow, pending_line_dec, - blur_is_noop, + &mut prim_list, ) } ShadowItem::NormalBorder(ref pending_border) => { - self.create_shadow_prim( + self.add_shadow_prim( &pending_shadow, pending_border, - blur_is_noop, + &mut prim_list, ) } ShadowItem::Primitive(ref pending_primitive) => { - self.create_shadow_prim( + self.add_shadow_prim( &pending_shadow, pending_primitive, - blur_is_noop, + &mut prim_list, ) } ShadowItem::TextRun(ref pending_text_run) => { - self.create_shadow_prim( + self.add_shadow_prim( &pending_shadow, pending_text_run, - blur_is_noop, + &mut prim_list, ) } - _ => { - 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, - ); + _ => {} } } @@ -2767,10 +2467,9 @@ impl<'a> SceneBuilder<'a> { // 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(); + let blur_filter = Filter::Blur(std_deviation); + let composite_mode = PictureCompositeMode::Filter(blur_filter); + let composite_mode_key = Some(composite_mode.clone()).into(); // Pass through configuration information about whether WR should // do the bounding rect inflation for text shadows. @@ -2782,12 +2481,15 @@ impl<'a> SceneBuilder<'a> { let shadow_pic_index = PictureIndex(self.prim_store.pictures .alloc() .init(PicturePrimitive::new_image( - composite_mode, + Some(composite_mode), Picture3DContext::Out, - false, + None, + is_passthrough, PrimitiveFlags::IS_BACKFACE_VISIBLE, + raster_space, prim_list, pending_shadow.spatial_node_index, + None, options, )) ); @@ -2852,29 +2554,33 @@ impl<'a> SceneBuilder<'a> { self.pending_shadow_items = items; } - fn create_shadow_prim<P>( + fn add_shadow_prim<P>( &mut self, pending_shadow: &PendingShadow, pending_primitive: &PendingPrimitive<P>, - blur_is_noop: bool, - ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex) + prim_list: &mut PrimitiveList, + ) where P: InternablePrimitive + CreateShadow, Interners: AsMut<Interner<P>>, { + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + pending_primitive.spatial_node_index, + &self.spatial_tree, + ); + // 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 = snap_to_device.snap_rect( &info.rect.translate(pending_shadow.shadow.offset), - pending_primitive.spatial_node_index, ); - info.clip_rect = self.snap_rect( + info.clip_rect = snap_to_device.snap_rect( &info.clip_rect.translate(pending_shadow.shadow.offset), - pending_primitive.spatial_node_index, ); // Construct and add a primitive for the given shadow. @@ -2882,14 +2588,16 @@ impl<'a> SceneBuilder<'a> { &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(), - ), + pending_primitive.prim.create_shadow(&pending_shadow.shadow), ); - (shadow_prim_instance, info, pending_primitive.spatial_node_index) + // Add the new primitive to the shadow picture. + prim_list.add_prim( + shadow_prim_instance, + info.rect, + pending_primitive.spatial_node_index, + info.flags, + ); } fn add_shadow_prim_to_draw_list<P>( @@ -2931,25 +2639,54 @@ impl<'a> SceneBuilder<'a> { ) { } - pub fn add_clear_rectangle( + pub fn add_solid_rectangle( &mut self, spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, info: &LayoutPrimitiveInfo, + color: PropertyBinding<ColorF>, ) { - // Clear prims must be in their own picture cache slice to - // be composited correctly. - self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); + match color { + PropertyBinding::Value(value) => { + if value.a == 0.0 { + // 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( + info, + spatial_node_index, + clip_chain_id, + ); + return; + } + }, + PropertyBinding::Binding(..) => {}, + } self.add_primitive( spatial_node_index, clip_chain_id, info, Vec::new(), - PrimitiveKeyKind::Clear, + PrimitiveKeyKind::Rectangle { + color: color.into(), + }, ); + } - self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); + 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( @@ -3066,7 +2803,7 @@ impl<'a> SceneBuilder<'a> { &info, gradient.start_point, gradient.end_point, - read_gradient_stops(gradient_stops), + gradient_stops, gradient.extend_mode, LayoutSize::new(border.height as f32, border.width as f32), LayoutSize::zero(), @@ -3091,7 +2828,7 @@ impl<'a> SceneBuilder<'a> { gradient.start_offset * gradient.radius.width, gradient.end_offset * gradient.radius.width, gradient.radius.width / gradient.radius.height, - read_gradient_stops(gradient_stops), + gradient_stops, gradient.extend_mode, LayoutSize::new(border.height as f32, border.width as f32), LayoutSize::zero(), @@ -3147,7 +2884,7 @@ impl<'a> SceneBuilder<'a> { info: &LayoutPrimitiveInfo, start_point: LayoutPoint, end_point: LayoutPoint, - stops: Vec<GradientStopKey>, + stops: ItemRange<GradientStop>, extend_mode: ExtendMode, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, @@ -3156,17 +2893,19 @@ impl<'a> SceneBuilder<'a> { let mut prim_rect = info.rect; simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); - let mut is_entirely_transparent = true; - for stop in &stops { - if stop.color.a > 0 { - is_entirely_transparent = false; - break; + 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 is_entirely_transparent { + if max_alpha <= 0.0 { return None; } @@ -3188,13 +2927,6 @@ impl<'a> SceneBuilder<'a> { (start_point, end_point) }; - let is_tiled = prim_rect.size.width > stretch_size.width - || prim_rect.size.height > stretch_size.height; - // SWGL has a fast-path that can render gradients faster than it can sample from the - // texture cache so we disable caching in this configuration. Cached gradients are - // faster on hardware. - let cached = !self.config.is_software || is_tiled; - Some(LinearGradient { extend_mode, start_point: sp.into(), @@ -3204,7 +2936,6 @@ impl<'a> SceneBuilder<'a> { stops, reverse_stops, nine_patch, - cached, }) } @@ -3215,7 +2946,7 @@ impl<'a> SceneBuilder<'a> { start_radius: f32, end_radius: f32, ratio_xy: f32, - stops: Vec<GradientStopKey>, + stops: ItemRange<GradientStop>, extend_mode: ExtendMode, stretch_size: LayoutSize, mut tile_spacing: LayoutSize, @@ -3230,6 +2961,13 @@ impl<'a> SceneBuilder<'a> { ratio_xy, }; + let stops = stops.iter().map(|stop| { + GradientStopKey { + offset: stop.offset, + color: stop.color.into(), + } + }).collect(); + RadialGradient { extend_mode, center: center.into(), @@ -3337,18 +3075,10 @@ impl<'a> SceneBuilder<'a> { }) .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, } }; @@ -3463,6 +3193,7 @@ impl<'a> SceneBuilder<'a> { }; let backdrop_spatial_node_index = self.prim_store.pictures[backdrop_pic_index.0].spatial_node_index; + let requested_raster_space = self.sc_stack.last().expect("no active stacking context").requested_raster_space; let mut instance = self.create_primitive( info, @@ -3501,10 +3232,13 @@ impl<'a> SceneBuilder<'a> { .init(PicturePrimitive::new_image( composite_mode.clone(), Picture3DContext::Out, + None, true, prim_flags, + requested_raster_space, prim_list, backdrop_spatial_node_index, + None, PictureOptions { inflate_if_required: false, }, @@ -3519,17 +3253,15 @@ impl<'a> SceneBuilder<'a> { ); } - let mut source = PictureChainBuilder::from_instance( + let (mut filtered_pic_index, mut filtered_instance) = self.wrap_prim_with_filters( instance, - info.flags, - backdrop_spatial_node_index, - ); - - source = self.wrap_prim_with_filters( - source, + backdrop_pic_index, filters, filter_primitives, filter_datas, + info.flags, + requested_raster_space, + backdrop_spatial_node_index, false, ); @@ -3544,20 +3276,23 @@ impl<'a> SceneBuilder<'a> { 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, + let (pic_index, instance) = self.wrap_prim_with_filters( + filtered_instance, + filtered_pic_index, filters, filter_primitives, filter_datas, + info.flags, + requested_raster_space, + backdrop_spatial_node_index, false, ); + + filtered_instance = instance; + filtered_pic_index = pic_index; } - let filtered_instance = source.finalize( - clip_chain_id, - &mut self.interners, - &mut self.prim_store, - ); + filtered_instance.clip_chain_id = clip_chain_id; self.sc_stack .iter_mut() @@ -3573,9 +3308,6 @@ impl<'a> SceneBuilder<'a> { ); } - /// Create pictures for each stacking context rendered into their parents, down to the nearest - /// backdrop root until we have a picture that represents the contents of all primitives added - /// since the backdrop root pub fn cut_backdrop_picture(&mut self) -> Option<PictureIndex> { let mut flattened_items = None; let mut backdrop_root = None; @@ -3619,15 +3351,18 @@ impl<'a> SceneBuilder<'a> { Some(pic_index) } - #[must_use] fn wrap_prim_with_filters( &mut self, - mut source: PictureChainBuilder, + mut cur_instance: PrimitiveInstance, + mut current_pic_index: PictureIndex, mut filter_ops: Vec<Filter>, mut filter_primitives: Vec<FilterPrimitive>, filter_datas: Vec<FilterData>, + flags: PrimitiveFlags, + requested_raster_space: RasterSpace, + spatial_node_index: SpatialNodeIndex, inflate_if_required: bool, - ) -> PictureChainBuilder { + ) -> (PictureIndex, PrimitiveInstance) { // 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. @@ -3637,7 +3372,7 @@ impl<'a> SceneBuilder<'a> { // 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 { + let composite_mode = Some(match *filter { Filter::ComponentTransfer => { let filter_data = &filter_datas[current_filter_data_index]; @@ -3666,22 +3401,50 @@ impl<'a> SceneBuilder<'a> { PictureCompositeMode::ComponentTransferFilter(handle) } } - _ => { - if filter.is_noop() { - continue; - } else { - PictureCompositeMode::Filter(filter.clone()) - } - } - }; + _ => PictureCompositeMode::Filter(filter.clone()), + }); - source = source.add_picture( - composite_mode, - Picture3DContext::Out, - PictureOptions { inflate_if_required }, + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + spatial_node_index, + flags, + ); + + let filter_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + Picture3DContext::Out, + None, + true, + flags, + requested_raster_space, + prim_list, + spatial_node_index, + None, + PictureOptions { + inflate_if_required, + }, + )) + ); + + current_pic_index = filter_pic_index; + cur_instance = create_prim_instance( + current_pic_index, + composite_mode.into(), + ClipChainId::NONE, &mut self.interners, - &mut self.prim_store, ); + + if cur_instance.is_chased() { + println!("\tis a composite picture for a stacking context with {:?}", filter); + } + + // Run the optimize pass on this picture, to see if we can + // collapse opacity and avoid drawing to an off-screen surface. + self.prim_store.optimize_picture_if_possible(current_pic_index); } if !filter_primitives.is_empty() { @@ -3711,27 +3474,55 @@ impl<'a> SceneBuilder<'a> { filter_datas, ); - source = source.add_picture( - composite_mode, - Picture3DContext::Out, - PictureOptions { inflate_if_required }, + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + spatial_node_index, + flags, + ); + + let filter_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + Some(composite_mode.clone()), + Picture3DContext::Out, + None, + true, + flags, + requested_raster_space, + prim_list, + spatial_node_index, + None, + PictureOptions { + inflate_if_required, + }, + )) + ); + + current_pic_index = filter_pic_index; + cur_instance = create_prim_instance( + current_pic_index, + Some(composite_mode).into(), + ClipChainId::NONE, &mut self.interners, - &mut self.prim_store, ); - } - source + if cur_instance.is_chased() { + println!("\tis a composite picture for a stacking context with an SVG filter"); + } + + // Run the optimize pass on this picture, to see if we can + // collapse opacity and avoid drawing to an off-screen surface. + self.prim_store.optimize_picture_if_possible(current_pic_index); + } + (current_pic_index, cur_instance) } } pub trait CreateShadow { - fn create_shadow( - &self, - shadow: &Shadow, - blur_is_noop: bool, - current_raster_space: RasterSpace, - ) -> Self; + fn create_shadow(&self, shadow: &Shadow) -> Self; } pub trait IsVisible { @@ -3747,19 +3538,6 @@ struct ExtendedPrimitiveInstance { 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 hit-testing scene. - pop_hit_testing_clip: 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, - /// If true, set a tile cache barrier when popping the stacking context. - set_tile_cache_barrier: bool, -} - /// Properties of a stacking context that are maintained /// during creation of the scene. These structures are /// not persisted after the initial scene build. @@ -3770,11 +3548,20 @@ struct FlattenedStackingContext { /// Primitive instance flags for compositing this stacking context prim_flags: PrimitiveFlags, + /// Whether or not the caller wants this drawn in + /// screen space (quality) or local space (performance) + requested_raster_space: RasterSpace, + /// The positioning node for this stacking context spatial_node_index: SpatialNodeIndex, /// The clip chain for this stacking context clip_chain_id: ClipChainId, + clip_id: Option<ClipId>, + + /// If set, this should be provided to caller + /// as an output texture. + frame_output_pipeline_id: Option<PipelineId>, /// The list of filters / mix-blend-mode for this /// stacking context. @@ -3784,6 +3571,9 @@ struct FlattenedStackingContext { /// be an offscreen surface. blit_reason: BlitReason, + /// Pipeline this stacking context belongs to. + pipeline_id: PipelineId, + /// CSS transform-style property. transform_style: TransformStyle, @@ -3796,8 +3586,12 @@ struct FlattenedStackingContext { /// True if this stacking context is redundant (i.e. doesn't require a surface) is_redundant: bool, - /// Flags identifying the type of container (among other things) this stacking context is - flags: StackingContextFlags, + /// 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 FlattenedStackingContext { @@ -3806,38 +3600,171 @@ impl FlattenedStackingContext { self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty() } + /// Set up appropriate cluster flags for picture caching on this stacking context. + fn init_picture_caching( + &mut self, + spatial_tree: &SpatialTree, + clip_store: &ClipStore, + quality_settings: &QualitySettings, + ) -> usize { + struct SliceInfo { + cluster_index: usize, + scroll_root: SpatialNodeIndex, + cluster_flags: ClusterFlags, + } + + let mut content_slice_count = 0; + let mut slices: Vec<SliceInfo> = Vec::new(); + + // Step through each cluster, and work out where the slice boundaries should be. + for (cluster_index, cluster) in self.prim_list.clusters.iter().enumerate() { + let scroll_root = spatial_tree.find_scroll_root( + cluster.spatial_node_index, + ); + + // We want to create a slice in the following conditions: + // (1) This cluster is a scrollbar + // (2) Certain conditions when the scroll root changes (see below) + // (3) No slice exists yet + let mut cluster_flags = ClusterFlags::empty(); + + if cluster.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) { + // Scrollbar containers need to ensure that a new slice is + // created both before and after the scrollbar, so that no + // other prims with the same scroll root sneak into this slice. + cluster_flags.insert( + ClusterFlags::CREATE_PICTURE_CACHE_PRE | + ClusterFlags::CREATE_PICTURE_CACHE_POST + ); + } + + let create_new_slice_for_scroll_root = + slices.last().map(|slice| { + match (slice.scroll_root, scroll_root) { + (ROOT_SPATIAL_NODE_INDEX, ROOT_SPATIAL_NODE_INDEX) => { + // Both current slice and this cluster are fixed position, no need to cut + false + } + (ROOT_SPATIAL_NODE_INDEX, _) => { + // A real scroll root is being established, so create a cache slice + true + } + (_, ROOT_SPATIAL_NODE_INDEX) => { + // If quality settings force subpixel AA over performance, skip creating + // a slice for the fixed position element(s) here. + if quality_settings.force_subpixel_aa_where_possible { + return false; + } + + // A fixed position slice is encountered within a scroll root. Only create + // a slice in this case if all the clips referenced by this cluster are also + // fixed position. There's no real point in creating slices for these cases, + // since we'll have to rasterize them as the scrolling clip moves anyway. It + // also allows us to retain subpixel AA in these cases. For these types of + // slices, the intra-slice dirty rect handling typically works quite well + // (a common case is parallax scrolling effects). + for prim_instance in &cluster.prim_instances { + let mut current_clip_chain_id = prim_instance.clip_chain_id; + + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; + let spatial_root = spatial_tree.find_scroll_root(clip_chain_node.spatial_node_index); + if spatial_root != ROOT_SPATIAL_NODE_INDEX { + return false; + } + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + } + + true + } + (curr_scroll_root, scroll_root) => { + // Two scrolling roots - only need a new slice if they differ + curr_scroll_root != scroll_root + } + } + }).unwrap_or(true); + + if create_new_slice_for_scroll_root { + cluster_flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + } + + // Create a new slice if required + if !cluster_flags.is_empty() { + slices.push(SliceInfo { + cluster_index, + scroll_root, + cluster_flags, + }); + } + } + + // If the page would create too many slices (an arbitrary definition where + // it's assumed the GPU memory + compositing overhead would be too high) + // then just create a single picture cache for the entire content. This at + // least means that we can cache small content changes efficiently when + // scrolling isn't occurring. Scrolling regions will be handled reasonably + // efficiently by the dirty rect tracking (since it's likely that if the + // page has so many slices there isn't a single major scroll region). + const MAX_CONTENT_SLICES: usize = 8; + + if slices.len() > MAX_CONTENT_SLICES { + if let Some(cluster) = self.prim_list.clusters.first_mut() { + content_slice_count = 1; + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + cluster.cache_scroll_root = None; + } + } else { + // Walk the list of slices, setting appropriate flags on the clusters which are + // later used during setup_picture_caching. + for slice in slices.drain(..) { + content_slice_count += 1; + let cluster = &mut self.prim_list.clusters[slice.cluster_index]; + // Mark that this cluster creates a picture cache slice + cluster.flags.insert(slice.cluster_flags); + cluster.cache_scroll_root = Some(slice.scroll_root); + } + } + + // Always end the cache at the end of the stacking context, so that we don't + // cache anything from primitives outside this pipeline in the same slice. + if let Some(cluster) = self.prim_list.clusters.last_mut() { + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_POST); + } + + content_slice_count + } + /// Return true if the stacking context isn't needed. pub fn is_redundant( - sc_flags: StackingContextFlags, context_3d: &Picture3DContext<ExtendedPrimitiveInstance>, composite_ops: &CompositeOps, - blit_reason: BlitReason, - parent: Option<&FlattenedStackingContext>, prim_flags: PrimitiveFlags, + blit_reason: BlitReason, + requested_raster_space: RasterSpace, + parent: &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) { + // Any 3d context is required + if let Picture3DContext::In { .. } = context_3d { return false; } - // Any 3d context is required - if let Picture3DContext::In { .. } = context_3d { + // If there are filters / mix-blend-mode + if !composite_ops.filters.is_empty() { return false; } - // If any filters are present that affect the output - if composite_ops.has_valid_filters() { + // If there are svg filters + if !composite_ops.filter_primitives.is_empty() { 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 composite_ops.mix_blend_mode.is_some() && + !parent.prim_list.is_empty() { + return false; } // If backface visibility is explicitly set. @@ -3845,11 +3772,21 @@ impl FlattenedStackingContext { return false; } + // If rasterization space is different + if requested_raster_space != parent.requested_raster_space { + 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 } @@ -3871,10 +3808,13 @@ impl FlattenedStackingContext { .init(PicturePrimitive::new_image( composite_mode.clone(), flat_items_context_3d, + None, true, self.prim_flags, + self.requested_raster_space, mem::replace(&mut self.prim_list, PrimitiveList::empty()), self.spatial_node_index, + None, PictureOptions::default(), )) ); @@ -4043,11 +3983,108 @@ fn process_repeat_size( ) } -fn read_gradient_stops(stops: ItemRange<GradientStop>) -> Vec<GradientStopKey> { - stops.iter().map(|stop| { - GradientStopKey { - offset: stop.offset, - color: stop.color.into(), +/// Given a PrimitiveList and scroll root, construct a tile cache primitive instance +/// that wraps the primitive list. +fn create_tile_cache( + slice: usize, + slice_flags: SliceFlags, + scroll_root: SpatialNodeIndex, + prim_list: PrimitiveList, + background_color: Option<ColorF>, + shared_clips: Vec<ClipInstance>, + interners: &mut Interners, + prim_store: &mut PrimitiveStore, + clip_store: &mut ClipStore, + picture_cache_spatial_nodes: &mut FastHashSet<SpatialNodeIndex>, + frame_builder_config: &FrameBuilderConfig, +) -> PrimitiveInstance { + // Add this spatial node to the list to check for complex transforms + // at the start of a frame build. + picture_cache_spatial_nodes.insert(scroll_root); + + // Now, create a picture with tile caching enabled that will hold all + // of the primitives selected as belonging to the main scroll root. + let pic_key = PictureKey::new( + Picture { + composite_mode_key: PictureCompositeKey::Identity, + }, + ); + + let pic_data_handle = interners + .picture + .intern(&pic_key, || ()); + + // Build a clip-chain for the tile cache, that contains any of the shared clips + // we will apply when drawing the tiles. In all cases provided by Gecko, these + // are rectangle clips with a scale/offset transform only, and get handled as + // a simple local clip rect in the vertex shader. However, this should in theory + // also work with any complex clips, such as rounded rects and image masks, by + // producing a clip mask that is applied to the picture cache tiles. + let mut parent_clip_chain_id = ClipChainId::NONE; + for clip_instance in &shared_clips { + // Add this spatial node to the list to check for complex transforms + // at the start of a frame build. + picture_cache_spatial_nodes.insert(clip_instance.spatial_node_index); + + parent_clip_chain_id = clip_store.add_clip_chain_node( + clip_instance.handle, + clip_instance.spatial_node_index, + parent_clip_chain_id, + ); + } + + let tile_cache = Box::new(TileCacheInstance::new( + slice, + slice_flags, + scroll_root, + background_color, + shared_clips, + parent_clip_chain_id, + frame_builder_config, + )); + + let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image( + Some(PictureCompositeMode::TileCache { }), + Picture3DContext::Out, + None, + true, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + RasterSpace::Screen, + prim_list, + scroll_root, + Some(tile_cache), + PictureOptions::default(), + )); + + PrimitiveInstance::new( + LayoutRect::max_rect(), + PrimitiveInstanceKind::Picture { + data_handle: pic_data_handle, + pic_index: PictureIndex(pic_index), + segment_instance_index: SegmentInstanceIndex::INVALID, + }, + parent_clip_chain_id, + ) +} + +// Helper fn to collect clip handles from a given clip chain. +fn add_clips( + clip_chain_id: ClipChainId, + prim_clips: &mut Vec<ClipInstance>, + clip_store: &ClipStore, + interners: &Interners, +) { + let mut current_clip_chain_id = clip_chain_id; + + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; + + let clip_node_data = &interners.clip[clip_chain_node.handle]; + if let ClipNodeKind::Rectangle = clip_node_data.clip_node_kind { + prim_clips.push(ClipInstance::new(clip_chain_node.handle, clip_chain_node.spatial_node_index)); } - }).collect() + + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } } |