aboutsummaryrefslogtreecommitdiffstats
path: root/third_party/webrender/webrender/src/scene_building.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/webrender/webrender/src/scene_building.rs')
-rw-r--r--third_party/webrender/webrender/src/scene_building.rs2657
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,
+ &current_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,
+ &current_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(&region.rect, spatial_node_index);
+ let snapped_region_rect = snap_to_device.snap_rect(&region.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;
+ }
}