1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 //! Scene building is the phase during which display lists, a representation built for
8 //! serialization, are turned into a scene, webrender's internal representation that is
9 //! suited for rendering frames.
11 //! This phase is happening asynchronously on the scene builder thread.
13 //! # General algorithm
15 //! The important aspects of scene building are:
16 //! - Building up primitive lists (much of the cost of scene building goes here).
17 //! - Creating pictures for content that needs to be rendered into a surface, be it so that
18 //! filters can be applied or for caching purposes.
19 //! - Maintaining a temporary stack of stacking contexts to keep track of some of the
21 //! - Stitching multiple display lists which reference each other (without cycles) into
22 //! a single scene (see build_reference_frame).
23 //! - Interning, which detects when some of the retained state stays the same between display
26 //! The scene builder linearly traverses the serialized display list which is naturally
27 //! ordered back-to-front, accumulating primitives in the top-most stacking context's
29 //! At the end of each stacking context (see pop_stacking_context), its primitive list is
30 //! either handed over to a picture if one is created, or it is concatenated into the parent
31 //! stacking context's primitive list.
33 //! The flow of the algorithm is mostly linear except when handling:
34 //! - shadow stacks (see push_shadow and pop_all_shadows),
35 //! - backdrop filters (see add_backdrop_filter)
38 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags};
39 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
40 use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData, SharedFontInstanceMap};
41 use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
42 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
43 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
44 use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
45 use api::{Shadow, SpaceAndClipInfo, SpatialId, StickyFrameDisplayItem, ImageMask, ItemTag};
46 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
47 use api::{ReferenceTransformBinding, Rotation, FillRule};
49 use crate::image_tiling::simplify_repeated_primitive;
50 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind};
51 use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance, SceneClipInstance};
52 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
53 use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
54 use crate::glyph_rasterizer::FontInstance;
55 use crate::hit_test::HitTestingScene;
56 use crate::intern::Interner;
57 use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter};
58 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions};
59 use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList};
60 use crate::prim_store::{PrimitiveInstance, register_prim_chase_id};
61 use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
62 use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex, PictureIndex};
63 use crate::prim_store::backdrop::Backdrop;
64 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
65 use crate::prim_store::gradient::{
66 GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient,
67 ConicGradientParams, optimize_radial_gradient,
69 use crate::prim_store::image::{Image, YuvImage};
70 use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size};
71 use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey};
72 use crate::prim_store::text_run::TextRun;
73 use crate::render_backend::SceneView;
74 use crate::resource_cache::ImageRequest;
75 use crate::scene::{Scene, ScenePipeline, BuiltScene, SceneStats, StackingContextHelpers};
76 use crate::scene_builder_thread::Interners;
77 use crate::space::SpaceSnapper;
78 use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind};
79 use crate::tile_cache::TileCacheBuilder;
80 use euclid::approxeq::ApproxEq;
81 use std::{f32, mem, usize};
82 use std::collections::vec_deque::VecDeque;
84 use crate::util::{MaxRect, VecHelper};
85 use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
86 use smallvec::SmallVec;
88 /// The offset stack for a given reference frame.
89 struct ReferenceFrameState {
90 /// A stack of current offsets from the current reference frame scope.
91 offsets: Vec<LayoutVector2D>,
94 /// Maps from stacking context layout coordinates into reference frame
95 /// relative coordinates.
96 struct ReferenceFrameMapper {
97 /// A stack of reference frame scopes.
98 frames: Vec<ReferenceFrameState>,
101 impl ReferenceFrameMapper {
103 ReferenceFrameMapper {
105 ReferenceFrameState {
107 LayoutVector2D::zero(),
114 /// Push a new scope. This resets the current offset to zero, and is
115 /// used when a new reference frame or iframe is pushed.
116 fn push_scope(&mut self) {
117 self.frames.push(ReferenceFrameState {
119 LayoutVector2D::zero(),
124 /// Pop a reference frame scope off the stack.
125 fn pop_scope(&mut self) {
126 self.frames.pop().unwrap();
129 /// Push a new offset for the current scope. This is used when
130 /// a new stacking context is pushed.
131 fn push_offset(&mut self, offset: LayoutVector2D) {
132 let frame = self.frames.last_mut().unwrap();
133 let current_offset = *frame.offsets.last().unwrap();
134 frame.offsets.push(current_offset + offset);
137 /// Pop a local stacking context offset from the current scope.
138 fn pop_offset(&mut self) {
139 let frame = self.frames.last_mut().unwrap();
140 frame.offsets.pop().unwrap();
143 /// Retrieve the current offset to allow converting a stacking context
144 /// relative coordinate to be relative to the owing reference frame.
145 /// TODO(gw): We could perhaps have separate coordinate spaces for this,
146 /// however that's going to either mean a lot of changes to
147 /// public API code, or a lot of changes to internal code.
148 /// Before doing that, we should revisit how Gecko would
149 /// prefer to provide coordinates.
150 /// TODO(gw): For now, this includes only the reference frame relative
151 /// offset. Soon, we will expand this to include the initial
152 /// scroll offsets that are now available on scroll nodes. This
153 /// will allow normalizing the coordinates even between display
154 /// lists where APZ has scrolled the content.
155 fn current_offset(&self) -> LayoutVector2D {
156 *self.frames.last().unwrap().offsets.last().unwrap()
160 /// Offsets primitives (and clips) by the external scroll offset
161 /// supplied to scroll nodes.
162 pub struct ScrollOffsetMapper {
163 pub current_spatial_node: SpatialNodeIndex,
164 pub current_offset: LayoutVector2D,
167 impl ScrollOffsetMapper {
170 current_spatial_node: SpatialNodeIndex::INVALID,
171 current_offset: LayoutVector2D::zero(),
175 /// Return the accumulated external scroll offset for a spatial
176 /// node. This caches the last result, which is the common case,
177 /// or defers to the spatial tree to build the value.
178 fn external_scroll_offset(
180 spatial_node_index: SpatialNodeIndex,
181 spatial_tree: &SpatialTree,
182 ) -> LayoutVector2D {
183 if spatial_node_index != self.current_spatial_node {
184 self.current_spatial_node = spatial_node_index;
185 self.current_offset = spatial_tree.external_scroll_offset(spatial_node_index);
192 /// A data structure that keeps track of mapping between API Ids for spatials and the indices
193 /// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives
194 /// and clips during frame building.
196 pub struct NodeIdToIndexMapper {
197 spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>,
200 impl NodeIdToIndexMapper {
201 fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) {
202 let _old_value = self.spatial_node_map.insert(id, index);
203 debug_assert!(_old_value.is_none());
206 fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
207 self.spatial_node_map[&id]
211 #[derive(Debug, Clone, Default)]
212 pub struct CompositeOps {
213 // Requires only a single texture as input (e.g. most filters)
214 pub filters: Vec<Filter>,
215 pub filter_datas: Vec<FilterData>,
216 pub filter_primitives: Vec<FilterPrimitive>,
218 // Requires two source textures (e.g. mix-blend-mode)
219 pub mix_blend_mode: Option<MixBlendMode>,
224 filters: Vec<Filter>,
225 filter_datas: Vec<FilterData>,
226 filter_primitives: Vec<FilterPrimitive>,
227 mix_blend_mode: Option<MixBlendMode>
237 pub fn is_empty(&self) -> bool {
238 self.filters.is_empty() &&
239 self.filter_primitives.is_empty() &&
240 self.mix_blend_mode.is_none()
243 /// Returns true if this CompositeOps contains any filters that affect
244 /// the content (false if no filters, or filters are all no-ops).
245 fn has_valid_filters(&self) -> bool {
246 // For each filter, create a new image with that composite mode.
247 let mut current_filter_data_index = 0;
248 for filter in &self.filters {
250 Filter::ComponentTransfer => {
252 &self.filter_datas[current_filter_data_index];
253 let filter_data = filter_data.sanitize();
254 current_filter_data_index = current_filter_data_index + 1;
255 if filter_data.is_identity() {
262 if filter.is_noop() {
271 if !self.filter_primitives.is_empty() {
279 /// Represents the current input for a picture chain builder (either a
280 /// prim list from the stacking context, or a wrapped picture instance).
283 prim_list: PrimitiveList,
286 instance: PrimitiveInstance,
290 /// Helper struct to build picture chains during scene building from
291 /// a flattened stacking context struct.
292 struct PictureChainBuilder {
293 /// The current input source for the next picture
294 current: PictureSource,
296 /// Positioning node for this picture chain
297 spatial_node_index: SpatialNodeIndex,
298 /// Prim flags for any pictures in this chain
299 flags: PrimitiveFlags,
302 impl PictureChainBuilder {
303 /// Create a new picture chain builder, from a primitive list
305 prim_list: PrimitiveList,
306 flags: PrimitiveFlags,
307 spatial_node_index: SpatialNodeIndex,
309 PictureChainBuilder {
310 current: PictureSource::PrimitiveList {
318 /// Create a new picture chain builder, from a picture wrapper instance
320 instance: PrimitiveInstance,
321 flags: PrimitiveFlags,
322 spatial_node_index: SpatialNodeIndex,
324 PictureChainBuilder {
325 current: PictureSource::WrappedPicture {
333 /// Wrap the existing content with a new picture with the given parameters
337 composite_mode: PictureCompositeMode,
338 context_3d: Picture3DContext<OrderedPictureChild>,
339 options: PictureOptions,
340 interners: &mut Interners,
341 prim_store: &mut PrimitiveStore,
342 ) -> PictureChainBuilder {
343 let prim_list = match self.current {
344 PictureSource::PrimitiveList { prim_list } => {
347 PictureSource::WrappedPicture { instance } => {
348 let mut prim_list = PrimitiveList::empty();
353 self.spatial_node_index,
361 let pic_index = PictureIndex(prim_store.pictures
363 .init(PicturePrimitive::new_image(
364 Some(composite_mode.clone()),
369 self.spatial_node_index,
374 let instance = create_prim_instance(
376 Some(composite_mode).into(),
381 PictureChainBuilder {
382 current: PictureSource::WrappedPicture {
385 spatial_node_index: self.spatial_node_index,
390 /// Finish building this picture chain. Set the clip chain on the outermost picture
393 clip_chain_id: ClipChainId,
394 interners: &mut Interners,
395 prim_store: &mut PrimitiveStore,
396 ) -> PrimitiveInstance {
398 PictureSource::WrappedPicture { mut instance } => {
399 instance.clip_set.clip_chain_id = clip_chain_id;
402 PictureSource::PrimitiveList { prim_list } => {
403 // If no picture was created for this stacking context, create a
404 // pass-through wrapper now. This is only needed in 1-2 edge cases
405 // now, and will be removed as a follow up.
406 let pic_index = PictureIndex(prim_store.pictures
408 .init(PicturePrimitive::new_image(
410 Picture3DContext::Out,
414 self.spatial_node_index,
415 PictureOptions::default(),
419 create_prim_instance(
432 pub struct SliceFlags : u8 {
433 /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER
434 const IS_SCROLLBAR = 1;
438 /// A structure that converts a serialized display list into a form that WebRender
439 /// can use to later build a frame. This structure produces a BuiltScene. Public
440 /// members are typically those that are destructured into the BuiltScene.
441 pub struct SceneBuilder<'a> {
442 /// The scene that we are currently building.
445 /// The map of all font instances.
446 font_instances: SharedFontInstanceMap,
448 /// The data structure that converts between ClipId/SpatialId and the various
449 /// index types that the SpatialTree uses.
450 id_to_index_mapper: NodeIdToIndexMapper,
452 /// A stack of stacking context properties.
453 sc_stack: Vec<FlattenedStackingContext>,
455 /// Stack of spatial node indices forming containing block for 3d contexts
456 containing_block_stack: Vec<SpatialNodeIndex>,
458 /// Stack of requested raster spaces for stacking contexts
459 raster_space_stack: Vec<RasterSpace>,
461 /// Maintains state for any currently active shadows
462 pending_shadow_items: VecDeque<ShadowItem>,
464 /// The SpatialTree that we are currently building during building.
465 pub spatial_tree: SpatialTree,
467 /// The store of primitives.
468 pub prim_store: PrimitiveStore,
470 /// Information about all primitives involved in hit testing.
471 pub hit_testing_scene: HitTestingScene,
473 /// The store which holds all complex clipping information.
474 pub clip_store: ClipStore,
476 /// The configuration to use for the FrameBuilder. We consult this in
477 /// order to determine the default font.
478 pub config: FrameBuilderConfig,
480 /// Reference to the set of data that is interned across display lists.
481 interners: &'a mut Interners,
483 /// Helper struct to map stacking context coords <-> reference frame coords.
484 rf_mapper: ReferenceFrameMapper,
486 /// Helper struct to map spatial nodes to external scroll offsets.
487 external_scroll_mapper: ScrollOffsetMapper,
489 /// The current recursion depth of iframes encountered. Used to restrict picture
490 /// caching slices to only the top-level content frame.
491 iframe_size: Vec<LayoutSize>,
493 /// The current quality / performance settings for this scene.
494 quality_settings: QualitySettings,
496 /// Maintains state about the list of tile caches being built for this scene.
497 tile_cache_builder: TileCacheBuilder,
499 /// A helper struct to snap local rects in device space. During frame
500 /// building we may establish new raster roots, however typically that is in
501 /// cases where we won't be applying snapping (e.g. has perspective), or in
502 /// edge cases (e.g. SVG filter) where we can accept slightly incorrect
503 /// behaviour in favour of getting the common case right.
504 snap_to_device: SpaceSnapper,
507 impl<'a> SceneBuilder<'a> {
510 font_instances: SharedFontInstanceMap,
512 frame_builder_config: &FrameBuilderConfig,
513 interners: &mut Interners,
516 profile_scope!("build_scene");
518 // We checked that the root pipeline is available on the render backend.
519 let root_pipeline_id = scene.root_pipeline_id.unwrap();
520 let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
522 let background_color = root_pipeline
524 .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
526 let device_pixel_scale = view.accumulated_scale_factor_for_snapping();
527 let spatial_tree = SpatialTree::new();
529 let snap_to_device = SpaceSnapper::new(
530 ROOT_SPATIAL_NODE_INDEX,
534 let mut builder = SceneBuilder {
538 config: *frame_builder_config,
539 id_to_index_mapper: NodeIdToIndexMapper::default(),
540 hit_testing_scene: HitTestingScene::new(&stats.hit_test_stats),
541 pending_shadow_items: VecDeque::new(),
542 sc_stack: Vec::new(),
543 containing_block_stack: Vec::new(),
544 raster_space_stack: vec![RasterSpace::Screen],
545 prim_store: PrimitiveStore::new(&stats.prim_store_stats),
546 clip_store: ClipStore::new(),
548 rf_mapper: ReferenceFrameMapper::new(),
549 external_scroll_mapper: ScrollOffsetMapper::new(),
550 iframe_size: Vec::new(),
551 quality_settings: view.quality_settings,
552 tile_cache_builder: TileCacheBuilder::new(),
556 builder.build_all(&root_pipeline);
558 // Construct the picture cache primitive instance(s) from the tile cache builder
559 let (tile_cache_config, tile_cache_pictures) = builder.tile_cache_builder.build(
561 &mut builder.clip_store,
562 &mut builder.prim_store,
566 has_root_pipeline: scene.has_root_pipeline(),
567 pipeline_epochs: scene.pipeline_epochs.clone(),
568 output_rect: view.device_rect.size.into(),
570 hit_testing_scene: Arc::new(builder.hit_testing_scene),
571 spatial_tree: builder.spatial_tree,
572 prim_store: builder.prim_store,
573 clip_store: builder.clip_store,
574 config: builder.config,
580 /// Retrieve the current offset to allow converting a stacking context
581 /// relative coordinate to be relative to the owing reference frame,
582 /// also considering any external scroll offset on the provided
586 spatial_node_index: SpatialNodeIndex,
587 ) -> LayoutVector2D {
588 // Get the current offset from stacking context <-> reference frame space.
589 let rf_offset = self.rf_mapper.current_offset();
591 // Get the external scroll offset, if applicable.
592 let scroll_offset = self
593 .external_scroll_mapper
594 .external_scroll_offset(
599 rf_offset + scroll_offset
602 fn build_all(&mut self, root_pipeline: &ScenePipeline) {
603 enum ContextKind<'a> {
606 sc_info: StackingContextInfo,
610 parent_traversal: BuiltDisplayListIter<'a>,
613 struct BuildContext<'a> {
614 pipeline_id: PipelineId,
615 kind: ContextKind<'a>,
618 let root_clip_id = ClipId::root(root_pipeline.pipeline_id);
619 self.clip_store.register_clip_template(root_clip_id, root_clip_id, &[]);
620 self.clip_store.push_clip_root(Some(root_clip_id), false);
622 root_pipeline.pipeline_id,
623 &root_pipeline.viewport_size,
626 let mut stack = vec![BuildContext {
627 pipeline_id: root_pipeline.pipeline_id,
628 kind: ContextKind::Root,
630 let mut traversal = root_pipeline.display_list.iter();
632 'outer: while let Some(bc) = stack.pop() {
634 let item = match traversal.next() {
640 DisplayItem::PushStackingContext(ref info) => {
641 profile_scope!("build_stacking_context");
642 let spatial_node_index = self.get_space(info.spatial_id);
643 let mut subtraversal = item.sub_iter();
644 // Avoid doing unnecessary work for empty stacking contexts.
645 if subtraversal.current_stacking_context_empty() {
646 subtraversal.skip_current_stacking_context();
647 traversal = subtraversal;
651 let composition_operations = CompositeOps::new(
652 filter_ops_for_compositing(item.filters()),
653 filter_datas_for_compositing(item.filter_datas()),
654 filter_primitives_for_compositing(item.filter_primitives()),
655 info.stacking_context.mix_blend_mode_for_compositing(),
658 let sc_info = self.push_stacking_context(
659 composition_operations,
660 info.stacking_context.transform_style,
663 info.stacking_context.clip_id,
664 info.stacking_context.raster_space,
665 info.stacking_context.flags,
669 self.rf_mapper.push_offset(info.origin.to_vector());
670 let new_context = BuildContext {
671 pipeline_id: bc.pipeline_id,
672 kind: ContextKind::StackingContext {
677 stack.push(new_context);
679 subtraversal.merge_debug_stats_from(&mut traversal);
680 traversal = subtraversal;
683 DisplayItem::PushReferenceFrame(ref info) => {
684 profile_scope!("build_reference_frame");
685 let parent_space = self.get_space(info.parent_spatial_id);
686 let mut subtraversal = item.sub_iter();
687 let current_offset = self.current_offset(parent_space);
689 let transform = match info.reference_frame.transform {
690 ReferenceTransformBinding::Static { binding } => binding,
691 ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => {
692 let content_size = &self.iframe_size.last().unwrap();
694 let mut transform = if let Some(scale_from) = scale_from {
695 // If we have a 90/270 degree rotation, then scale_from
696 // and content_size are in different coordinate spaces and
697 // we need to swap width/height for them to be correct.
700 Rotation::Degree180 => {
701 LayoutTransform::scale(
702 content_size.width / scale_from.width,
703 content_size.height / scale_from.height,
708 Rotation::Degree270 => {
709 LayoutTransform::scale(
710 content_size.height / scale_from.width,
711 content_size.width / scale_from.height,
718 LayoutTransform::identity()
722 let content_size = &self.iframe_size.last().unwrap();
723 transform = transform
724 .then_translate(LayoutVector3D::new(0.0, content_size.height, 0.0))
725 .pre_scale(1.0, -1.0, 1.0);
728 let rotate = rotation.to_matrix(**content_size);
729 let transform = transform.then(&rotate);
731 PropertyBinding::Value(transform)
735 self.push_reference_frame(
736 info.reference_frame.id,
739 info.reference_frame.transform_style,
741 info.reference_frame.kind,
742 current_offset + info.origin.to_vector(),
745 self.rf_mapper.push_scope();
746 let new_context = BuildContext {
747 pipeline_id: bc.pipeline_id,
748 kind: ContextKind::ReferenceFrame,
751 stack.push(new_context);
753 subtraversal.merge_debug_stats_from(&mut traversal);
754 traversal = subtraversal;
757 DisplayItem::PopReferenceFrame |
758 DisplayItem::PopStackingContext => break,
759 DisplayItem::Iframe(ref info) => {
760 profile_scope!("iframe");
762 let space = self.get_space(info.space_and_clip.spatial_id);
763 let (size, subtraversal) = match self.push_iframe(info, space) {
768 // If this is a root iframe, force a new tile cache both before and after
769 // adding primitives for this iframe.
770 if self.iframe_size.is_empty() {
771 self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
774 self.rf_mapper.push_scope();
775 self.iframe_size.push(size);
777 let new_context = BuildContext {
778 pipeline_id: info.pipeline_id,
779 kind: ContextKind::Iframe {
780 parent_traversal: mem::replace(&mut traversal, subtraversal),
784 stack.push(new_context);
788 self.build_item(item, bc.pipeline_id);
794 ContextKind::Root => {}
795 ContextKind::StackingContext { sc_info } => {
796 self.rf_mapper.pop_offset();
797 self.pop_stacking_context(sc_info);
799 ContextKind::ReferenceFrame => {
800 self.rf_mapper.pop_scope();
802 ContextKind::Iframe { parent_traversal } => {
803 self.iframe_size.pop();
804 self.rf_mapper.pop_scope();
806 self.clip_store.pop_clip_root();
807 if self.iframe_size.is_empty() {
808 self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
811 traversal = parent_traversal;
815 // TODO: factor this out to be part of capture
816 if cfg!(feature = "display_list_stats") {
817 let stats = traversal.debug_stats();
818 let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
819 println!("item, total count, total bytes, % of DL bytes, bytes per item");
820 for (label, stats) in stats {
821 println!("{}, {}, {}kb, {}%, {}",
824 stats.num_bytes / 1000,
825 ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
826 stats.num_bytes / stats.total_count.max(1));
832 self.clip_store.pop_clip_root();
833 debug_assert!(self.sc_stack.is_empty());
836 fn build_sticky_frame(
838 info: &StickyFrameDisplayItem,
839 parent_node_index: SpatialNodeIndex,
841 let current_offset = self.current_offset(parent_node_index);
842 let frame_rect = info.bounds.translate(current_offset);
843 let sticky_frame_info = StickyFrameInfo::new(
846 info.vertical_offset_bounds,
847 info.horizontal_offset_bounds,
848 info.previously_applied_offset,
851 let index = self.spatial_tree.add_sticky_frame(
854 info.id.pipeline_id(),
856 self.id_to_index_mapper.add_spatial_node(info.id, index);
859 fn build_scroll_frame(
861 info: &ScrollFrameDisplayItem,
862 parent_node_index: SpatialNodeIndex,
863 pipeline_id: PipelineId,
865 let current_offset = self.current_offset(parent_node_index);
866 let clip_region = ClipRegion::create_for_clip_node_with_local_clip(
870 // Just use clip rectangle as the frame rect for this scroll frame.
871 // This is useful when calculating scroll extents for the
872 // SpatialNode::scroll(..) API as well as for properly setting sticky
873 // positioning offsets.
874 let frame_rect = clip_region.main;
875 let content_size = info.content_rect.size;
877 self.add_clip_node(info.clip_id, &info.parent_space_and_clip, clip_region);
879 self.add_scroll_frame(
880 info.scroll_frame_id,
886 info.scroll_sensitivity,
887 ScrollFrameKind::Explicit,
888 info.external_scroll_offset,
894 info: &IframeDisplayItem,
895 spatial_node_index: SpatialNodeIndex,
896 ) -> Option<(LayoutSize, BuiltDisplayListIter<'a>)> {
897 let iframe_pipeline_id = info.pipeline_id;
898 let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
899 Some(pipeline) => pipeline,
901 debug_assert!(info.ignore_missing_pipeline);
906 let current_offset = self.current_offset(spatial_node_index);
908 ClipId::root(iframe_pipeline_id),
909 &info.space_and_clip,
910 ClipRegion::create_for_clip_node_with_local_clip(
916 self.clip_store.push_clip_root(
917 Some(ClipId::root(iframe_pipeline_id)),
921 let bounds = self.snap_rect(
922 &info.bounds.translate(current_offset),
926 let spatial_node_index = self.push_reference_frame(
927 SpatialId::root_reference_frame(iframe_pipeline_id),
928 Some(spatial_node_index),
930 TransformStyle::Flat,
931 PropertyBinding::Value(LayoutTransform::identity()),
932 ReferenceFrameKind::Transform {
933 is_2d_scale_translation: false,
936 bounds.origin.to_vector(),
939 let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
940 let is_root_pipeline = self.iframe_size.is_empty();
942 self.add_scroll_frame(
943 SpatialId::root_scroll_node(iframe_pipeline_id),
945 ExternalScrollId(0, iframe_pipeline_id),
949 ScrollSensitivity::ScriptAndInputEvents,
950 ScrollFrameKind::PipelineRoot {
953 LayoutVector2D::zero(),
956 Some((bounds.size, pipeline.display_list.iter()))
961 spatial_id: SpatialId,
962 ) -> SpatialNodeIndex {
963 self.id_to_index_mapper.get_spatial_node_index(spatial_id)
970 self.clip_store.get_or_build_clip_chain_id(clip_id)
973 fn process_common_properties(
975 common: &CommonItemProperties,
976 bounds: Option<&LayoutRect>,
977 ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) {
978 let spatial_node_index = self.get_space(common.spatial_id);
979 let clip_chain_id = self.get_clip_chain(common.clip_id);
981 let current_offset = self.current_offset(spatial_node_index);
983 let unsnapped_clip_rect = common.clip_rect.translate(current_offset);
984 let clip_rect = self.snap_rect(
985 &unsnapped_clip_rect,
989 let unsnapped_rect = bounds.map(|bounds| {
990 bounds.translate(current_offset)
993 // If no bounds rect is given, default to clip rect.
994 let rect = unsnapped_rect.map_or(clip_rect, |bounds| {
1001 let layout = LayoutPrimitiveInfo {
1004 flags: common.flags,
1007 (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id)
1010 fn process_common_properties_with_bounds(
1012 common: &CommonItemProperties,
1013 bounds: &LayoutRect,
1014 ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) {
1015 self.process_common_properties(
1024 target_spatial_node: SpatialNodeIndex,
1026 self.snap_to_device.set_target_spatial_node(
1027 target_spatial_node,
1030 self.snap_to_device.snap_rect(rect)
1035 item: DisplayItemRef,
1036 pipeline_id: PipelineId,
1038 match *item.item() {
1039 DisplayItem::Image(ref info) => {
1040 profile_scope!("image");
1042 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1054 info.image_rendering,
1059 DisplayItem::RepeatingImage(ref info) => {
1060 profile_scope!("repeating_image");
1062 let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1067 let stretch_size = process_repeat_size(
1080 info.image_rendering,
1085 DisplayItem::YuvImage(ref info) => {
1086 profile_scope!("yuv_image");
1088 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1101 info.image_rendering,
1104 DisplayItem::Text(ref info) => {
1105 profile_scope!("text");
1107 // TODO(aosmond): Snapping text primitives does not make much sense, given the
1108 // primitive bounds and clip are supposed to be conservative, not definitive.
1109 // E.g. they should be able to grow and not impact the output. However there
1110 // are subtle interactions between the primitive origin and the glyph offset
1111 // which appear to be significant (presumably due to some sort of accumulated
1112 // error throughout the layers). We should fix this at some point.
1113 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1128 DisplayItem::Rectangle(ref info) => {
1129 profile_scope!("rect");
1131 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1141 PrimitiveKeyKind::Rectangle {
1142 color: info.color.into(),
1146 DisplayItem::HitTest(ref info) => {
1147 profile_scope!("hit_test");
1149 // TODO(gw): We could skip building the clip-chain here completely, as it's not used by
1151 let (layout, _, spatial_node_index, _) = self.process_common_properties(
1156 // Don't add transparent rectangles to the draw list,
1157 // but do consider them for hit testing. This allows
1158 // specifying invisible hit testing areas.
1159 self.add_primitive_to_hit_testing_list(
1162 info.common.clip_id,
1166 DisplayItem::ClearRectangle(ref info) => {
1167 profile_scope!("clear");
1169 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1174 self.add_clear_rectangle(
1180 DisplayItem::Line(ref info) => {
1181 profile_scope!("line");
1183 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1192 info.wavy_line_thickness,
1198 DisplayItem::Gradient(ref info) => {
1199 profile_scope!("gradient");
1201 let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1206 let tile_size = process_repeat_size(
1212 if let Some(prim_key_kind) = self.create_linear_gradient_prim(
1214 info.gradient.start_point,
1215 info.gradient.end_point,
1216 item.gradient_stops(),
1217 info.gradient.extend_mode,
1222 self.add_nonshadowable_primitive(
1231 DisplayItem::RadialGradient(ref info) => {
1232 profile_scope!("radial");
1234 let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1239 let stops = read_gradient_stops(item.gradient_stops());
1241 let mut tile_size = process_repeat_size(
1247 let mut prim_rect = layout.rect;
1248 let mut tile_spacing = info.tile_spacing;
1249 let mut center = info.gradient.center;
1250 optimize_radial_gradient(
1255 info.gradient.radius,
1256 info.gradient.extend_mode,
1258 &mut |solid_rect, color| {
1259 self.add_nonshadowable_primitive(
1262 &LayoutPrimitiveInfo {
1267 PrimitiveKeyKind::Rectangle { color: PropertyBinding::Value(color) },
1272 // TODO: create_radial_gradient_prim already calls
1273 // this, but it leaves the info variable that is
1274 // passed to add_nonshadowable_primitive unmodified
1275 // which can cause issues.
1276 simplify_repeated_primitive(&tile_size, &mut tile_spacing, &mut prim_rect);
1278 if !tile_size.to_i32().is_empty() {
1279 layout.rect = prim_rect;
1280 let prim_key_kind = self.create_radial_gradient_prim(
1283 info.gradient.start_offset * info.gradient.radius.width,
1284 info.gradient.end_offset * info.gradient.radius.width,
1285 info.gradient.radius.width / info.gradient.radius.height,
1287 info.gradient.extend_mode,
1293 self.add_nonshadowable_primitive(
1302 DisplayItem::ConicGradient(ref info) => {
1303 profile_scope!("conic");
1305 let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1310 let tile_size = process_repeat_size(
1316 if !tile_size.to_i32().is_empty() {
1317 let prim_key_kind = self.create_conic_gradient_prim(
1319 info.gradient.center,
1320 info.gradient.angle,
1321 info.gradient.start_offset,
1322 info.gradient.end_offset,
1323 item.gradient_stops(),
1324 info.gradient.extend_mode,
1330 self.add_nonshadowable_primitive(
1339 DisplayItem::BoxShadow(ref info) => {
1340 profile_scope!("box_shadow");
1342 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1347 self.add_box_shadow(
1359 DisplayItem::Border(ref info) => {
1360 profile_scope!("border");
1362 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1372 item.gradient_stops(),
1375 DisplayItem::ImageMaskClip(ref info) => {
1376 profile_scope!("image_clip");
1378 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
1379 let current_offset = self.current_offset(parent_space);
1381 let image_mask = ImageMask {
1382 rect: info.image_mask.rect.translate(current_offset),
1386 self.add_image_mask_clip_node(
1388 &info.parent_space_and_clip,
1394 DisplayItem::RoundedRectClip(ref info) => {
1395 profile_scope!("rounded_clip");
1397 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
1398 let current_offset = self.current_offset(parent_space);
1400 self.add_rounded_rect_clip_node(
1402 &info.parent_space_and_clip,
1407 DisplayItem::RectClip(ref info) => {
1408 profile_scope!("rect_clip");
1410 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
1411 let current_offset = self.current_offset(parent_space);
1412 let clip_rect = info.clip_rect.translate(current_offset);
1414 self.add_rect_clip_node(
1416 &info.parent_space_and_clip,
1420 DisplayItem::Clip(ref info) => {
1421 profile_scope!("clip");
1423 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
1424 let current_offset = self.current_offset(parent_space);
1425 let clip_region = ClipRegion::create_for_clip_node(
1427 item.complex_clip().iter(),
1430 self.add_clip_node(info.id, &info.parent_space_and_clip, clip_region);
1432 DisplayItem::ClipChain(ref info) => {
1433 profile_scope!("clip_chain");
1435 let parent = info.parent.map_or(ClipId::root(pipeline_id), |id| ClipId::ClipChain(id));
1436 let mut clips: SmallVec<[SceneClipInstance; 4]> = SmallVec::new();
1438 for clip_item in item.clip_chain_items() {
1439 let template = self.clip_store.get_template(clip_item);
1440 clips.extend_from_slice(&template.clips);
1443 self.clip_store.register_clip_template(
1444 ClipId::ClipChain(info.id),
1449 DisplayItem::ScrollFrame(ref info) => {
1450 profile_scope!("scrollframe");
1452 let parent_space = self.get_space(info.parent_space_and_clip.spatial_id);
1453 self.build_scroll_frame(
1459 DisplayItem::StickyFrame(ref info) => {
1460 profile_scope!("stickyframe");
1462 let parent_space = self.get_space(info.parent_spatial_id);
1463 self.build_sticky_frame(
1468 DisplayItem::BackdropFilter(ref info) => {
1469 profile_scope!("backdrop");
1471 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties(
1476 let filters = filter_ops_for_compositing(item.filters());
1477 let filter_datas = filter_datas_for_compositing(item.filter_datas());
1478 let filter_primitives = filter_primitives_for_compositing(item.filter_primitives());
1480 self.add_backdrop_filter(
1490 // Do nothing; these are dummy items for the display list parser
1491 DisplayItem::SetGradientStops |
1492 DisplayItem::SetFilterOps |
1493 DisplayItem::SetFilterData |
1494 DisplayItem::SetFilterPrimitives |
1495 DisplayItem::SetPoints => {}
1497 // Special items that are handled in the parent method
1498 DisplayItem::PushStackingContext(..) |
1499 DisplayItem::PushReferenceFrame(..) |
1500 DisplayItem::PopReferenceFrame |
1501 DisplayItem::PopStackingContext |
1502 DisplayItem::Iframe(_) => {
1503 unreachable!("Handled in `build_all`")
1506 DisplayItem::ReuseItems(key) |
1507 DisplayItem::RetainedItems(key) => {
1508 unreachable!("Iterator logic error: {:?}", key);
1511 DisplayItem::PushShadow(info) => {
1512 profile_scope!("push_shadow");
1514 let spatial_node_index = self.get_space(info.space_and_clip.spatial_id);
1515 let clip_chain_id = self.get_clip_chain(
1516 info.space_and_clip.clip_id,
1523 info.should_inflate,
1526 DisplayItem::PopAllShadows => {
1527 profile_scope!("pop_all_shadows");
1529 self.pop_all_shadows();
1534 // Given a list of clip sources, a positioning node and
1535 // a parent clip chain, return a new clip chain entry.
1536 // If the supplied list of clip sources is empty, then
1537 // just return the parent clip chain id directly.
1538 fn build_clip_chain(
1540 clip_items: Vec<ClipItemKey>,
1541 spatial_node_index: SpatialNodeIndex,
1542 parent_clip_chain_id: ClipChainId,
1544 if clip_items.is_empty() {
1545 parent_clip_chain_id
1547 let mut clip_chain_id = parent_clip_chain_id;
1549 for item in clip_items {
1550 // Intern this clip item, and store the handle
1551 // in the clip chain node.
1552 let handle = self.interners
1556 clip_node_kind: item.kind.node_kind(),
1560 clip_chain_id = self.clip_store.add_clip_chain_node(
1571 /// Create a primitive and add it to the prim store. This method doesn't
1572 /// add the primitive to the draw list, so can be used for creating
1575 /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list`
1576 fn create_primitive<P>(
1578 info: &LayoutPrimitiveInfo,
1579 spatial_node_index: SpatialNodeIndex,
1580 clip_chain_id: ClipChainId,
1582 ) -> PrimitiveInstance
1584 P: InternablePrimitive,
1585 Interners: AsMut<Interner<P>>,
1587 // Build a primitive key.
1588 let prim_key = prim.into_key(info);
1590 let current_offset = self.current_offset(spatial_node_index);
1591 let interner = self.interners.as_mut();
1592 let prim_data_handle = interner
1593 .intern(&prim_key, || ());
1595 let instance_kind = P::make_instance_kind(
1598 &mut self.prim_store,
1602 PrimitiveInstance::new(
1609 pub fn add_primitive_to_hit_testing_list(
1611 info: &LayoutPrimitiveInfo,
1612 spatial_node_index: SpatialNodeIndex,
1616 self.hit_testing_scene.add_item(
1625 /// Add an already created primitive to the draw lists.
1626 pub fn add_primitive_to_draw_list(
1628 prim_instance: PrimitiveInstance,
1629 prim_rect: LayoutRect,
1630 spatial_node_index: SpatialNodeIndex,
1631 flags: PrimitiveFlags,
1633 // Add primitive to the top-most stacking context on the stack.
1634 if prim_instance.is_chased() {
1635 println!("\tadded to stacking context at {}", self.sc_stack.len());
1638 // If we have a valid stacking context, the primitive gets added to that.
1639 // Otherwise, it gets added to a top-level picture cache slice.
1641 match self.sc_stack.last_mut() {
1642 Some(stacking_context) => {
1643 stacking_context.prim_list.add_prim(
1651 self.tile_cache_builder.add_prim(
1660 &self.quality_settings,
1666 /// Convenience interface that creates a primitive entry and adds it
1667 /// to the draw list.
1668 fn add_nonshadowable_primitive<P>(
1670 spatial_node_index: SpatialNodeIndex,
1671 clip_chain_id: ClipChainId,
1672 info: &LayoutPrimitiveInfo,
1673 clip_items: Vec<ClipItemKey>,
1677 P: InternablePrimitive + IsVisible,
1678 Interners: AsMut<Interner<P>>,
1680 if prim.is_visible() {
1681 let clip_chain_id = self.build_clip_chain(
1686 self.add_prim_to_draw_list(
1695 pub fn add_primitive<P>(
1697 spatial_node_index: SpatialNodeIndex,
1698 clip_chain_id: ClipChainId,
1699 info: &LayoutPrimitiveInfo,
1700 clip_items: Vec<ClipItemKey>,
1704 P: InternablePrimitive + IsVisible,
1705 Interners: AsMut<Interner<P>>,
1706 ShadowItem: From<PendingPrimitive<P>>
1708 // If a shadow context is not active, then add the primitive
1709 // directly to the parent picture.
1710 if self.pending_shadow_items.is_empty() {
1711 self.add_nonshadowable_primitive(
1719 debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives");
1721 // There is an active shadow context. Store as a pending primitive
1722 // for processing during pop_all_shadows.
1723 self.pending_shadow_items.push_back(PendingPrimitive {
1732 fn add_prim_to_draw_list<P>(
1734 info: &LayoutPrimitiveInfo,
1735 spatial_node_index: SpatialNodeIndex,
1736 clip_chain_id: ClipChainId,
1740 P: InternablePrimitive,
1741 Interners: AsMut<Interner<P>>,
1743 let prim_instance = self.create_primitive(
1749 self.register_chase_primitive_by_rect(
1753 self.add_primitive_to_draw_list(
1761 /// If no stacking contexts are present (i.e. we are adding prims to a tile
1762 /// cache), set a barrier to force creation of a slice before the next prim
1763 fn add_tile_cache_barrier_if_needed(
1765 slice_flags: SliceFlags,
1767 if self.sc_stack.is_empty() {
1768 // Shadows can only exist within a stacking context
1769 assert!(self.pending_shadow_items.is_empty());
1771 self.tile_cache_builder.add_tile_cache_barrier(slice_flags);
1775 /// Push a new stacking context. Returns context that must be passed to pop_stacking_context().
1776 fn push_stacking_context(
1778 composite_ops: CompositeOps,
1779 transform_style: TransformStyle,
1780 prim_flags: PrimitiveFlags,
1781 spatial_node_index: SpatialNodeIndex,
1782 clip_id: Option<ClipId>,
1783 requested_raster_space: RasterSpace,
1784 flags: StackingContextFlags,
1785 pipeline_id: PipelineId,
1786 ) -> StackingContextInfo {
1787 profile_scope!("push_stacking_context");
1789 // Push current requested raster space on stack for prims to access
1790 self.raster_space_stack.push(requested_raster_space);
1792 // Get the transform-style of the parent stacking context,
1793 // which determines if we *might* need to draw this on
1794 // an intermediate surface for plane splitting purposes.
1795 let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
1796 Some(ref mut sc) if sc.is_3d() => {
1797 let flat_items_context_3d = match sc.context_3d {
1798 Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
1802 Picture3DContext::Out => panic!("Unexpected out of 3D context"),
1804 // Cut the sequence of flat children before starting a child stacking context,
1805 // so that the relative order between them and our current SC is preserved.
1806 let extra_instance = sc.cut_item_sequence(
1807 &mut self.prim_store,
1808 &mut self.interners,
1809 Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)),
1810 flat_items_context_3d,
1812 let extra_instance = extra_instance.map(|(_, instance)| {
1813 ExtendedPrimitiveInstance {
1815 spatial_node_index: sc.spatial_node_index,
1816 flags: sc.prim_flags,
1819 (true, extra_instance)
1824 if let Some(instance) = extra_3d_instance {
1825 self.add_primitive_instance_to_3d_root(instance);
1828 // If this is preserve-3d *or* the parent is, then this stacking
1829 // context is participating in the 3d rendering context. In that
1830 // case, hoist the picture up to the 3d rendering context
1831 // container, so that it's rendered as a sibling with other
1832 // elements in this context.
1833 let participating_in_3d_context =
1834 composite_ops.is_empty() &&
1835 (parent_is_3d || transform_style == TransformStyle::Preserve3D);
1837 let context_3d = if participating_in_3d_context {
1838 // Get the spatial node index of the containing block, which
1839 // defines the context of backface-visibility.
1840 let ancestor_index = self.containing_block_stack
1843 .unwrap_or(ROOT_SPATIAL_NODE_INDEX);
1845 Picture3DContext::In {
1846 root_data: if parent_is_3d {
1854 Picture3DContext::Out
1857 // Force an intermediate surface if the stacking context has a
1858 // complex clip node. In the future, we may decide during
1859 // prepare step to skip the intermediate surface if the
1860 // clip node doesn't affect the stacking context rect.
1861 let mut blit_reason = BlitReason::empty();
1863 if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) {
1864 blit_reason |= BlitReason::ISOLATE;
1867 // If this stacking context has any complex clips, we need to draw it
1868 // to an off-screen surface.
1869 if let Some(clip_id) = clip_id {
1870 if self.clip_store.has_complex_clips(clip_id) {
1871 blit_reason |= BlitReason::CLIP;
1875 let is_redundant = FlattenedStackingContext::is_redundant(
1880 self.sc_stack.last(),
1884 // If stacking context is a scrollbar, force a new slice for the primitives
1885 // within. The stacking context will be redundant and removed by above check.
1886 let set_tile_cache_barrier = prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER);
1888 if set_tile_cache_barrier {
1889 self.add_tile_cache_barrier_if_needed(SliceFlags::IS_SCROLLBAR);
1892 let mut sc_info = StackingContextInfo {
1893 pop_hit_testing_clip: false,
1894 pop_stacking_context: false,
1895 pop_containing_block: false,
1896 set_tile_cache_barrier,
1899 // If this is not 3d, then it establishes an ancestor root for child 3d contexts.
1900 if !participating_in_3d_context {
1901 sc_info.pop_containing_block = true;
1902 self.containing_block_stack.push(spatial_node_index);
1905 // If this stacking context is redundant, we don't care about getting a clip-chain for it.
1906 // However, if we _do_ have a clip, we must build it here before the `push_clip_root`
1907 // calls below, to ensure we get the clips for drawing this stacking context itself.
1908 let clip_chain_id = if is_redundant {
1911 // Get a clip-chain for this stacking context - even if the stacking context
1912 // itself has no clips, it's possible that there are clips to collect from
1913 // the previous clip-chain builder.
1914 let clip_id = clip_id.unwrap_or(ClipId::root(pipeline_id));
1915 self.clip_store.get_or_build_clip_chain_id(clip_id)
1918 // If this has a valid clip, register with the hit-testing scene
1919 if let Some(clip_id) = clip_id {
1920 self.hit_testing_scene.push_clip(clip_id);
1921 sc_info.pop_hit_testing_clip = true;
1924 // If this stacking context is redundant (prims will be pushed into
1925 // the parent during pop) but it has a valid clip, then we need to
1926 // add that clip to the current clip chain builder, so it's correctly
1927 // applied to any primitives within this redundant stacking context.
1928 // For the normal case, we start a new clip root, knowing that the
1929 // clip on this stacking context will be pushed onto the stack during
1932 self.clip_store.push_clip_root(clip_id, true);
1934 self.clip_store.push_clip_root(None, false);
1937 // If not redundant, create a stacking context to hold primitive clusters
1939 sc_info.pop_stacking_context = true;
1941 // Push the SC onto the stack, so we know how to handle things in
1942 // pop_stacking_context.
1943 self.sc_stack.push(FlattenedStackingContext {
1944 prim_list: PrimitiveList::empty(),
1953 is_backdrop_root: flags.contains(StackingContextFlags::IS_BACKDROP_ROOT),
1961 fn pop_stacking_context(
1963 info: StackingContextInfo,
1965 profile_scope!("pop_stacking_context");
1967 // Pop off current raster space (pushed unconditionally in push_stacking_context)
1968 self.raster_space_stack.pop().unwrap();
1970 // Pop off clip builder root (pushed unconditionally in push_stacking_context)
1971 self.clip_store.pop_clip_root();
1973 // If the stacking context formed a containing block, pop off the stack
1974 if info.pop_containing_block {
1975 self.containing_block_stack.pop().unwrap();
1978 if info.set_tile_cache_barrier {
1979 self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
1982 // If the stacking context established a clip root, pop off the stack
1983 if info.pop_hit_testing_clip {
1984 self.hit_testing_scene.pop_clip();
1987 // If the stacking context was otherwise redundant, early exit
1988 if !info.pop_stacking_context {
1992 let stacking_context = self.sc_stack.pop().unwrap();
1994 // If the stacking context is a blend container, and if we're at the top level
1995 // of the stacking context tree, we can make this blend container into a tile
1996 // cache. This means that we get caching and correct scrolling invalidation for
1997 // root level blend containers. For these cases, the readbacks of the backdrop
1998 // are handled by doing partial reads of the picture cache tiles during rendering.
1999 if stacking_context.flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) &&
2000 self.sc_stack.is_empty() &&
2001 self.tile_cache_builder.can_add_container_tile_cache()
2003 self.tile_cache_builder.add_tile_cache(
2004 stacking_context.prim_list,
2005 stacking_context.clip_chain_id,
2015 let parent_is_empty = match self.sc_stack.last() {
2016 Some(parent_sc) => {
2017 assert!(!stacking_context.is_redundant);
2018 parent_sc.prim_list.is_empty()
2023 let mut source = match stacking_context.context_3d {
2024 // TODO(gw): For now, as soon as this picture is in
2025 // a 3D context, we draw it to an intermediate
2026 // surface and apply plane splitting. However,
2027 // there is a large optimization opportunity here.
2028 // During culling, we can check if there is actually
2029 // perspective present, and skip the plane splitting
2030 // completely when that is not the case.
2031 Picture3DContext::In { ancestor_index, .. } => {
2032 let composite_mode = Some(
2033 PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)
2036 // Add picture for this actual stacking context contents to render into.
2037 let pic_index = PictureIndex(self.prim_store.pictures
2039 .init(PicturePrimitive::new_image(
2040 composite_mode.clone(),
2041 Picture3DContext::In { root_data: None, ancestor_index },
2043 stacking_context.prim_flags,
2044 stacking_context.prim_list,
2045 stacking_context.spatial_node_index,
2046 PictureOptions::default(),
2050 let instance = create_prim_instance(
2052 composite_mode.into(),
2054 &mut self.interners,
2057 PictureChainBuilder::from_instance(
2059 stacking_context.prim_flags,
2060 stacking_context.spatial_node_index,
2063 Picture3DContext::Out => {
2064 if stacking_context.blit_reason.is_empty() {
2065 PictureChainBuilder::from_prim_list(
2066 stacking_context.prim_list,
2067 stacking_context.prim_flags,
2068 stacking_context.spatial_node_index,
2071 let composite_mode = Some(
2072 PictureCompositeMode::Blit(stacking_context.blit_reason)
2075 // Add picture for this actual stacking context contents to render into.
2076 let pic_index = PictureIndex(self.prim_store.pictures
2078 .init(PicturePrimitive::new_image(
2079 composite_mode.clone(),
2080 Picture3DContext::Out,
2082 stacking_context.prim_flags,
2083 stacking_context.prim_list,
2084 stacking_context.spatial_node_index,
2085 PictureOptions::default(),
2089 let instance = create_prim_instance(
2091 composite_mode.into(),
2093 &mut self.interners,
2096 PictureChainBuilder::from_instance(
2098 stacking_context.prim_flags,
2099 stacking_context.spatial_node_index,
2105 // If establishing a 3d context, the `cur_instance` represents
2106 // a picture with all the *trailing* immediate children elements.
2107 // We append this to the preserve-3D picture set and make a container picture of them.
2108 if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
2109 let instance = source.finalize(
2111 &mut self.interners,
2112 &mut self.prim_store,
2115 prims.push(ExtendedPrimitiveInstance {
2117 spatial_node_index: stacking_context.spatial_node_index,
2118 flags: stacking_context.prim_flags,
2121 let mut prim_list = PrimitiveList::empty();
2122 for ext_prim in prims.drain(..) {
2126 ext_prim.spatial_node_index,
2131 // This is the acttual picture representing our 3D hierarchy root.
2132 let pic_index = PictureIndex(self.prim_store.pictures
2134 .init(PicturePrimitive::new_image(
2136 Picture3DContext::In {
2137 root_data: Some(Vec::new()),
2141 stacking_context.prim_flags,
2143 stacking_context.spatial_node_index,
2144 PictureOptions::default(),
2148 let instance = create_prim_instance(
2150 PictureCompositeKey::Identity,
2152 &mut self.interners,
2155 source = PictureChainBuilder::from_instance(
2157 stacking_context.prim_flags,
2158 stacking_context.spatial_node_index,
2162 let has_filters = stacking_context.composite_ops.has_valid_filters();
2164 source = self.wrap_prim_with_filters(
2166 stacking_context.composite_ops.filters,
2167 stacking_context.composite_ops.filter_primitives,
2168 stacking_context.composite_ops.filter_datas,
2172 // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent
2173 // stacking context.
2174 // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is:
2175 // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs)
2177 // Cs = Source color
2178 // ab = Backdrop alpha
2179 // Cb = Backdrop color
2181 // If we're the first primitive within a stacking context, then we can guarantee that the
2182 // backdrop alpha will be 0, and then the blend equation collapses to just
2183 // Cs = Cs, and the blend mode isn't taken into account at all.
2184 if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) {
2185 let parent_is_isolated = match self.sc_stack.last() {
2186 Some(parent_sc) => parent_sc.blit_reason.contains(BlitReason::ISOLATE),
2189 if parent_is_isolated {
2190 let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode);
2192 source = source.add_picture(
2194 Picture3DContext::Out,
2195 PictureOptions::default(),
2196 &mut self.interners,
2197 &mut self.prim_store,
2200 // If we have a mix-blend-mode, the stacking context needs to be isolated
2201 // to blend correctly as per the CSS spec.
2202 // If not already isolated, we can't correctly blend.
2203 warn!("found a mix-blend-mode outside a blend container, ignoring");
2207 // Set the stacking context clip on the outermost picture in the chain,
2208 // unless we already set it on the leaf picture.
2209 let cur_instance = source.finalize(
2210 stacking_context.clip_chain_id,
2211 &mut self.interners,
2212 &mut self.prim_store,
2215 // The primitive instance for the remainder of flat children of this SC
2216 // if it's a part of 3D hierarchy but not the root of it.
2217 let trailing_children_instance = match self.sc_stack.last_mut() {
2218 // Preserve3D path (only relevant if there are no filters/mix-blend modes)
2219 Some(ref parent_sc) if !has_filters && parent_sc.is_3d() => {
2222 // Regular parenting path
2223 Some(ref mut parent_sc) => {
2224 parent_sc.prim_list.add_prim(
2227 stacking_context.spatial_node_index,
2228 stacking_context.prim_flags,
2232 // This must be the root stacking context
2234 self.add_primitive_to_draw_list(
2237 stacking_context.spatial_node_index,
2238 stacking_context.prim_flags,
2245 // finally, if there any outstanding 3D primitive instances,
2246 // find the 3D hierarchy root and add them there.
2247 if let Some(instance) = trailing_children_instance {
2248 self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance {
2250 spatial_node_index: stacking_context.spatial_node_index,
2251 flags: stacking_context.prim_flags,
2256 self.pending_shadow_items.is_empty(),
2257 "Found unpopped shadows when popping stacking context!"
2261 pub fn push_reference_frame(
2263 reference_frame_id: SpatialId,
2264 parent_index: Option<SpatialNodeIndex>,
2265 pipeline_id: PipelineId,
2266 transform_style: TransformStyle,
2267 source_transform: PropertyBinding<LayoutTransform>,
2268 kind: ReferenceFrameKind,
2269 origin_in_parent_reference_frame: LayoutVector2D,
2270 ) -> SpatialNodeIndex {
2271 let index = self.spatial_tree.add_reference_frame(
2276 origin_in_parent_reference_frame,
2279 self.id_to_index_mapper.add_spatial_node(reference_frame_id, index);
2286 pipeline_id: PipelineId,
2287 viewport_size: &LayoutSize,
2289 if let ChasePrimitive::Id(id) = self.config.chase_primitive {
2290 println!("Chasing {:?} by index", id);
2291 register_prim_chase_id(id);
2294 let spatial_node_index = self.push_reference_frame(
2295 SpatialId::root_reference_frame(pipeline_id),
2298 TransformStyle::Flat,
2299 PropertyBinding::Value(LayoutTransform::identity()),
2300 ReferenceFrameKind::Transform {
2301 is_2d_scale_translation: false,
2304 LayoutVector2D::zero(),
2307 let viewport_rect = self.snap_rect(
2308 &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
2312 self.add_scroll_frame(
2313 SpatialId::root_scroll_node(pipeline_id),
2315 ExternalScrollId(0, pipeline_id),
2318 &viewport_rect.size,
2319 ScrollSensitivity::ScriptAndInputEvents,
2320 ScrollFrameKind::PipelineRoot {
2321 is_root_pipeline: true,
2323 LayoutVector2D::zero(),
2327 fn add_image_mask_clip_node(
2329 new_node_id: ClipId,
2330 space_and_clip: &SpaceAndClipInfo,
2331 image_mask: &ImageMask,
2332 _fill_rule: FillRule,
2333 _points_range: ItemRange<LayoutPoint>,
2335 // TODO(bradwerth): incorporate fill_rule and points_range into
2337 let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
2339 let snapped_mask_rect = self.snap_rect(
2343 let item = ClipItemKey {
2344 kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect),
2352 clip_node_kind: ClipNodeKind::Complex,
2356 let instance = SceneClipInstance {
2358 clip: ClipInstance::new(handle, spatial_node_index),
2361 self.clip_store.register_clip_template(
2363 space_and_clip.clip_id,
2368 /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`.
2369 pub fn add_rect_clip_node(
2371 new_node_id: ClipId,
2372 space_and_clip: &SpaceAndClipInfo,
2373 clip_rect: &LayoutRect,
2375 let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
2377 let snapped_clip_rect = self.snap_rect(
2382 let item = ClipItemKey {
2383 kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
2390 clip_node_kind: ClipNodeKind::Rectangle,
2394 let instance = SceneClipInstance {
2396 clip: ClipInstance::new(handle, spatial_node_index),
2399 self.clip_store.register_clip_template(
2401 space_and_clip.clip_id,
2406 pub fn add_rounded_rect_clip_node(
2408 new_node_id: ClipId,
2409 space_and_clip: &SpaceAndClipInfo,
2410 clip: &ComplexClipRegion,
2411 current_offset: LayoutVector2D,
2413 let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
2415 let snapped_region_rect = self.snap_rect(
2416 &clip.rect.translate(current_offset),
2419 let item = ClipItemKey {
2420 kind: ClipItemKeyKind::rounded_rect(
2421 snapped_region_rect,
2432 clip_node_kind: ClipNodeKind::Complex,
2436 let instance = SceneClipInstance {
2438 clip: ClipInstance::new(handle, spatial_node_index),
2441 self.clip_store.register_clip_template(
2443 space_and_clip.clip_id,
2448 pub fn add_clip_node<I>(
2450 new_node_id: ClipId,
2451 space_and_clip: &SpaceAndClipInfo,
2452 clip_region: ClipRegion<I>,
2455 I: IntoIterator<Item = ComplexClipRegion>
2457 // Map the ClipId for the positioning node to a spatial node index.
2458 let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
2460 let snapped_clip_rect = self.snap_rect(
2464 let mut instances: SmallVec<[SceneClipInstance; 4]> = SmallVec::new();
2466 // Intern each clip item in this clip node, and add the interned
2467 // handle to a clip chain node, parented to form a chain.
2468 // TODO(gw): We could re-structure this to share some of the
2469 // interning and chaining code.
2471 // Build the clip sources from the supplied region.
2472 let item = ClipItemKey {
2473 kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
2480 clip_node_kind: ClipNodeKind::Rectangle,
2486 clip: ClipInstance::new(handle, spatial_node_index),
2490 for region in clip_region.complex_clips {
2491 let snapped_region_rect = self.snap_rect(®ion.rect, spatial_node_index);
2492 let item = ClipItemKey {
2493 kind: ClipItemKeyKind::rounded_rect(
2494 snapped_region_rect,
2505 clip_node_kind: ClipNodeKind::Complex,
2512 clip: ClipInstance::new(handle, spatial_node_index),
2517 self.clip_store.register_clip_template(
2519 space_and_clip.clip_id,
2524 pub fn add_scroll_frame(
2526 new_node_id: SpatialId,
2527 parent_node_index: SpatialNodeIndex,
2528 external_id: ExternalScrollId,
2529 pipeline_id: PipelineId,
2530 frame_rect: &LayoutRect,
2531 content_size: &LayoutSize,
2532 scroll_sensitivity: ScrollSensitivity,
2533 frame_kind: ScrollFrameKind,
2534 external_scroll_offset: LayoutVector2D,
2535 ) -> SpatialNodeIndex {
2536 let node_index = self.spatial_tree.add_scroll_frame(
2544 external_scroll_offset,
2546 self.id_to_index_mapper.add_spatial_node(new_node_id, node_index);
2553 spatial_node_index: SpatialNodeIndex,
2554 clip_chain_id: ClipChainId,
2555 should_inflate: bool,
2557 // Store this shadow in the pending list, for processing
2558 // during pop_all_shadows.
2559 self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
2567 pub fn pop_all_shadows(
2570 assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
2572 let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
2575 // The pending_shadow_items queue contains a list of shadows and primitives
2576 // that were pushed during the active shadow context. To process these, we:
2578 // Iterate the list, popping an item from the front each iteration.
2580 // If the item is a shadow:
2581 // - Create a shadow picture primitive.
2582 // - Add *any* primitives that remain in the item list to this shadow.
2583 // If the item is a primitive:
2584 // - Add that primitive as a normal item (if alpha > 0)
2587 while let Some(item) = items.pop_front() {
2589 ShadowItem::Shadow(pending_shadow) => {
2590 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
2591 // "the image that would be generated by applying to the shadow a
2592 // Gaussian blur with a standard deviation equal to half the blur radius."
2593 let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
2595 // Add any primitives that come after this shadow in the item
2596 // list to this shadow.
2597 let mut prim_list = PrimitiveList::empty();
2598 let blur_filter = Filter::Blur(std_deviation, std_deviation);
2599 let blur_is_noop = blur_filter.is_noop();
2601 for item in &items {
2602 let (instance, info, spatial_node_index) = match item {
2603 ShadowItem::Image(ref pending_image) => {
2604 self.create_shadow_prim(
2610 ShadowItem::LineDecoration(ref pending_line_dec) => {
2611 self.create_shadow_prim(
2617 ShadowItem::NormalBorder(ref pending_border) => {
2618 self.create_shadow_prim(
2624 ShadowItem::Primitive(ref pending_primitive) => {
2625 self.create_shadow_prim(
2631 ShadowItem::TextRun(ref pending_text_run) => {
2632 self.create_shadow_prim(
2644 self.add_primitive_to_draw_list(
2660 // No point in adding a shadow here if there were no primitives
2661 // added to the shadow.
2662 if !prim_list.is_empty() {
2663 // Create a picture that the shadow primitives will be added to. If the
2664 // blur radius is 0, the code in Picture::prepare_for_render will
2665 // detect this and mark the picture to be drawn directly into the
2666 // parent picture, which avoids an intermediate surface and blur.
2667 let blur_filter = Filter::Blur(std_deviation, std_deviation);
2668 assert!(!blur_filter.is_noop());
2669 let composite_mode = Some(PictureCompositeMode::Filter(blur_filter));
2670 let composite_mode_key = composite_mode.clone().into();
2672 // Pass through configuration information about whether WR should
2673 // do the bounding rect inflation for text shadows.
2674 let options = PictureOptions {
2675 inflate_if_required: pending_shadow.should_inflate,
2678 // Create the primitive to draw the shadow picture into the scene.
2679 let shadow_pic_index = PictureIndex(self.prim_store.pictures
2681 .init(PicturePrimitive::new_image(
2683 Picture3DContext::Out,
2685 PrimitiveFlags::IS_BACKFACE_VISIBLE,
2687 pending_shadow.spatial_node_index,
2692 let shadow_pic_key = PictureKey::new(
2693 Picture { composite_mode_key },
2696 let shadow_prim_data_handle = self.interners
2698 .intern(&shadow_pic_key, || ());
2700 let shadow_prim_instance = PrimitiveInstance::new(
2701 LayoutRect::max_rect(),
2702 PrimitiveInstanceKind::Picture {
2703 data_handle: shadow_prim_data_handle,
2704 pic_index: shadow_pic_index,
2705 segment_instance_index: SegmentInstanceIndex::INVALID,
2707 pending_shadow.clip_chain_id,
2710 // Add the shadow primitive. This must be done before pushing this
2711 // picture on to the shadow stack, to avoid infinite recursion!
2712 self.add_primitive_to_draw_list(
2713 shadow_prim_instance,
2715 pending_shadow.spatial_node_index,
2716 PrimitiveFlags::IS_BACKFACE_VISIBLE,
2720 ShadowItem::Image(pending_image) => {
2721 self.add_shadow_prim_to_draw_list(
2725 ShadowItem::LineDecoration(pending_line_dec) => {
2726 self.add_shadow_prim_to_draw_list(
2730 ShadowItem::NormalBorder(pending_border) => {
2731 self.add_shadow_prim_to_draw_list(
2735 ShadowItem::Primitive(pending_primitive) => {
2736 self.add_shadow_prim_to_draw_list(
2740 ShadowItem::TextRun(pending_text_run) => {
2741 self.add_shadow_prim_to_draw_list(
2748 debug_assert!(items.is_empty());
2749 self.pending_shadow_items = items;
2752 fn create_shadow_prim<P>(
2754 pending_shadow: &PendingShadow,
2755 pending_primitive: &PendingPrimitive<P>,
2757 ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex)
2759 P: InternablePrimitive + CreateShadow,
2760 Interners: AsMut<Interner<P>>,
2762 // Offset the local rect and clip rect by the shadow offset. The pending
2763 // primitive has already been snapped, but we will need to snap the
2764 // shadow after translation. We don't need to worry about the size
2765 // changing because the shadow has the same raster space as the
2766 // primitive, and thus we know the size is already rounded.
2767 let mut info = pending_primitive.info.clone();
2768 info.rect = self.snap_rect(
2769 &info.rect.translate(pending_shadow.shadow.offset),
2770 pending_primitive.spatial_node_index,
2772 info.clip_rect = self.snap_rect(
2773 &info.clip_rect.translate(pending_shadow.shadow.offset),
2774 pending_primitive.spatial_node_index,
2777 // Construct and add a primitive for the given shadow.
2778 let shadow_prim_instance = self.create_primitive(
2780 pending_primitive.spatial_node_index,
2781 pending_primitive.clip_chain_id,
2782 pending_primitive.prim.create_shadow(
2783 &pending_shadow.shadow,
2785 self.raster_space_stack.last().cloned().unwrap(),
2789 (shadow_prim_instance, info, pending_primitive.spatial_node_index)
2792 fn add_shadow_prim_to_draw_list<P>(
2794 pending_primitive: PendingPrimitive<P>,
2796 P: InternablePrimitive + IsVisible,
2797 Interners: AsMut<Interner<P>>,
2799 // For a normal primitive, if it has alpha > 0, then we add this
2800 // as a normal primitive to the parent picture.
2801 if pending_primitive.prim.is_visible() {
2802 self.add_prim_to_draw_list(
2803 &pending_primitive.info,
2804 pending_primitive.spatial_node_index,
2805 pending_primitive.clip_chain_id,
2806 pending_primitive.prim,
2811 #[cfg(debug_assertions)]
2812 fn register_chase_primitive_by_rect(
2815 prim_instance: &PrimitiveInstance,
2817 if ChasePrimitive::LocalRect(*rect) == self.config.chase_primitive {
2818 println!("Chasing {:?} by local rect", prim_instance.id);
2819 register_prim_chase_id(prim_instance.id);
2823 #[cfg(not(debug_assertions))]
2824 fn register_chase_primitive_by_rect(
2827 _prim_instance: &PrimitiveInstance,
2831 pub fn add_clear_rectangle(
2833 spatial_node_index: SpatialNodeIndex,
2834 clip_chain_id: ClipChainId,
2835 info: &LayoutPrimitiveInfo,
2837 // Clear prims must be in their own picture cache slice to
2838 // be composited correctly.
2839 self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
2846 PrimitiveKeyKind::Clear,
2849 self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
2854 spatial_node_index: SpatialNodeIndex,
2855 clip_chain_id: ClipChainId,
2856 info: &LayoutPrimitiveInfo,
2857 wavy_line_thickness: f32,
2858 orientation: LineOrientation,
2862 // For line decorations, we can construct the render task cache key
2863 // here during scene building, since it doesn't depend on device
2864 // pixel ratio or transform.
2865 let mut info = info.clone();
2867 let size = get_line_decoration_size(
2871 wavy_line_thickness,
2874 let cache_key = size.map(|size| {
2875 // If dotted, adjust the clip rect to ensure we don't draw a final
2877 if style == LineStyle::Dotted {
2878 let clip_size = match orientation {
2879 LineOrientation::Horizontal => {
2881 size.width * (info.rect.size.width / size.width).floor(),
2882 info.rect.size.height,
2885 LineOrientation::Vertical => {
2887 info.rect.size.width,
2888 size.height * (info.rect.size.height / size.height).floor(),
2892 let clip_rect = LayoutRect::new(
2896 info.clip_rect = clip_rect
2897 .intersection(&info.clip_rect)
2898 .unwrap_or_else(LayoutRect::zero);
2901 LineDecorationCacheKey {
2904 wavy_line_thickness: Au::from_f32_px(wavy_line_thickness),
2916 color: color.into(),
2923 spatial_node_index: SpatialNodeIndex,
2924 clip_chain_id: ClipChainId,
2925 info: &LayoutPrimitiveInfo,
2926 border_item: &BorderDisplayItem,
2927 gradient_stops: ItemRange<GradientStop>,
2929 match border_item.details {
2930 BorderDetails::NinePatch(ref border) => {
2931 let nine_patch = NinePatchDescriptor {
2932 width: border.width,
2933 height: border.height,
2934 slice: border.slice,
2936 repeat_horizontal: border.repeat_horizontal,
2937 repeat_vertical: border.repeat_vertical,
2938 outset: border.outset.into(),
2939 widths: border_item.widths.into(),
2942 match border.source {
2943 NinePatchBorderSource::Image(image_key) => {
2944 let prim = ImageBorder {
2945 request: ImageRequest {
2947 rendering: ImageRendering::Auto,
2953 self.add_nonshadowable_primitive(
2961 NinePatchBorderSource::Gradient(gradient) => {
2962 let prim = match self.create_linear_gradient_prim(
2964 gradient.start_point,
2967 gradient.extend_mode,
2968 LayoutSize::new(border.height as f32, border.width as f32),
2970 Some(Box::new(nine_patch)),
2976 self.add_nonshadowable_primitive(
2984 NinePatchBorderSource::RadialGradient(gradient) => {
2985 let prim = self.create_radial_gradient_prim(
2988 gradient.start_offset * gradient.radius.width,
2989 gradient.end_offset * gradient.radius.width,
2990 gradient.radius.width / gradient.radius.height,
2991 read_gradient_stops(gradient_stops),
2992 gradient.extend_mode,
2993 LayoutSize::new(border.height as f32, border.width as f32),
2995 Some(Box::new(nine_patch)),
2998 self.add_nonshadowable_primitive(
3006 NinePatchBorderSource::ConicGradient(gradient) => {
3007 let prim = self.create_conic_gradient_prim(
3011 gradient.start_offset,
3012 gradient.end_offset,
3014 gradient.extend_mode,
3015 LayoutSize::new(border.height as f32, border.width as f32),
3017 Some(Box::new(nine_patch)),
3020 self.add_nonshadowable_primitive(
3030 BorderDetails::Normal(ref border) => {
3031 self.add_normal_border(
3042 pub fn create_linear_gradient_prim(
3044 info: &LayoutPrimitiveInfo,
3045 start_point: LayoutPoint,
3046 end_point: LayoutPoint,
3047 stops: ItemRange<GradientStop>,
3048 extend_mode: ExtendMode,
3049 stretch_size: LayoutSize,
3050 mut tile_spacing: LayoutSize,
3051 nine_patch: Option<Box<NinePatchDescriptor>>,
3052 ) -> Option<LinearGradient> {
3053 let mut prim_rect = info.rect;
3054 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3056 let mut max_alpha: f32 = 0.0;
3058 let stops = stops.iter().map(|stop| {
3059 max_alpha = max_alpha.max(stop.color.a);
3061 offset: stop.offset,
3062 color: stop.color.into(),
3066 // If all the stops have no alpha, then this
3067 // gradient can't contribute to the scene.
3068 if max_alpha <= 0.0 {
3072 // Try to ensure that if the gradient is specified in reverse, then so long as the stops
3073 // are also supplied in reverse that the rendered result will be equivalent. To do this,
3074 // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
3075 // just designate the reference orientation as start < end. Aligned gradient rendering
3076 // manages to produce the same result regardless of orientation, so don't worry about
3077 // reversing in that case.
3078 let reverse_stops = start_point.x > end_point.x ||
3079 (start_point.x == end_point.x && start_point.y > end_point.y);
3081 // To get reftests exactly matching with reverse start/end
3082 // points, it's necessary to reverse the gradient
3083 // line in some cases.
3084 let (sp, ep) = if reverse_stops {
3085 (end_point, start_point)
3087 (start_point, end_point)
3090 Some(LinearGradient {
3092 start_point: sp.into(),
3093 end_point: ep.into(),
3094 stretch_size: stretch_size.into(),
3095 tile_spacing: tile_spacing.into(),
3102 pub fn create_radial_gradient_prim(
3104 info: &LayoutPrimitiveInfo,
3105 center: LayoutPoint,
3109 stops: Vec<GradientStopKey>,
3110 extend_mode: ExtendMode,
3111 stretch_size: LayoutSize,
3112 mut tile_spacing: LayoutSize,
3113 nine_patch: Option<Box<NinePatchDescriptor>>,
3114 ) -> RadialGradient {
3115 let mut prim_rect = info.rect;
3116 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3118 let params = RadialGradientParams {
3126 center: center.into(),
3128 stretch_size: stretch_size.into(),
3129 tile_spacing: tile_spacing.into(),
3135 pub fn create_conic_gradient_prim(
3137 info: &LayoutPrimitiveInfo,
3138 center: LayoutPoint,
3142 stops: ItemRange<GradientStop>,
3143 extend_mode: ExtendMode,
3144 stretch_size: LayoutSize,
3145 mut tile_spacing: LayoutSize,
3146 nine_patch: Option<Box<NinePatchDescriptor>>,
3147 ) -> ConicGradient {
3148 let mut prim_rect = info.rect;
3149 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3151 let stops = stops.iter().map(|stop| {
3153 offset: stop.offset,
3154 color: stop.color.into(),
3160 center: center.into(),
3161 params: ConicGradientParams { angle, start_offset, end_offset },
3162 stretch_size: stretch_size.into(),
3163 tile_spacing: tile_spacing.into(),
3171 spatial_node_index: SpatialNodeIndex,
3172 clip_chain_id: ClipChainId,
3173 prim_info: &LayoutPrimitiveInfo,
3174 font_instance_key: &FontInstanceKey,
3175 text_color: &ColorF,
3176 glyph_range: ItemRange<GlyphInstance>,
3177 glyph_options: Option<GlyphOptions>,
3179 let offset = self.current_offset(spatial_node_index);
3182 let instance_map = self.font_instances.lock().unwrap();
3183 let font_instance = match instance_map.get(font_instance_key) {
3184 Some(instance) => instance,
3186 warn!("Unknown font instance key");
3187 debug!("key={:?}", font_instance_key);
3192 // Trivial early out checks
3193 if font_instance.size <= FontSize::zero() {
3197 // TODO(gw): Use a proper algorithm to select
3198 // whether this item should be rendered with
3200 let mut render_mode = self.config
3201 .default_font_render_mode
3202 .limit_by(font_instance.render_mode);
3203 let mut flags = font_instance.flags;
3204 if let Some(options) = glyph_options {
3205 render_mode = render_mode.limit_by(options.render_mode);
3206 flags |= options.flags;
3209 let font = FontInstance::new(
3210 Arc::clone(font_instance),
3211 (*text_color).into(),
3216 // TODO(gw): It'd be nice not to have to allocate here for creating
3217 // the primitive key, when the common case is that the
3218 // hash will match and we won't end up creating a new
3219 // primitive template.
3220 let prim_offset = prim_info.rect.origin.to_vector() - offset;
3221 let glyphs = glyph_range
3226 point: glyph.point - prim_offset,
3231 // Query the current requested raster space (stack handled by push/pop
3232 // stacking context).
3233 let requested_raster_space = self.raster_space_stack
3239 glyphs: Arc::new(glyphs),
3242 requested_raster_space,
3257 spatial_node_index: SpatialNodeIndex,
3258 clip_chain_id: ClipChainId,
3259 info: &LayoutPrimitiveInfo,
3260 stretch_size: LayoutSize,
3261 mut tile_spacing: LayoutSize,
3262 image_key: ImageKey,
3263 image_rendering: ImageRendering,
3264 alpha_type: AlphaType,
3267 let mut prim_rect = info.rect;
3268 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3269 let info = LayoutPrimitiveInfo {
3281 tile_spacing: tile_spacing.into(),
3282 stretch_size: stretch_size.into(),
3283 color: color.into(),
3290 pub fn add_yuv_image(
3292 spatial_node_index: SpatialNodeIndex,
3293 clip_chain_id: ClipChainId,
3294 info: &LayoutPrimitiveInfo,
3296 color_depth: ColorDepth,
3297 color_space: YuvColorSpace,
3298 color_range: ColorRange,
3299 image_rendering: ImageRendering,
3301 let format = yuv_data.get_format();
3302 let yuv_key = match yuv_data {
3303 YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
3304 YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
3305 YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
3308 self.add_nonshadowable_primitive(
3324 fn add_primitive_instance_to_3d_root(
3326 prim: ExtendedPrimitiveInstance,
3328 // find the 3D root and append to the children list
3329 for sc in self.sc_stack.iter_mut().rev() {
3330 match sc.context_3d {
3331 Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
3335 Picture3DContext::In { .. } => {}
3336 Picture3DContext::Out => panic!("Unable to find 3D root"),
3341 pub fn add_backdrop_filter(
3343 spatial_node_index: SpatialNodeIndex,
3344 clip_chain_id: ClipChainId,
3345 info: &LayoutPrimitiveInfo,
3346 filters: Vec<Filter>,
3347 filter_datas: Vec<FilterData>,
3348 filter_primitives: Vec<FilterPrimitive>,
3350 let mut backdrop_pic_index = match self.cut_backdrop_picture() {
3351 // Backdrop contains no content, so no need to add backdrop-filter
3353 Some(backdrop_pic_index) => backdrop_pic_index,
3356 let backdrop_spatial_node_index = self.prim_store.pictures[backdrop_pic_index.0].spatial_node_index;
3358 let mut instance = self.create_primitive(
3360 // TODO(cbrewster): This is a bit of a hack to help figure out the correct sizing of the backdrop
3361 // region. By makings sure to include this, the clip chain instance computes the correct clip rect,
3362 // but we don't actually apply the filtered backdrop clip yet (this is done to the last instance in
3363 // the filter chain below).
3364 backdrop_spatial_node_index,
3367 pic_index: backdrop_pic_index,
3369 border_rect: info.rect.into(),
3373 // We will append the filtered backdrop to the backdrop root, but we need to
3374 // make sure all clips between the current stacking context and backdrop root
3375 // are taken into account. So we wrap the backdrop filter instance with a picture with
3376 // a clip for each stacking context.
3377 for stacking_context in self.sc_stack.iter().rev().take_while(|sc| !sc.is_backdrop_root) {
3378 let clip_chain_id = stacking_context.clip_chain_id;
3379 let prim_flags = stacking_context.prim_flags;
3380 let composite_mode = None;
3382 let mut prim_list = PrimitiveList::empty();
3386 backdrop_spatial_node_index,
3390 backdrop_pic_index = PictureIndex(self.prim_store.pictures
3392 .init(PicturePrimitive::new_image(
3393 composite_mode.clone(),
3394 Picture3DContext::Out,
3398 backdrop_spatial_node_index,
3400 inflate_if_required: false,
3405 instance = create_prim_instance(
3407 composite_mode.into(),
3409 &mut self.interners,
3413 let mut source = PictureChainBuilder::from_instance(
3416 backdrop_spatial_node_index,
3419 source = self.wrap_prim_with_filters(
3427 // Apply filters from all stacking contexts up to, but not including the backdrop root.
3428 // Gecko pushes separate stacking contexts for filters and opacity,
3429 // so we must iterate through multiple stacking contexts to find all effects
3430 // that need to be applied to the filtered backdrop.
3431 let backdrop_root_pos = self.sc_stack.iter().rposition(|sc| sc.is_backdrop_root).expect("no backdrop root?");
3432 for i in ((backdrop_root_pos + 1)..self.sc_stack.len()).rev() {
3433 let stacking_context = &self.sc_stack[i];
3434 let filters = stacking_context.composite_ops.filters.clone();
3435 let filter_primitives = stacking_context.composite_ops.filter_primitives.clone();
3436 let filter_datas = stacking_context.composite_ops.filter_datas.clone();
3438 source = self.wrap_prim_with_filters(
3447 let filtered_instance = source.finalize(
3449 &mut self.interners,
3450 &mut self.prim_store,
3456 .find(|sc| sc.is_backdrop_root)
3462 backdrop_spatial_node_index,
3467 /// Create pictures for each stacking context rendered into their parents, down to the nearest
3468 /// backdrop root until we have a picture that represents the contents of all primitives added
3469 /// since the backdrop root
3470 pub fn cut_backdrop_picture(&mut self) -> Option<PictureIndex> {
3471 let mut flattened_items = None;
3472 let mut backdrop_root = None;
3473 let mut spatial_node_index = SpatialNodeIndex::INVALID;
3474 let mut prim_flags = PrimitiveFlags::default();
3475 for sc in self.sc_stack.iter_mut().rev() {
3476 // Add child contents to parent stacking context
3477 if let Some((_, flattened_instance)) = flattened_items.take() {
3478 sc.prim_list.add_prim(
3485 flattened_items = sc.cut_item_sequence(
3486 &mut self.prim_store,
3487 &mut self.interners,
3489 Picture3DContext::Out,
3491 spatial_node_index = sc.spatial_node_index;
3492 prim_flags = sc.prim_flags;
3493 if sc.is_backdrop_root {
3494 backdrop_root = Some(sc);
3499 let (pic_index, instance) = flattened_items?;
3500 self.prim_store.pictures[pic_index.0].requested_composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP));
3501 backdrop_root.expect("no backdrop root found")
3514 fn wrap_prim_with_filters(
3516 mut source: PictureChainBuilder,
3517 mut filter_ops: Vec<Filter>,
3518 mut filter_primitives: Vec<FilterPrimitive>,
3519 filter_datas: Vec<FilterData>,
3520 inflate_if_required: bool,
3521 ) -> PictureChainBuilder {
3522 // TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will
3523 // happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives.
3524 // At some point, these two should be combined and CSS filters should be expressed in terms of SVG filters.
3525 assert!(filter_ops.is_empty() || filter_primitives.is_empty(),
3526 "Filter ops and filter primitives are not allowed on the same stacking context.");
3528 // For each filter, create a new image with that composite mode.
3529 let mut current_filter_data_index = 0;
3530 for filter in &mut filter_ops {
3531 let composite_mode = match filter {
3532 Filter::ComponentTransfer => {
3534 &filter_datas[current_filter_data_index];
3535 let filter_data = filter_data.sanitize();
3536 current_filter_data_index = current_filter_data_index + 1;
3537 if filter_data.is_identity() {
3540 let filter_data_key = SFilterDataKey {
3543 r_func: SFilterDataComponent::from_functype_values(
3544 filter_data.func_r_type, &filter_data.r_values),
3545 g_func: SFilterDataComponent::from_functype_values(
3546 filter_data.func_g_type, &filter_data.g_values),
3547 b_func: SFilterDataComponent::from_functype_values(
3548 filter_data.func_b_type, &filter_data.b_values),
3549 a_func: SFilterDataComponent::from_functype_values(
3550 filter_data.func_a_type, &filter_data.a_values),
3554 let handle = self.interners
3556 .intern(&filter_data_key, || ());
3557 PictureCompositeMode::ComponentTransferFilter(handle)
3561 if filter.is_noop() {
3564 PictureCompositeMode::Filter(filter.clone())
3569 source = source.add_picture(
3571 Picture3DContext::Out,
3572 PictureOptions { inflate_if_required },
3573 &mut self.interners,
3574 &mut self.prim_store,
3578 if !filter_primitives.is_empty() {
3579 let filter_datas = filter_datas.iter()
3580 .map(|filter_data| filter_data.sanitize())
3581 .map(|filter_data| {
3583 r_func: SFilterDataComponent::from_functype_values(
3584 filter_data.func_r_type, &filter_data.r_values),
3585 g_func: SFilterDataComponent::from_functype_values(
3586 filter_data.func_g_type, &filter_data.g_values),
3587 b_func: SFilterDataComponent::from_functype_values(
3588 filter_data.func_b_type, &filter_data.b_values),
3589 a_func: SFilterDataComponent::from_functype_values(
3590 filter_data.func_a_type, &filter_data.a_values),
3595 // Sanitize filter inputs
3596 for primitive in &mut filter_primitives {
3597 primitive.sanitize();
3600 let composite_mode = PictureCompositeMode::SvgFilter(
3605 source = source.add_picture(
3607 Picture3DContext::Out,
3608 PictureOptions { inflate_if_required },
3609 &mut self.interners,
3610 &mut self.prim_store,
3619 pub trait CreateShadow {
3624 current_raster_space: RasterSpace,
3628 pub trait IsVisible {
3629 fn is_visible(&self) -> bool;
3632 /// A primitive instance + some extra information about the primitive. This is
3633 /// stored when constructing 3d rendering contexts, which involve cutting
3634 /// primitive lists.
3635 struct ExtendedPrimitiveInstance {
3636 instance: PrimitiveInstance,
3637 spatial_node_index: SpatialNodeIndex,
3638 flags: PrimitiveFlags,
3641 /// Internal tracking information about the currently pushed stacking context.
3642 /// Used to track what operations need to happen when a stacking context is popped.
3643 struct StackingContextInfo {
3644 /// If true, pop an entry from the hit-testing scene.
3645 pop_hit_testing_clip: bool,
3646 /// If true, pop and entry from the containing block stack.
3647 pop_containing_block: bool,
3648 /// If true, pop an entry from the flattened stacking context stack.
3649 pop_stacking_context: bool,
3650 /// If true, set a tile cache barrier when popping the stacking context.
3651 set_tile_cache_barrier: bool,
3654 /// Properties of a stacking context that are maintained
3655 /// during creation of the scene. These structures are
3656 /// not persisted after the initial scene build.
3657 struct FlattenedStackingContext {
3658 /// The list of primitive instances added to this stacking context.
3659 prim_list: PrimitiveList,
3661 /// Primitive instance flags for compositing this stacking context
3662 prim_flags: PrimitiveFlags,
3664 /// The positioning node for this stacking context
3665 spatial_node_index: SpatialNodeIndex,
3667 /// The clip chain for this stacking context
3668 clip_chain_id: ClipChainId,
3670 /// The list of filters / mix-blend-mode for this
3671 /// stacking context.
3672 composite_ops: CompositeOps,
3674 /// Bitfield of reasons this stacking context needs to
3675 /// be an offscreen surface.
3676 blit_reason: BlitReason,
3678 /// CSS transform-style property.
3679 transform_style: TransformStyle,
3681 /// Defines the relationship to a preserve-3D hiearachy.
3682 context_3d: Picture3DContext<ExtendedPrimitiveInstance>,
3684 /// True if this stacking context is a backdrop root.
3685 is_backdrop_root: bool,
3687 /// True if this stacking context is redundant (i.e. doesn't require a surface)
3690 /// Flags identifying the type of container (among other things) this stacking context is
3691 flags: StackingContextFlags,
3694 impl FlattenedStackingContext {
3695 /// Return true if the stacking context has a valid preserve-3d property
3696 pub fn is_3d(&self) -> bool {
3697 self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
3700 /// Return true if the stacking context isn't needed.
3701 pub fn is_redundant(
3702 sc_flags: StackingContextFlags,
3703 context_3d: &Picture3DContext<ExtendedPrimitiveInstance>,
3704 composite_ops: &CompositeOps,
3705 blit_reason: BlitReason,
3706 parent: Option<&FlattenedStackingContext>,
3707 prim_flags: PrimitiveFlags,
3709 // If this is a backdrop or blend container, it's needed
3710 if sc_flags.intersects(StackingContextFlags::IS_BACKDROP_ROOT | StackingContextFlags::IS_BLEND_CONTAINER) {
3714 // Any 3d context is required
3715 if let Picture3DContext::In { .. } = context_3d {
3719 // If any filters are present that affect the output
3720 if composite_ops.has_valid_filters() {
3724 // We can skip mix-blend modes if they are the first primitive in a stacking context,
3725 // see pop_stacking_context for a full explanation.
3726 if composite_ops.mix_blend_mode.is_some() {
3727 if let Some(parent) = parent {
3728 if !parent.prim_list.is_empty() {
3734 // If backface visibility is explicitly set.
3735 if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
3739 // If need to isolate in surface due to clipping / mix-blend-mode
3740 if !blit_reason.is_empty() {
3748 /// Cut the sequence of the immediate children recorded so far and generate a picture from them.
3749 pub fn cut_item_sequence(
3751 prim_store: &mut PrimitiveStore,
3752 interners: &mut Interners,
3753 composite_mode: Option<PictureCompositeMode>,
3754 flat_items_context_3d: Picture3DContext<OrderedPictureChild>,
3755 ) -> Option<(PictureIndex, PrimitiveInstance)> {
3756 if self.prim_list.is_empty() {
3760 let pic_index = PictureIndex(prim_store.pictures
3762 .init(PicturePrimitive::new_image(
3763 composite_mode.clone(),
3764 flat_items_context_3d,
3767 mem::replace(&mut self.prim_list, PrimitiveList::empty()),
3768 self.spatial_node_index,
3769 PictureOptions::default(),
3773 let prim_instance = create_prim_instance(
3775 composite_mode.into(),
3780 Some((pic_index, prim_instance))
3784 /// A primitive that is added while a shadow context is
3785 /// active is stored as a pending primitive and only
3786 /// added to pictures during pop_all_shadows.
3787 pub struct PendingPrimitive<T> {
3788 spatial_node_index: SpatialNodeIndex,
3789 clip_chain_id: ClipChainId,
3790 info: LayoutPrimitiveInfo,
3794 /// As shadows are pushed, they are stored as pending
3795 /// shadows, and handled at once during pop_all_shadows.
3796 pub struct PendingShadow {
3798 should_inflate: bool,
3799 spatial_node_index: SpatialNodeIndex,
3800 clip_chain_id: ClipChainId,
3803 pub enum ShadowItem {
3804 Shadow(PendingShadow),
3805 Image(PendingPrimitive<Image>),
3806 LineDecoration(PendingPrimitive<LineDecoration>),
3807 NormalBorder(PendingPrimitive<NormalBorderPrim>),
3808 Primitive(PendingPrimitive<PrimitiveKeyKind>),
3809 TextRun(PendingPrimitive<TextRun>),
3812 impl From<PendingPrimitive<Image>> for ShadowItem {
3813 fn from(image: PendingPrimitive<Image>) -> Self {
3814 ShadowItem::Image(image)
3818 impl From<PendingPrimitive<LineDecoration>> for ShadowItem {
3819 fn from(line_dec: PendingPrimitive<LineDecoration>) -> Self {
3820 ShadowItem::LineDecoration(line_dec)
3824 impl From<PendingPrimitive<NormalBorderPrim>> for ShadowItem {
3825 fn from(border: PendingPrimitive<NormalBorderPrim>) -> Self {
3826 ShadowItem::NormalBorder(border)
3830 impl From<PendingPrimitive<PrimitiveKeyKind>> for ShadowItem {
3831 fn from(container: PendingPrimitive<PrimitiveKeyKind>) -> Self {
3832 ShadowItem::Primitive(container)
3836 impl From<PendingPrimitive<TextRun>> for ShadowItem {
3837 fn from(text_run: PendingPrimitive<TextRun>) -> Self {
3838 ShadowItem::TextRun(text_run)
3842 fn create_prim_instance(
3843 pic_index: PictureIndex,
3844 composite_mode_key: PictureCompositeKey,
3845 clip_chain_id: ClipChainId,
3846 interners: &mut Interners,
3847 ) -> PrimitiveInstance {
3848 let pic_key = PictureKey::new(
3849 Picture { composite_mode_key },
3852 let data_handle = interners
3854 .intern(&pic_key, || ());
3856 PrimitiveInstance::new(
3857 LayoutRect::max_rect(),
3858 PrimitiveInstanceKind::Picture {
3861 segment_instance_index: SegmentInstanceIndex::INVALID,
3867 fn filter_ops_for_compositing(
3868 input_filters: ItemRange<FilterOp>,
3870 // TODO(gw): Now that we resolve these later on,
3871 // we could probably make it a bit
3872 // more efficient than cloning these here.
3873 input_filters.iter().map(|filter| filter.into()).collect()
3876 fn filter_datas_for_compositing(
3877 input_filter_datas: &[TempFilterData],
3878 ) -> Vec<FilterData> {
3879 // TODO(gw): Now that we resolve these later on,
3880 // we could probably make it a bit
3881 // more efficient than cloning these here.
3882 let mut filter_datas = vec![];
3883 for temp_filter_data in input_filter_datas {
3884 let func_types : Vec<ComponentTransferFuncType> = temp_filter_data.func_types.iter().collect();
3885 debug_assert!(func_types.len() == 4);
3886 filter_datas.push( FilterData {
3887 func_r_type: func_types[0],
3888 r_values: temp_filter_data.r_values.iter().collect(),
3889 func_g_type: func_types[1],
3890 g_values: temp_filter_data.g_values.iter().collect(),
3891 func_b_type: func_types[2],
3892 b_values: temp_filter_data.b_values.iter().collect(),
3893 func_a_type: func_types[3],
3894 a_values: temp_filter_data.a_values.iter().collect(),
3900 fn filter_primitives_for_compositing(
3901 input_filter_primitives: ItemRange<FilterPrimitive>,
3902 ) -> Vec<FilterPrimitive> {
3903 // Resolve these in the flattener?
3904 // TODO(gw): Now that we resolve these later on,
3905 // we could probably make it a bit
3906 // more efficient than cloning these here.
3907 input_filter_primitives.iter().map(|primitive| primitive).collect()
3910 fn process_repeat_size(
3911 snapped_rect: &LayoutRect,
3912 unsnapped_rect: &LayoutRect,
3913 repeat_size: LayoutSize,
3915 // FIXME(aosmond): The tile size is calculated based on several parameters
3916 // during display list building. It may produce a slightly different result
3917 // than the bounds due to floating point error accumulation, even though in
3918 // theory they should be the same. We do a fuzzy check here to paper over
3919 // that. It may make more sense to push the original parameters into scene
3920 // building and let it do a saner calculation with more information (e.g.
3921 // the snapped values).
3922 const EPSILON: f32 = 0.001;
3924 if repeat_size.width.approx_eq_eps(&unsnapped_rect.size.width, &EPSILON) {
3925 snapped_rect.size.width
3929 if repeat_size.height.approx_eq_eps(&unsnapped_rect.size.height, &EPSILON) {
3930 snapped_rect.size.height
3937 fn read_gradient_stops(stops: ItemRange<GradientStop>) -> Vec<GradientStopKey> {
3938 stops.iter().map(|stop| {
3940 offset: stop.offset,
3941 color: stop.color.into(),