Bug 1675375 Part 1: Define WebRender structures for polygons. r=gw
[gecko.git] / gfx / wr / webrender / src / scene_building.rs
blobe462d46a628fbb9092fc6a72a1c492b0fbc139e4
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/. */
5 //! # Scene building
6 //!
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.
10 //!
11 //! This phase is happening asynchronously on the scene builder thread.
12 //!
13 //! # General algorithm
14 //!
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
20 //!   drawing states.
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
24 //!   lists.
25 //!
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
28 //! primitive list.
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.
32 //!
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)
36 //!
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};
48 use api::units::*;
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;
83 use std::sync::Arc;
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 {
102     fn new() -> Self {
103         ReferenceFrameMapper {
104             frames: vec![
105                 ReferenceFrameState {
106                     offsets: vec![
107                         LayoutVector2D::zero(),
108                     ],
109                 }
110             ],
111         }
112     }
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 {
118             offsets: vec![
119                 LayoutVector2D::zero(),
120             ],
121         });
122     }
124     /// Pop a reference frame scope off the stack.
125     fn pop_scope(&mut self) {
126         self.frames.pop().unwrap();
127     }
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);
135     }
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();
141     }
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()
157     }
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 {
168     fn new() -> Self {
169         ScrollOffsetMapper {
170             current_spatial_node: SpatialNodeIndex::INVALID,
171             current_offset: LayoutVector2D::zero(),
172         }
173     }
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(
179         &mut self,
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);
186         }
188         self.current_offset
189     }
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.
195 #[derive(Default)]
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());
204     }
206     fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex {
207         self.spatial_node_map[&id]
208     }
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>,
222 impl CompositeOps {
223     pub fn new(
224         filters: Vec<Filter>,
225         filter_datas: Vec<FilterData>,
226         filter_primitives: Vec<FilterPrimitive>,
227         mix_blend_mode: Option<MixBlendMode>
228     ) -> Self {
229         CompositeOps {
230             filters,
231             filter_datas,
232             filter_primitives,
233             mix_blend_mode,
234         }
235     }
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()
241     }
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 {
249             match filter {
250                 Filter::ComponentTransfer => {
251                     let filter_data =
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() {
256                         continue
257                     } else {
258                         return true;
259                     }
260                 }
261                 _ => {
262                     if filter.is_noop() {
263                         continue;
264                     } else {
265                         return true;
266                     }
267                 }
268             }
269         }
271         if !self.filter_primitives.is_empty() {
272             return true;
273         }
275         false
276     }
279 /// Represents the current input for a picture chain builder (either a
280 /// prim list from the stacking context, or a wrapped picture instance).
281 enum PictureSource {
282     PrimitiveList {
283         prim_list: PrimitiveList,
284     },
285     WrappedPicture {
286         instance: PrimitiveInstance,
287     },
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
304     fn from_prim_list(
305         prim_list: PrimitiveList,
306         flags: PrimitiveFlags,
307         spatial_node_index: SpatialNodeIndex,
308     ) -> Self {
309         PictureChainBuilder {
310             current: PictureSource::PrimitiveList {
311                 prim_list,
312             },
313             spatial_node_index,
314             flags,
315         }
316     }
318     /// Create a new picture chain builder, from a picture wrapper instance
319     fn from_instance(
320         instance: PrimitiveInstance,
321         flags: PrimitiveFlags,
322         spatial_node_index: SpatialNodeIndex,
323     ) -> Self {
324         PictureChainBuilder {
325             current: PictureSource::WrappedPicture {
326                 instance,
327             },
328             flags,
329             spatial_node_index,
330         }
331     }
333     /// Wrap the existing content with a new picture with the given parameters
334     #[must_use]
335     fn add_picture(
336         self,
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 } => {
345                 prim_list
346             }
347             PictureSource::WrappedPicture { instance } => {
348                 let mut prim_list = PrimitiveList::empty();
350                 prim_list.add_prim(
351                     instance,
352                     LayoutRect::zero(),
353                     self.spatial_node_index,
354                     self.flags,
355                 );
357                 prim_list
358             }
359         };
361         let pic_index = PictureIndex(prim_store.pictures
362             .alloc()
363             .init(PicturePrimitive::new_image(
364                 Some(composite_mode.clone()),
365                 context_3d,
366                 true,
367                 self.flags,
368                 prim_list,
369                 self.spatial_node_index,
370                 options,
371             ))
372         );
374         let instance = create_prim_instance(
375             pic_index,
376             Some(composite_mode).into(),
377             ClipChainId::NONE,
378             interners,
379         );
381         PictureChainBuilder {
382             current: PictureSource::WrappedPicture {
383                 instance,
384             },
385             spatial_node_index: self.spatial_node_index,
386             flags: self.flags,
387         }
388     }
390     /// Finish building this picture chain. Set the clip chain on the outermost picture
391     fn finalize(
392         self,
393         clip_chain_id: ClipChainId,
394         interners: &mut Interners,
395         prim_store: &mut PrimitiveStore,
396     ) -> PrimitiveInstance {
397         match self.current {
398             PictureSource::WrappedPicture { mut instance } => {
399                 instance.clip_set.clip_chain_id = clip_chain_id;
400                 instance
401             }
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
407                     .alloc()
408                     .init(PicturePrimitive::new_image(
409                         None,
410                         Picture3DContext::Out,
411                         true,
412                         self.flags,
413                         prim_list,
414                         self.spatial_node_index,
415                         PictureOptions::default(),
416                     ))
417                 );
419                 create_prim_instance(
420                     pic_index,
421                     None.into(),
422                     clip_chain_id,
423                     interners,
424                 )
425             }
426         }
427     }
430 bitflags! {
431     /// Slice flags
432     pub struct SliceFlags : u8 {
433         /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER
434         const IS_SCROLLBAR = 1;
435     }
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.
443     scene: &'a Scene,
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> {
508     pub fn build(
509         scene: &Scene,
510         font_instances: SharedFontInstanceMap,
511         view: &SceneView,
512         frame_builder_config: &FrameBuilderConfig,
513         interners: &mut Interners,
514         stats: &SceneStats,
515     ) -> BuiltScene {
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
523             .background_color
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,
531             device_pixel_scale,
532         );
534         let mut builder = SceneBuilder {
535             scene,
536             spatial_tree,
537             font_instances,
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(),
547             interners,
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(),
553             snap_to_device,
554         };
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(
560             &builder.config,
561             &mut builder.clip_store,
562             &mut builder.prim_store,
563         );
565         BuiltScene {
566             has_root_pipeline: scene.has_root_pipeline(),
567             pipeline_epochs: scene.pipeline_epochs.clone(),
568             output_rect: view.device_rect.size.into(),
569             background_color,
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,
575             tile_cache_config,
576             tile_cache_pictures,
577         }
578     }
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
583     /// spatial node.
584     fn current_offset(
585         &mut self,
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(
595                 spatial_node_index,
596                 &self.spatial_tree,
597             );
599         rf_offset + scroll_offset
600     }
602     fn build_all(&mut self, root_pipeline: &ScenePipeline) {
603         enum ContextKind<'a> {
604             Root,
605             StackingContext {
606                 sc_info: StackingContextInfo,
607             },
608             ReferenceFrame,
609             Iframe {
610                 parent_traversal: BuiltDisplayListIter<'a>,
611             }
612         }
613         struct BuildContext<'a> {
614             pipeline_id: PipelineId,
615             kind: ContextKind<'a>,
616         }
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);
621         self.push_root(
622             root_pipeline.pipeline_id,
623             &root_pipeline.viewport_size,
624         );
626         let mut stack = vec![BuildContext {
627             pipeline_id: root_pipeline.pipeline_id,
628             kind: ContextKind::Root,
629         }];
630         let mut traversal = root_pipeline.display_list.iter();
632         'outer: while let Some(bc) = stack.pop() {
633             loop {
634                 let item = match traversal.next() {
635                     Some(item) => item,
636                     None => break,
637                 };
639                 match item.item() {
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;
648                             continue;
649                         }
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(),
656                         );
658                         let sc_info = self.push_stacking_context(
659                             composition_operations,
660                             info.stacking_context.transform_style,
661                             info.prim_flags,
662                             spatial_node_index,
663                             info.stacking_context.clip_id,
664                             info.stacking_context.raster_space,
665                             info.stacking_context.flags,
666                             bc.pipeline_id,
667                         );
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 {
673                                 sc_info,
674                             },
675                         };
676                         stack.push(bc);
677                         stack.push(new_context);
679                         subtraversal.merge_debug_stats_from(&mut traversal);
680                         traversal = subtraversal;
681                         continue 'outer;
682                     }
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.
698                                     match rotation {
699                                         Rotation::Degree0 |
700                                         Rotation::Degree180 => {
701                                             LayoutTransform::scale(
702                                                 content_size.width / scale_from.width,
703                                                 content_size.height / scale_from.height,
704                                                 1.0
705                                             )
706                                         },
707                                         Rotation::Degree90 |
708                                         Rotation::Degree270 => {
709                                             LayoutTransform::scale(
710                                                 content_size.height / scale_from.width,
711                                                 content_size.width / scale_from.height,
712                                                 1.0
713                                             )
715                                         }
716                                     }
717                                 } else {
718                                     LayoutTransform::identity()
719                                 };
721                                 if vertical_flip {
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);
726                                 }
728                                 let rotate = rotation.to_matrix(**content_size);
729                                 let transform = transform.then(&rotate);
731                                 PropertyBinding::Value(transform)
732                             },
733                         };
735                         self.push_reference_frame(
736                             info.reference_frame.id,
737                             Some(parent_space),
738                             bc.pipeline_id,
739                             info.reference_frame.transform_style,
740                             transform,
741                             info.reference_frame.kind,
742                             current_offset + info.origin.to_vector(),
743                         );
745                         self.rf_mapper.push_scope();
746                         let new_context = BuildContext {
747                             pipeline_id: bc.pipeline_id,
748                             kind: ContextKind::ReferenceFrame,
749                         };
750                         stack.push(bc);
751                         stack.push(new_context);
753                         subtraversal.merge_debug_stats_from(&mut traversal);
754                         traversal = subtraversal;
755                         continue 'outer;
756                     }
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) {
764                             Some(pair) => pair,
765                             None => continue,
766                         };
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());
772                         }
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),
781                             },
782                         };
783                         stack.push(bc);
784                         stack.push(new_context);
785                         continue 'outer;
786                     }
787                     _ => {
788                         self.build_item(item, bc.pipeline_id);
789                     }
790                 };
791             }
793             match bc.kind {
794                 ContextKind::Root => {}
795                 ContextKind::StackingContext { sc_info } => {
796                     self.rf_mapper.pop_offset();
797                     self.pop_stacking_context(sc_info);
798                 }
799                 ContextKind::ReferenceFrame => {
800                     self.rf_mapper.pop_scope();
801                 }
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());
809                     }
811                     traversal = parent_traversal;
812                 }
813             }
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, {}%, {}",
822                         label,
823                         stats.total_count,
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));
827                 }
828                 println!();
829             }
830         }
832         self.clip_store.pop_clip_root();
833         debug_assert!(self.sc_stack.is_empty());
834     }
836     fn build_sticky_frame(
837         &mut self,
838         info: &StickyFrameDisplayItem,
839         parent_node_index: SpatialNodeIndex,
840     ) {
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(
844             frame_rect,
845             info.margins,
846             info.vertical_offset_bounds,
847             info.horizontal_offset_bounds,
848             info.previously_applied_offset,
849         );
851         let index = self.spatial_tree.add_sticky_frame(
852             parent_node_index,
853             sticky_frame_info,
854             info.id.pipeline_id(),
855         );
856         self.id_to_index_mapper.add_spatial_node(info.id, index);
857     }
859     fn build_scroll_frame(
860         &mut self,
861         info: &ScrollFrameDisplayItem,
862         parent_node_index: SpatialNodeIndex,
863         pipeline_id: PipelineId,
864     ) {
865         let current_offset = self.current_offset(parent_node_index);
866         let clip_region = ClipRegion::create_for_clip_node_with_local_clip(
867             &info.clip_rect,
868             &current_offset,
869         );
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,
881             parent_node_index,
882             info.external_id,
883             pipeline_id,
884             &frame_rect,
885             &content_size,
886             info.scroll_sensitivity,
887             ScrollFrameKind::Explicit,
888             info.external_scroll_offset,
889         );
890     }
892     fn push_iframe(
893         &mut self,
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,
900             None => {
901                 debug_assert!(info.ignore_missing_pipeline);
902                 return None
903             },
904         };
906         let current_offset = self.current_offset(spatial_node_index);
907         self.add_clip_node(
908             ClipId::root(iframe_pipeline_id),
909             &info.space_and_clip,
910             ClipRegion::create_for_clip_node_with_local_clip(
911                 &info.clip_rect,
912                 &current_offset,
913             ),
914         );
916         self.clip_store.push_clip_root(
917             Some(ClipId::root(iframe_pipeline_id)),
918             true,
919         );
921         let bounds = self.snap_rect(
922             &info.bounds.translate(current_offset),
923             spatial_node_index,
924         );
926         let spatial_node_index = self.push_reference_frame(
927             SpatialId::root_reference_frame(iframe_pipeline_id),
928             Some(spatial_node_index),
929             iframe_pipeline_id,
930             TransformStyle::Flat,
931             PropertyBinding::Value(LayoutTransform::identity()),
932             ReferenceFrameKind::Transform {
933                 is_2d_scale_translation: false,
934                 should_snap: false
935             },
936             bounds.origin.to_vector(),
937         );
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),
944             spatial_node_index,
945             ExternalScrollId(0, iframe_pipeline_id),
946             iframe_pipeline_id,
947             &iframe_rect,
948             &bounds.size,
949             ScrollSensitivity::ScriptAndInputEvents,
950             ScrollFrameKind::PipelineRoot {
951                 is_root_pipeline,
952             },
953             LayoutVector2D::zero(),
954         );
956         Some((bounds.size, pipeline.display_list.iter()))
957     }
959     fn get_space(
960         &self,
961         spatial_id: SpatialId,
962     ) -> SpatialNodeIndex {
963         self.id_to_index_mapper.get_spatial_node_index(spatial_id)
964     }
966     fn get_clip_chain(
967         &mut self,
968         clip_id: ClipId,
969     ) -> ClipChainId {
970         self.clip_store.get_or_build_clip_chain_id(clip_id)
971     }
973     fn process_common_properties(
974         &mut self,
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,
986             spatial_node_index,
987         );
989         let unsnapped_rect = bounds.map(|bounds| {
990             bounds.translate(current_offset)
991         });
993         // If no bounds rect is given, default to clip rect.
994         let rect = unsnapped_rect.map_or(clip_rect, |bounds| {
995             self.snap_rect(
996                 &bounds,
997                 spatial_node_index,
998             )
999         });
1001         let layout = LayoutPrimitiveInfo {
1002             rect,
1003             clip_rect,
1004             flags: common.flags,
1005         };
1007         (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id)
1008     }
1010     fn process_common_properties_with_bounds(
1011         &mut self,
1012         common: &CommonItemProperties,
1013         bounds: &LayoutRect,
1014     ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) {
1015         self.process_common_properties(
1016             common,
1017             Some(bounds),
1018         )
1019     }
1021     pub fn snap_rect(
1022         &mut self,
1023         rect: &LayoutRect,
1024         target_spatial_node: SpatialNodeIndex,
1025     ) -> LayoutRect {
1026         self.snap_to_device.set_target_spatial_node(
1027             target_spatial_node,
1028             &self.spatial_tree
1029         );
1030         self.snap_to_device.snap_rect(rect)
1031     }
1033     fn build_item<'b>(
1034         &'b mut self,
1035         item: DisplayItemRef,
1036         pipeline_id: PipelineId,
1037     ) {
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(
1043                     &info.common,
1044                     &info.bounds,
1045                 );
1047                 self.add_image(
1048                     spatial_node_index,
1049                     clip_chain_id,
1050                     &layout,
1051                     layout.rect.size,
1052                     LayoutSize::zero(),
1053                     info.image_key,
1054                     info.image_rendering,
1055                     info.alpha_type,
1056                     info.color,
1057                 );
1058             }
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(
1063                     &info.common,
1064                     &info.bounds,
1065                 );
1067                 let stretch_size = process_repeat_size(
1068                     &layout.rect,
1069                     &unsnapped_rect,
1070                     info.stretch_size,
1071                 );
1073                 self.add_image(
1074                     spatial_node_index,
1075                     clip_chain_id,
1076                     &layout,
1077                     stretch_size,
1078                     info.tile_spacing,
1079                     info.image_key,
1080                     info.image_rendering,
1081                     info.alpha_type,
1082                     info.color,
1083                 );
1084             }
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(
1089                     &info.common,
1090                     &info.bounds,
1091                 );
1093                 self.add_yuv_image(
1094                     spatial_node_index,
1095                     clip_chain_id,
1096                     &layout,
1097                     info.yuv_data,
1098                     info.color_depth,
1099                     info.color_space,
1100                     info.color_range,
1101                     info.image_rendering,
1102                 );
1103             }
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(
1114                     &info.common,
1115                     &info.bounds,
1116                 );
1118                 self.add_text(
1119                     spatial_node_index,
1120                     clip_chain_id,
1121                     &layout,
1122                     &info.font_key,
1123                     &info.color,
1124                     item.glyphs(),
1125                     info.glyph_options,
1126                 );
1127             }
1128             DisplayItem::Rectangle(ref info) => {
1129                 profile_scope!("rect");
1131                 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1132                     &info.common,
1133                     &info.bounds,
1134                 );
1136                 self.add_primitive(
1137                     spatial_node_index,
1138                     clip_chain_id,
1139                     &layout,
1140                     Vec::new(),
1141                     PrimitiveKeyKind::Rectangle {
1142                         color: info.color.into(),
1143                     },
1144                 );
1145             }
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
1150                 //           hit-test items.
1151                 let (layout, _, spatial_node_index, _) = self.process_common_properties(
1152                     &info.common,
1153                     None,
1154                 );
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(
1160                     &layout,
1161                     spatial_node_index,
1162                     info.common.clip_id,
1163                     info.tag,
1164                 );
1165             }
1166             DisplayItem::ClearRectangle(ref info) => {
1167                 profile_scope!("clear");
1169                 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1170                     &info.common,
1171                     &info.bounds,
1172                 );
1174                 self.add_clear_rectangle(
1175                     spatial_node_index,
1176                     clip_chain_id,
1177                     &layout,
1178                 );
1179             }
1180             DisplayItem::Line(ref info) => {
1181                 profile_scope!("line");
1183                 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1184                     &info.common,
1185                     &info.area,
1186                 );
1188                 self.add_line(
1189                     spatial_node_index,
1190                     clip_chain_id,
1191                     &layout,
1192                     info.wavy_line_thickness,
1193                     info.orientation,
1194                     info.color,
1195                     info.style,
1196                 );
1197             }
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(
1202                     &info.common,
1203                     &info.bounds,
1204                 );
1206                 let tile_size = process_repeat_size(
1207                     &layout.rect,
1208                     &unsnapped_rect,
1209                     info.tile_size,
1210                 );
1212                 if let Some(prim_key_kind) = self.create_linear_gradient_prim(
1213                     &layout,
1214                     info.gradient.start_point,
1215                     info.gradient.end_point,
1216                     item.gradient_stops(),
1217                     info.gradient.extend_mode,
1218                     tile_size,
1219                     info.tile_spacing,
1220                     None,
1221                 ) {
1222                     self.add_nonshadowable_primitive(
1223                         spatial_node_index,
1224                         clip_chain_id,
1225                         &layout,
1226                         Vec::new(),
1227                         prim_key_kind,
1228                     );
1229                 }
1230             }
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(
1235                     &info.common,
1236                     &info.bounds,
1237                 );
1239                 let stops = read_gradient_stops(item.gradient_stops());
1241                 let mut tile_size = process_repeat_size(
1242                     &layout.rect,
1243                     &unsnapped_rect,
1244                     info.tile_size,
1245                 );
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(
1251                     &mut prim_rect,
1252                     &mut tile_size,
1253                     &mut center,
1254                     &mut tile_spacing,
1255                     info.gradient.radius,
1256                     info.gradient.extend_mode,
1257                     &stops,
1258                     &mut |solid_rect, color| {
1259                         self.add_nonshadowable_primitive(
1260                             spatial_node_index,
1261                             clip_chain_id,
1262                             &LayoutPrimitiveInfo {
1263                                 rect: *solid_rect,
1264                                 .. layout
1265                             },
1266                             Vec::new(),
1267                             PrimitiveKeyKind::Rectangle { color: PropertyBinding::Value(color) },
1268                         );
1269                     }
1270                 );
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(
1281                         &layout,
1282                         center,
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,
1286                         stops,
1287                         info.gradient.extend_mode,
1288                         tile_size,
1289                         tile_spacing,
1290                         None,
1291                     );
1293                     self.add_nonshadowable_primitive(
1294                         spatial_node_index,
1295                         clip_chain_id,
1296                         &layout,
1297                         Vec::new(),
1298                         prim_key_kind,
1299                     );
1300                 }
1301             }
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(
1306                     &info.common,
1307                     &info.bounds,
1308                 );
1310                 let tile_size = process_repeat_size(
1311                     &layout.rect,
1312                     &unsnapped_rect,
1313                     info.tile_size,
1314                 );
1316                 if !tile_size.to_i32().is_empty() {
1317                     let prim_key_kind = self.create_conic_gradient_prim(
1318                         &layout,
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,
1325                         tile_size,
1326                         info.tile_spacing,
1327                         None,
1328                     );
1330                     self.add_nonshadowable_primitive(
1331                         spatial_node_index,
1332                         clip_chain_id,
1333                         &layout,
1334                         Vec::new(),
1335                         prim_key_kind,
1336                     );
1337                 }
1338             }
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(
1343                     &info.common,
1344                     &info.box_bounds,
1345                 );
1347                 self.add_box_shadow(
1348                     spatial_node_index,
1349                     clip_chain_id,
1350                     &layout,
1351                     &info.offset,
1352                     info.color,
1353                     info.blur_radius,
1354                     info.spread_radius,
1355                     info.border_radius,
1356                     info.clip_mode,
1357                 );
1358             }
1359             DisplayItem::Border(ref info) => {
1360                 profile_scope!("border");
1362                 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds(
1363                     &info.common,
1364                     &info.bounds,
1365                 );
1367                 self.add_border(
1368                     spatial_node_index,
1369                     clip_chain_id,
1370                     &layout,
1371                     info,
1372                     item.gradient_stops(),
1373                 );
1374             }
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),
1383                     ..info.image_mask
1384                 };
1386                 self.add_image_mask_clip_node(
1387                     info.id,
1388                     &info.parent_space_and_clip,
1389                     &image_mask,
1390                     info.fill_rule,
1391                     item.points(),
1392                 );
1393             }
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(
1401                     info.id,
1402                     &info.parent_space_and_clip,
1403                     &info.clip,
1404                     current_offset,
1405                 );
1406             }
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(
1415                     info.id,
1416                     &info.parent_space_and_clip,
1417                     &clip_rect,
1418                 );
1419             }
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(
1426                     info.clip_rect,
1427                     item.complex_clip().iter(),
1428                     &current_offset,
1429                 );
1430                 self.add_clip_node(info.id, &info.parent_space_and_clip, clip_region);
1431             }
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);
1441                 }
1443                 self.clip_store.register_clip_template(
1444                     ClipId::ClipChain(info.id),
1445                     parent,
1446                     &clips,
1447                 );
1448             },
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(
1454                     info,
1455                     parent_space,
1456                     pipeline_id,
1457                 );
1458             }
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(
1464                     info,
1465                     parent_space,
1466                 );
1467             }
1468             DisplayItem::BackdropFilter(ref info) => {
1469                 profile_scope!("backdrop");
1471                 let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties(
1472                     &info.common,
1473                     None,
1474                 );
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(
1481                     spatial_node_index,
1482                     clip_chain_id,
1483                     &layout,
1484                     filters,
1485                     filter_datas,
1486                     filter_primitives,
1487                 );
1488             }
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`")
1504             }
1506             DisplayItem::ReuseItems(key) |
1507             DisplayItem::RetainedItems(key) => {
1508                 unreachable!("Iterator logic error: {:?}", key);
1509             }
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,
1517                 );
1519                 self.push_shadow(
1520                     info.shadow,
1521                     spatial_node_index,
1522                     clip_chain_id,
1523                     info.should_inflate,
1524                 );
1525             }
1526             DisplayItem::PopAllShadows => {
1527                 profile_scope!("pop_all_shadows");
1529                 self.pop_all_shadows();
1530             }
1531         }
1532     }
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(
1539         &mut self,
1540         clip_items: Vec<ClipItemKey>,
1541         spatial_node_index: SpatialNodeIndex,
1542         parent_clip_chain_id: ClipChainId,
1543     ) -> ClipChainId {
1544         if clip_items.is_empty() {
1545             parent_clip_chain_id
1546         } else {
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
1553                     .clip
1554                     .intern(&item, || {
1555                         ClipInternData {
1556                             clip_node_kind: item.kind.node_kind(),
1557                         }
1558                     });
1560                 clip_chain_id = self.clip_store.add_clip_chain_node(
1561                     handle,
1562                     spatial_node_index,
1563                     clip_chain_id,
1564                 );
1565             }
1567             clip_chain_id
1568         }
1569     }
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
1573     /// sub-primitives.
1574     ///
1575     /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list`
1576     fn create_primitive<P>(
1577         &mut self,
1578         info: &LayoutPrimitiveInfo,
1579         spatial_node_index: SpatialNodeIndex,
1580         clip_chain_id: ClipChainId,
1581         prim: P,
1582     ) -> PrimitiveInstance
1583     where
1584         P: InternablePrimitive,
1585         Interners: AsMut<Interner<P>>,
1586     {
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(
1596             prim_key,
1597             prim_data_handle,
1598             &mut self.prim_store,
1599             current_offset,
1600         );
1602         PrimitiveInstance::new(
1603             info.clip_rect,
1604             instance_kind,
1605             clip_chain_id,
1606         )
1607     }
1609     pub fn add_primitive_to_hit_testing_list(
1610         &mut self,
1611         info: &LayoutPrimitiveInfo,
1612         spatial_node_index: SpatialNodeIndex,
1613         clip_id: ClipId,
1614         tag: ItemTag,
1615     ) {
1616         self.hit_testing_scene.add_item(
1617             tag,
1618             info,
1619             spatial_node_index,
1620             clip_id,
1621             &self.clip_store,
1622         );
1623     }
1625     /// Add an already created primitive to the draw lists.
1626     pub fn add_primitive_to_draw_list(
1627         &mut self,
1628         prim_instance: PrimitiveInstance,
1629         prim_rect: LayoutRect,
1630         spatial_node_index: SpatialNodeIndex,
1631         flags: PrimitiveFlags,
1632     ) {
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());
1636         }
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(
1644                     prim_instance,
1645                     prim_rect,
1646                     spatial_node_index,
1647                     flags,
1648                 );
1649             }
1650             None => {
1651                 self.tile_cache_builder.add_prim(
1652                     prim_instance,
1653                     prim_rect,
1654                     spatial_node_index,
1655                     flags,
1656                     &self.spatial_tree,
1657                     &self.clip_store,
1658                     self.interners,
1659                     &self.config,
1660                     &self.quality_settings,
1661                 );
1662             }
1663         }
1664     }
1666     /// Convenience interface that creates a primitive entry and adds it
1667     /// to the draw list.
1668     fn add_nonshadowable_primitive<P>(
1669         &mut self,
1670         spatial_node_index: SpatialNodeIndex,
1671         clip_chain_id: ClipChainId,
1672         info: &LayoutPrimitiveInfo,
1673         clip_items: Vec<ClipItemKey>,
1674         prim: P,
1675     )
1676     where
1677         P: InternablePrimitive + IsVisible,
1678         Interners: AsMut<Interner<P>>,
1679     {
1680         if prim.is_visible() {
1681             let clip_chain_id = self.build_clip_chain(
1682                 clip_items,
1683                 spatial_node_index,
1684                 clip_chain_id,
1685             );
1686             self.add_prim_to_draw_list(
1687                 info,
1688                 spatial_node_index,
1689                 clip_chain_id,
1690                 prim,
1691             );
1692         }
1693     }
1695     pub fn add_primitive<P>(
1696         &mut self,
1697         spatial_node_index: SpatialNodeIndex,
1698         clip_chain_id: ClipChainId,
1699         info: &LayoutPrimitiveInfo,
1700         clip_items: Vec<ClipItemKey>,
1701         prim: P,
1702     )
1703     where
1704         P: InternablePrimitive + IsVisible,
1705         Interners: AsMut<Interner<P>>,
1706         ShadowItem: From<PendingPrimitive<P>>
1707     {
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(
1712                 spatial_node_index,
1713                 clip_chain_id,
1714                 info,
1715                 clip_items,
1716                 prim,
1717             );
1718         } else {
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 {
1724                 spatial_node_index,
1725                 clip_chain_id,
1726                 info: *info,
1727                 prim,
1728             }.into());
1729         }
1730     }
1732     fn add_prim_to_draw_list<P>(
1733         &mut self,
1734         info: &LayoutPrimitiveInfo,
1735         spatial_node_index: SpatialNodeIndex,
1736         clip_chain_id: ClipChainId,
1737         prim: P,
1738     )
1739     where
1740         P: InternablePrimitive,
1741         Interners: AsMut<Interner<P>>,
1742     {
1743         let prim_instance = self.create_primitive(
1744             info,
1745             spatial_node_index,
1746             clip_chain_id,
1747             prim,
1748         );
1749         self.register_chase_primitive_by_rect(
1750             &info.rect,
1751             &prim_instance,
1752         );
1753         self.add_primitive_to_draw_list(
1754             prim_instance,
1755             info.rect,
1756             spatial_node_index,
1757             info.flags,
1758         );
1759     }
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(
1764         &mut self,
1765         slice_flags: SliceFlags,
1766     ) {
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);
1772         }
1773     }
1775     /// Push a new stacking context. Returns context that must be passed to pop_stacking_context().
1776     fn push_stacking_context(
1777         &mut self,
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 {
1799                         root_data: None,
1800                         ancestor_index,
1801                     },
1802                     Picture3DContext::Out => panic!("Unexpected out of 3D context"),
1803                 };
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,
1811                 );
1812                 let extra_instance = extra_instance.map(|(_, instance)| {
1813                     ExtendedPrimitiveInstance {
1814                         instance,
1815                         spatial_node_index: sc.spatial_node_index,
1816                         flags: sc.prim_flags,
1817                     }
1818                 });
1819                 (true, extra_instance)
1820             },
1821             _ => (false, None),
1822         };
1824         if let Some(instance) = extra_3d_instance {
1825             self.add_primitive_instance_to_3d_root(instance);
1826         }
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
1841                 .last()
1842                 .cloned()
1843                 .unwrap_or(ROOT_SPATIAL_NODE_INDEX);
1845             Picture3DContext::In {
1846                 root_data: if parent_is_3d {
1847                     None
1848                 } else {
1849                     Some(Vec::new())
1850                 },
1851                 ancestor_index,
1852             }
1853         } else {
1854             Picture3DContext::Out
1855         };
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;
1865         }
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;
1872             }
1873         }
1875         let is_redundant = FlattenedStackingContext::is_redundant(
1876             flags,
1877             &context_3d,
1878             &composite_ops,
1879             blit_reason,
1880             self.sc_stack.last(),
1881             prim_flags,
1882         );
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);
1890         }
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,
1897         };
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);
1903         }
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 {
1909             ClipChainId::NONE
1910         }  else {
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)
1916         };
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;
1922         }
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
1930         // frame building.
1931         if is_redundant {
1932             self.clip_store.push_clip_root(clip_id, true);
1933         } else {
1934             self.clip_store.push_clip_root(None, false);
1935         }
1937         // If not redundant, create a stacking context to hold primitive clusters
1938         if !is_redundant {
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(),
1945                 prim_flags,
1946                 spatial_node_index,
1947                 clip_chain_id,
1948                 composite_ops,
1949                 blit_reason,
1950                 transform_style,
1951                 context_3d,
1952                 is_redundant,
1953                 is_backdrop_root: flags.contains(StackingContextFlags::IS_BACKDROP_ROOT),
1954                 flags,
1955             });
1956         }
1958         sc_info
1959     }
1961     fn pop_stacking_context(
1962         &mut self,
1963         info: StackingContextInfo,
1964     ) {
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();
1976         }
1978         if info.set_tile_cache_barrier {
1979             self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
1980         }
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();
1985         }
1987         // If the stacking context was otherwise redundant, early exit
1988         if !info.pop_stacking_context {
1989             return;
1990         }
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()
2002         {
2003             self.tile_cache_builder.add_tile_cache(
2004                 stacking_context.prim_list,
2005                 stacking_context.clip_chain_id,
2006                 &self.spatial_tree,
2007                 &self.clip_store,
2008                 self.interners,
2009                 &self.config,
2010             );
2012             return;
2013         }
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()
2019             },
2020             None => true,
2021         };
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)
2034                 );
2036                 // Add picture for this actual stacking context contents to render into.
2037                 let pic_index = PictureIndex(self.prim_store.pictures
2038                     .alloc()
2039                     .init(PicturePrimitive::new_image(
2040                         composite_mode.clone(),
2041                         Picture3DContext::In { root_data: None, ancestor_index },
2042                         true,
2043                         stacking_context.prim_flags,
2044                         stacking_context.prim_list,
2045                         stacking_context.spatial_node_index,
2046                         PictureOptions::default(),
2047                     ))
2048                 );
2050                 let instance = create_prim_instance(
2051                     pic_index,
2052                     composite_mode.into(),
2053                     ClipChainId::NONE,
2054                     &mut self.interners,
2055                 );
2057                 PictureChainBuilder::from_instance(
2058                     instance,
2059                     stacking_context.prim_flags,
2060                     stacking_context.spatial_node_index,
2061                 )
2062             }
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,
2069                     )
2070                 } else {
2071                     let composite_mode = Some(
2072                         PictureCompositeMode::Blit(stacking_context.blit_reason)
2073                     );
2075                     // Add picture for this actual stacking context contents to render into.
2076                     let pic_index = PictureIndex(self.prim_store.pictures
2077                         .alloc()
2078                         .init(PicturePrimitive::new_image(
2079                             composite_mode.clone(),
2080                             Picture3DContext::Out,
2081                             true,
2082                             stacking_context.prim_flags,
2083                             stacking_context.prim_list,
2084                             stacking_context.spatial_node_index,
2085                             PictureOptions::default(),
2086                         ))
2087                     );
2089                     let instance = create_prim_instance(
2090                         pic_index,
2091                         composite_mode.into(),
2092                         ClipChainId::NONE,
2093                         &mut self.interners,
2094                     );
2096                     PictureChainBuilder::from_instance(
2097                         instance,
2098                         stacking_context.prim_flags,
2099                         stacking_context.spatial_node_index,
2100                     )
2101                 }
2102             }
2103         };
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(
2110                 ClipChainId::NONE,
2111                 &mut self.interners,
2112                 &mut self.prim_store,
2113             );
2115             prims.push(ExtendedPrimitiveInstance {
2116                 instance,
2117                 spatial_node_index: stacking_context.spatial_node_index,
2118                 flags: stacking_context.prim_flags,
2119             });
2121             let mut prim_list = PrimitiveList::empty();
2122             for ext_prim in prims.drain(..) {
2123                 prim_list.add_prim(
2124                     ext_prim.instance,
2125                     LayoutRect::zero(),
2126                     ext_prim.spatial_node_index,
2127                     ext_prim.flags,
2128                 );
2129             }
2131             // This is the acttual picture representing our 3D hierarchy root.
2132             let pic_index = PictureIndex(self.prim_store.pictures
2133                 .alloc()
2134                 .init(PicturePrimitive::new_image(
2135                     None,
2136                     Picture3DContext::In {
2137                         root_data: Some(Vec::new()),
2138                         ancestor_index,
2139                     },
2140                     true,
2141                     stacking_context.prim_flags,
2142                     prim_list,
2143                     stacking_context.spatial_node_index,
2144                     PictureOptions::default(),
2145                 ))
2146             );
2148             let instance = create_prim_instance(
2149                 pic_index,
2150                 PictureCompositeKey::Identity,
2151                 ClipChainId::NONE,
2152                 &mut self.interners,
2153             );
2155             source = PictureChainBuilder::from_instance(
2156                 instance,
2157                 stacking_context.prim_flags,
2158                 stacking_context.spatial_node_index,
2159             );
2160         }
2162         let has_filters = stacking_context.composite_ops.has_valid_filters();
2164         source = self.wrap_prim_with_filters(
2165             source,
2166             stacking_context.composite_ops.filters,
2167             stacking_context.composite_ops.filter_primitives,
2168             stacking_context.composite_ops.filter_datas,
2169             true,
2170         );
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)
2176         // where
2177         // Cs = Source color
2178         // ab = Backdrop alpha
2179         // Cb = Backdrop color
2180         //
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),
2187                 None => false,
2188             };
2189             if parent_is_isolated {
2190                 let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode);
2192                 source = source.add_picture(
2193                     composite_mode,
2194                     Picture3DContext::Out,
2195                     PictureOptions::default(),
2196                     &mut self.interners,
2197                     &mut self.prim_store,
2198                 );
2199             } else {
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");
2204             }
2205         }
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,
2213         );
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() => {
2220                 Some(cur_instance)
2221             }
2222             // Regular parenting path
2223             Some(ref mut parent_sc) => {
2224                 parent_sc.prim_list.add_prim(
2225                     cur_instance,
2226                     LayoutRect::zero(),
2227                     stacking_context.spatial_node_index,
2228                     stacking_context.prim_flags,
2229                 );
2230                 None
2231             }
2232             // This must be the root stacking context
2233             None => {
2234                 self.add_primitive_to_draw_list(
2235                     cur_instance,
2236                     LayoutRect::zero(),
2237                     stacking_context.spatial_node_index,
2238                     stacking_context.prim_flags,
2239                 );
2241                 None
2242             }
2243         };
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 {
2249                 instance,
2250                 spatial_node_index: stacking_context.spatial_node_index,
2251                 flags: stacking_context.prim_flags,
2252             });
2253         }
2255         assert!(
2256             self.pending_shadow_items.is_empty(),
2257             "Found unpopped shadows when popping stacking context!"
2258         );
2259     }
2261     pub fn push_reference_frame(
2262         &mut self,
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(
2272             parent_index,
2273             transform_style,
2274             source_transform,
2275             kind,
2276             origin_in_parent_reference_frame,
2277             pipeline_id,
2278         );
2279         self.id_to_index_mapper.add_spatial_node(reference_frame_id, index);
2281         index
2282     }
2284     pub fn push_root(
2285         &mut self,
2286         pipeline_id: PipelineId,
2287         viewport_size: &LayoutSize,
2288     ) {
2289         if let ChasePrimitive::Id(id) = self.config.chase_primitive {
2290             println!("Chasing {:?} by index", id);
2291             register_prim_chase_id(id);
2292         }
2294         let spatial_node_index = self.push_reference_frame(
2295             SpatialId::root_reference_frame(pipeline_id),
2296             None,
2297             pipeline_id,
2298             TransformStyle::Flat,
2299             PropertyBinding::Value(LayoutTransform::identity()),
2300             ReferenceFrameKind::Transform {
2301                 is_2d_scale_translation: false,
2302                 should_snap: false,
2303             },
2304             LayoutVector2D::zero(),
2305         );
2307         let viewport_rect = self.snap_rect(
2308             &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
2309             spatial_node_index,
2310         );
2312         self.add_scroll_frame(
2313             SpatialId::root_scroll_node(pipeline_id),
2314             spatial_node_index,
2315             ExternalScrollId(0, pipeline_id),
2316             pipeline_id,
2317             &viewport_rect,
2318             &viewport_rect.size,
2319             ScrollSensitivity::ScriptAndInputEvents,
2320             ScrollFrameKind::PipelineRoot {
2321                 is_root_pipeline: true,
2322             },
2323             LayoutVector2D::zero(),
2324         );
2325     }
2327     fn add_image_mask_clip_node(
2328         &mut self,
2329         new_node_id: ClipId,
2330         space_and_clip: &SpaceAndClipInfo,
2331         image_mask: &ImageMask,
2332         _fill_rule: FillRule,
2333         _points_range: ItemRange<LayoutPoint>,
2334     ) {
2335         // TODO(bradwerth): incorporate fill_rule and points_range into
2336         // the ClipItemKey.
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(
2340             &image_mask.rect,
2341             spatial_node_index,
2342         );
2343         let item = ClipItemKey {
2344             kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect),
2345         };
2347         let handle = self
2348             .interners
2349             .clip
2350             .intern(&item, || {
2351                 ClipInternData {
2352                     clip_node_kind: ClipNodeKind::Complex,
2353                 }
2354             });
2356         let instance = SceneClipInstance {
2357             key: item,
2358             clip: ClipInstance::new(handle, spatial_node_index),
2359         };
2361         self.clip_store.register_clip_template(
2362             new_node_id,
2363             space_and_clip.clip_id,
2364             &[instance],
2365         );
2366     }
2368     /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`.
2369     pub fn add_rect_clip_node(
2370         &mut self,
2371         new_node_id: ClipId,
2372         space_and_clip: &SpaceAndClipInfo,
2373         clip_rect: &LayoutRect,
2374     ) {
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(
2378             clip_rect,
2379             spatial_node_index,
2380         );
2382         let item = ClipItemKey {
2383             kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
2384         };
2385         let handle = self
2386             .interners
2387             .clip
2388             .intern(&item, || {
2389                 ClipInternData {
2390                     clip_node_kind: ClipNodeKind::Rectangle,
2391                 }
2392             });
2394         let instance = SceneClipInstance {
2395             key: item,
2396             clip: ClipInstance::new(handle, spatial_node_index),
2397         };
2399         self.clip_store.register_clip_template(
2400             new_node_id,
2401             space_and_clip.clip_id,
2402             &[instance],
2403         );
2404     }
2406     pub fn add_rounded_rect_clip_node(
2407         &mut self,
2408         new_node_id: ClipId,
2409         space_and_clip: &SpaceAndClipInfo,
2410         clip: &ComplexClipRegion,
2411         current_offset: LayoutVector2D,
2412     ) {
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),
2417             spatial_node_index,
2418         );
2419         let item = ClipItemKey {
2420             kind: ClipItemKeyKind::rounded_rect(
2421                 snapped_region_rect,
2422                 clip.radii,
2423                 clip.mode,
2424             ),
2425         };
2427         let handle = self
2428             .interners
2429             .clip
2430             .intern(&item, || {
2431                 ClipInternData {
2432                     clip_node_kind: ClipNodeKind::Complex,
2433                 }
2434             });
2436         let instance = SceneClipInstance {
2437             key: item,
2438             clip: ClipInstance::new(handle, spatial_node_index),
2439         };
2441         self.clip_store.register_clip_template(
2442             new_node_id,
2443             space_and_clip.clip_id,
2444             &[instance],
2445         );
2446     }
2448     pub fn add_clip_node<I>(
2449         &mut self,
2450         new_node_id: ClipId,
2451         space_and_clip: &SpaceAndClipInfo,
2452         clip_region: ClipRegion<I>,
2453     )
2454     where
2455         I: IntoIterator<Item = ComplexClipRegion>
2456     {
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(
2461             &clip_region.main,
2462             spatial_node_index,
2463         );
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),
2474         };
2475         let handle = self
2476             .interners
2477             .clip
2478             .intern(&item, || {
2479                 ClipInternData {
2480                     clip_node_kind: ClipNodeKind::Rectangle,
2481                 }
2482             });
2483         instances.push(
2484             SceneClipInstance {
2485                 key: item,
2486                 clip: ClipInstance::new(handle, spatial_node_index),
2487             },
2488         );
2490         for region in clip_region.complex_clips {
2491             let snapped_region_rect = self.snap_rect(&region.rect, spatial_node_index);
2492             let item = ClipItemKey {
2493                 kind: ClipItemKeyKind::rounded_rect(
2494                     snapped_region_rect,
2495                     region.radii,
2496                     region.mode,
2497                 ),
2498             };
2500             let handle = self
2501                 .interners
2502                 .clip
2503                 .intern(&item, || {
2504                     ClipInternData {
2505                         clip_node_kind: ClipNodeKind::Complex,
2506                     }
2507                 });
2509             instances.push(
2510                 SceneClipInstance {
2511                     key: item,
2512                     clip: ClipInstance::new(handle, spatial_node_index),
2513                 },
2514             );
2515         }
2517         self.clip_store.register_clip_template(
2518             new_node_id,
2519             space_and_clip.clip_id,
2520             &instances,
2521         );
2522     }
2524     pub fn add_scroll_frame(
2525         &mut self,
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(
2537             parent_node_index,
2538             external_id,
2539             pipeline_id,
2540             frame_rect,
2541             content_size,
2542             scroll_sensitivity,
2543             frame_kind,
2544             external_scroll_offset,
2545         );
2546         self.id_to_index_mapper.add_spatial_node(new_node_id, node_index);
2547         node_index
2548     }
2550     pub fn push_shadow(
2551         &mut self,
2552         shadow: Shadow,
2553         spatial_node_index: SpatialNodeIndex,
2554         clip_chain_id: ClipChainId,
2555         should_inflate: bool,
2556     ) {
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 {
2560             shadow,
2561             spatial_node_index,
2562             clip_chain_id,
2563             should_inflate,
2564         }));
2565     }
2567     pub fn pop_all_shadows(
2568         &mut self,
2569     ) {
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());
2574         //
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:
2577         //
2578         // Iterate the list, popping an item from the front each iteration.
2579         //
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)
2585         //
2587         while let Some(item) = items.pop_front() {
2588             match item {
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(
2605                                     &pending_shadow,
2606                                     pending_image,
2607                                     blur_is_noop,
2608                                 )
2609                             }
2610                             ShadowItem::LineDecoration(ref pending_line_dec) => {
2611                                 self.create_shadow_prim(
2612                                     &pending_shadow,
2613                                     pending_line_dec,
2614                                     blur_is_noop,
2615                                 )
2616                             }
2617                             ShadowItem::NormalBorder(ref pending_border) => {
2618                                 self.create_shadow_prim(
2619                                     &pending_shadow,
2620                                     pending_border,
2621                                     blur_is_noop,
2622                                 )
2623                             }
2624                             ShadowItem::Primitive(ref pending_primitive) => {
2625                                 self.create_shadow_prim(
2626                                     &pending_shadow,
2627                                     pending_primitive,
2628                                     blur_is_noop,
2629                                 )
2630                             }
2631                             ShadowItem::TextRun(ref pending_text_run) => {
2632                                 self.create_shadow_prim(
2633                                     &pending_shadow,
2634                                     pending_text_run,
2635                                     blur_is_noop,
2636                                 )
2637                             }
2638                             _ => {
2639                                 continue;
2640                             }
2641                         };
2643                         if blur_is_noop {
2644                             self.add_primitive_to_draw_list(
2645                                 instance,
2646                                 info.rect,
2647                                 spatial_node_index,
2648                                 info.flags,
2649                             );
2650                         } else {
2651                             prim_list.add_prim(
2652                                 instance,
2653                                 info.rect,
2654                                 spatial_node_index,
2655                                 info.flags,
2656                             );
2657                         }
2658                     }
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,
2676                         };
2678                         // Create the primitive to draw the shadow picture into the scene.
2679                         let shadow_pic_index = PictureIndex(self.prim_store.pictures
2680                             .alloc()
2681                             .init(PicturePrimitive::new_image(
2682                                 composite_mode,
2683                                 Picture3DContext::Out,
2684                                 false,
2685                                 PrimitiveFlags::IS_BACKFACE_VISIBLE,
2686                                 prim_list,
2687                                 pending_shadow.spatial_node_index,
2688                                 options,
2689                             ))
2690                         );
2692                         let shadow_pic_key = PictureKey::new(
2693                             Picture { composite_mode_key },
2694                         );
2696                         let shadow_prim_data_handle = self.interners
2697                             .picture
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,
2706                             },
2707                             pending_shadow.clip_chain_id,
2708                         );
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,
2714                             LayoutRect::zero(),
2715                             pending_shadow.spatial_node_index,
2716                             PrimitiveFlags::IS_BACKFACE_VISIBLE,
2717                         );
2718                     }
2719                 }
2720                 ShadowItem::Image(pending_image) => {
2721                     self.add_shadow_prim_to_draw_list(
2722                         pending_image,
2723                     )
2724                 },
2725                 ShadowItem::LineDecoration(pending_line_dec) => {
2726                     self.add_shadow_prim_to_draw_list(
2727                         pending_line_dec,
2728                     )
2729                 },
2730                 ShadowItem::NormalBorder(pending_border) => {
2731                     self.add_shadow_prim_to_draw_list(
2732                         pending_border,
2733                     )
2734                 },
2735                 ShadowItem::Primitive(pending_primitive) => {
2736                     self.add_shadow_prim_to_draw_list(
2737                         pending_primitive,
2738                     )
2739                 },
2740                 ShadowItem::TextRun(pending_text_run) => {
2741                     self.add_shadow_prim_to_draw_list(
2742                         pending_text_run,
2743                     )
2744                 },
2745             }
2746         }
2748         debug_assert!(items.is_empty());
2749         self.pending_shadow_items = items;
2750     }
2752     fn create_shadow_prim<P>(
2753         &mut self,
2754         pending_shadow: &PendingShadow,
2755         pending_primitive: &PendingPrimitive<P>,
2756         blur_is_noop: bool,
2757     ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex)
2758     where
2759         P: InternablePrimitive + CreateShadow,
2760         Interners: AsMut<Interner<P>>,
2761     {
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,
2771         );
2772         info.clip_rect = self.snap_rect(
2773             &info.clip_rect.translate(pending_shadow.shadow.offset),
2774             pending_primitive.spatial_node_index,
2775         );
2777         // Construct and add a primitive for the given shadow.
2778         let shadow_prim_instance = self.create_primitive(
2779             &info,
2780             pending_primitive.spatial_node_index,
2781             pending_primitive.clip_chain_id,
2782             pending_primitive.prim.create_shadow(
2783                 &pending_shadow.shadow,
2784                 blur_is_noop,
2785                 self.raster_space_stack.last().cloned().unwrap(),
2786             ),
2787         );
2789         (shadow_prim_instance, info, pending_primitive.spatial_node_index)
2790     }
2792     fn add_shadow_prim_to_draw_list<P>(
2793         &mut self,
2794         pending_primitive: PendingPrimitive<P>,
2795     ) where
2796         P: InternablePrimitive + IsVisible,
2797         Interners: AsMut<Interner<P>>,
2798     {
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,
2807             );
2808         }
2809     }
2811     #[cfg(debug_assertions)]
2812     fn register_chase_primitive_by_rect(
2813         &mut self,
2814         rect: &LayoutRect,
2815         prim_instance: &PrimitiveInstance,
2816     ) {
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);
2820         }
2821     }
2823     #[cfg(not(debug_assertions))]
2824     fn register_chase_primitive_by_rect(
2825         &mut self,
2826         _rect: &LayoutRect,
2827         _prim_instance: &PrimitiveInstance,
2828     ) {
2829     }
2831     pub fn add_clear_rectangle(
2832         &mut self,
2833         spatial_node_index: SpatialNodeIndex,
2834         clip_chain_id: ClipChainId,
2835         info: &LayoutPrimitiveInfo,
2836     ) {
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());
2841         self.add_primitive(
2842             spatial_node_index,
2843             clip_chain_id,
2844             info,
2845             Vec::new(),
2846             PrimitiveKeyKind::Clear,
2847         );
2849         self.add_tile_cache_barrier_if_needed(SliceFlags::empty());
2850     }
2852     pub fn add_line(
2853         &mut self,
2854         spatial_node_index: SpatialNodeIndex,
2855         clip_chain_id: ClipChainId,
2856         info: &LayoutPrimitiveInfo,
2857         wavy_line_thickness: f32,
2858         orientation: LineOrientation,
2859         color: ColorF,
2860         style: LineStyle,
2861     ) {
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(
2868             &info.rect.size,
2869             orientation,
2870             style,
2871             wavy_line_thickness,
2872         );
2874         let cache_key = size.map(|size| {
2875             // If dotted, adjust the clip rect to ensure we don't draw a final
2876             // partial dot.
2877             if style == LineStyle::Dotted {
2878                 let clip_size = match orientation {
2879                     LineOrientation::Horizontal => {
2880                         LayoutSize::new(
2881                             size.width * (info.rect.size.width / size.width).floor(),
2882                             info.rect.size.height,
2883                         )
2884                     }
2885                     LineOrientation::Vertical => {
2886                         LayoutSize::new(
2887                             info.rect.size.width,
2888                             size.height * (info.rect.size.height / size.height).floor(),
2889                         )
2890                     }
2891                 };
2892                 let clip_rect = LayoutRect::new(
2893                     info.rect.origin,
2894                     clip_size,
2895                 );
2896                 info.clip_rect = clip_rect
2897                     .intersection(&info.clip_rect)
2898                     .unwrap_or_else(LayoutRect::zero);
2899             }
2901             LineDecorationCacheKey {
2902                 style,
2903                 orientation,
2904                 wavy_line_thickness: Au::from_f32_px(wavy_line_thickness),
2905                 size: size.to_au(),
2906             }
2907         });
2909         self.add_primitive(
2910             spatial_node_index,
2911             clip_chain_id,
2912             &info,
2913             Vec::new(),
2914             LineDecoration {
2915                 cache_key,
2916                 color: color.into(),
2917             },
2918         );
2919     }
2921     pub fn add_border(
2922         &mut self,
2923         spatial_node_index: SpatialNodeIndex,
2924         clip_chain_id: ClipChainId,
2925         info: &LayoutPrimitiveInfo,
2926         border_item: &BorderDisplayItem,
2927         gradient_stops: ItemRange<GradientStop>,
2928     ) {
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,
2935                     fill: border.fill,
2936                     repeat_horizontal: border.repeat_horizontal,
2937                     repeat_vertical: border.repeat_vertical,
2938                     outset: border.outset.into(),
2939                     widths: border_item.widths.into(),
2940                 };
2942                 match border.source {
2943                     NinePatchBorderSource::Image(image_key) => {
2944                         let prim = ImageBorder {
2945                             request: ImageRequest {
2946                                 key: image_key,
2947                                 rendering: ImageRendering::Auto,
2948                                 tile: None,
2949                             },
2950                             nine_patch,
2951                         };
2953                         self.add_nonshadowable_primitive(
2954                             spatial_node_index,
2955                             clip_chain_id,
2956                             info,
2957                             Vec::new(),
2958                             prim,
2959                         );
2960                     }
2961                     NinePatchBorderSource::Gradient(gradient) => {
2962                         let prim = match self.create_linear_gradient_prim(
2963                             &info,
2964                             gradient.start_point,
2965                             gradient.end_point,
2966                             gradient_stops,
2967                             gradient.extend_mode,
2968                             LayoutSize::new(border.height as f32, border.width as f32),
2969                             LayoutSize::zero(),
2970                             Some(Box::new(nine_patch)),
2971                         ) {
2972                             Some(prim) => prim,
2973                             None => return,
2974                         };
2976                         self.add_nonshadowable_primitive(
2977                             spatial_node_index,
2978                             clip_chain_id,
2979                             info,
2980                             Vec::new(),
2981                             prim,
2982                         );
2983                     }
2984                     NinePatchBorderSource::RadialGradient(gradient) => {
2985                         let prim = self.create_radial_gradient_prim(
2986                             &info,
2987                             gradient.center,
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),
2994                             LayoutSize::zero(),
2995                             Some(Box::new(nine_patch)),
2996                         );
2998                         self.add_nonshadowable_primitive(
2999                             spatial_node_index,
3000                             clip_chain_id,
3001                             info,
3002                             Vec::new(),
3003                             prim,
3004                         );
3005                     }
3006                     NinePatchBorderSource::ConicGradient(gradient) => {
3007                         let prim = self.create_conic_gradient_prim(
3008                             &info,
3009                             gradient.center,
3010                             gradient.angle,
3011                             gradient.start_offset,
3012                             gradient.end_offset,
3013                             gradient_stops,
3014                             gradient.extend_mode,
3015                             LayoutSize::new(border.height as f32, border.width as f32),
3016                             LayoutSize::zero(),
3017                             Some(Box::new(nine_patch)),
3018                         );
3020                         self.add_nonshadowable_primitive(
3021                             spatial_node_index,
3022                             clip_chain_id,
3023                             info,
3024                             Vec::new(),
3025                             prim,
3026                         );
3027                     }
3028                 };
3029             }
3030             BorderDetails::Normal(ref border) => {
3031                 self.add_normal_border(
3032                     info,
3033                     border,
3034                     border_item.widths,
3035                     spatial_node_index,
3036                     clip_chain_id,
3037                 );
3038             }
3039         }
3040     }
3042     pub fn create_linear_gradient_prim(
3043         &mut self,
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);
3060             GradientStopKey {
3061                 offset: stop.offset,
3062                 color: stop.color.into(),
3063             }
3064         }).collect();
3066         // If all the stops have no alpha, then this
3067         // gradient can't contribute to the scene.
3068         if max_alpha <= 0.0 {
3069             return None;
3070         }
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)
3086         } else {
3087             (start_point, end_point)
3088         };
3090         Some(LinearGradient {
3091             extend_mode,
3092             start_point: sp.into(),
3093             end_point: ep.into(),
3094             stretch_size: stretch_size.into(),
3095             tile_spacing: tile_spacing.into(),
3096             stops,
3097             reverse_stops,
3098             nine_patch,
3099         })
3100     }
3102     pub fn create_radial_gradient_prim(
3103         &mut self,
3104         info: &LayoutPrimitiveInfo,
3105         center: LayoutPoint,
3106         start_radius: f32,
3107         end_radius: f32,
3108         ratio_xy: f32,
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 {
3119             start_radius,
3120             end_radius,
3121             ratio_xy,
3122         };
3124         RadialGradient {
3125             extend_mode,
3126             center: center.into(),
3127             params,
3128             stretch_size: stretch_size.into(),
3129             tile_spacing: tile_spacing.into(),
3130             nine_patch,
3131             stops,
3132         }
3133     }
3135     pub fn create_conic_gradient_prim(
3136         &mut self,
3137         info: &LayoutPrimitiveInfo,
3138         center: LayoutPoint,
3139         angle: f32,
3140         start_offset: f32,
3141         end_offset: f32,
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| {
3152             GradientStopKey {
3153                 offset: stop.offset,
3154                 color: stop.color.into(),
3155             }
3156         }).collect();
3158         ConicGradient {
3159             extend_mode,
3160             center: center.into(),
3161             params: ConicGradientParams { angle, start_offset, end_offset },
3162             stretch_size: stretch_size.into(),
3163             tile_spacing: tile_spacing.into(),
3164             nine_patch,
3165             stops,
3166         }
3167     }
3169     pub fn add_text(
3170         &mut self,
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>,
3178     ) {
3179         let offset = self.current_offset(spatial_node_index);
3181         let text_run = {
3182             let instance_map = self.font_instances.lock().unwrap();
3183             let font_instance = match instance_map.get(font_instance_key) {
3184                 Some(instance) => instance,
3185                 None => {
3186                     warn!("Unknown font instance key");
3187                     debug!("key={:?}", font_instance_key);
3188                     return;
3189                 }
3190             };
3192             // Trivial early out checks
3193             if font_instance.size <= FontSize::zero() {
3194                 return;
3195             }
3197             // TODO(gw): Use a proper algorithm to select
3198             // whether this item should be rendered with
3199             // subpixel AA!
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;
3207             }
3209             let font = FontInstance::new(
3210                 Arc::clone(font_instance),
3211                 (*text_color).into(),
3212                 render_mode,
3213                 flags,
3214             );
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
3222                 .iter()
3223                 .map(|glyph| {
3224                     GlyphInstance {
3225                         index: glyph.index,
3226                         point: glyph.point - prim_offset,
3227                     }
3228                 })
3229                 .collect();
3231             // Query the current requested raster space (stack handled by push/pop
3232             // stacking context).
3233             let requested_raster_space = self.raster_space_stack
3234                 .last()
3235                 .cloned()
3236                 .unwrap();
3238             TextRun {
3239                 glyphs: Arc::new(glyphs),
3240                 font,
3241                 shadow: false,
3242                 requested_raster_space,
3243             }
3244         };
3246         self.add_primitive(
3247             spatial_node_index,
3248             clip_chain_id,
3249             prim_info,
3250             Vec::new(),
3251             text_run,
3252         );
3253     }
3255     pub fn add_image(
3256         &mut self,
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,
3265         color: ColorF,
3266     ) {
3267         let mut prim_rect = info.rect;
3268         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
3269         let info = LayoutPrimitiveInfo {
3270             rect: prim_rect,
3271             .. *info
3272         };
3274         self.add_primitive(
3275             spatial_node_index,
3276             clip_chain_id,
3277             &info,
3278             Vec::new(),
3279             Image {
3280                 key: image_key,
3281                 tile_spacing: tile_spacing.into(),
3282                 stretch_size: stretch_size.into(),
3283                 color: color.into(),
3284                 image_rendering,
3285                 alpha_type,
3286             },
3287         );
3288     }
3290     pub fn add_yuv_image(
3291         &mut self,
3292         spatial_node_index: SpatialNodeIndex,
3293         clip_chain_id: ClipChainId,
3294         info: &LayoutPrimitiveInfo,
3295         yuv_data: YuvData,
3296         color_depth: ColorDepth,
3297         color_space: YuvColorSpace,
3298         color_range: ColorRange,
3299         image_rendering: ImageRendering,
3300     ) {
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],
3306         };
3308         self.add_nonshadowable_primitive(
3309             spatial_node_index,
3310             clip_chain_id,
3311             info,
3312             Vec::new(),
3313             YuvImage {
3314                 color_depth,
3315                 yuv_key,
3316                 format,
3317                 color_space,
3318                 color_range,
3319                 image_rendering,
3320             },
3321         );
3322     }
3324     fn add_primitive_instance_to_3d_root(
3325         &mut self,
3326         prim: ExtendedPrimitiveInstance,
3327     ) {
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), .. } => {
3332                     prims.push(prim);
3333                     break;
3334                 }
3335                 Picture3DContext::In { .. } => {}
3336                 Picture3DContext::Out => panic!("Unable to find 3D root"),
3337             }
3338         }
3339     }
3341     pub fn add_backdrop_filter(
3342         &mut self,
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>,
3349     ) {
3350         let mut backdrop_pic_index = match self.cut_backdrop_picture() {
3351             // Backdrop contains no content, so no need to add backdrop-filter
3352             None => return,
3353             Some(backdrop_pic_index) => backdrop_pic_index,
3354         };
3356         let backdrop_spatial_node_index = self.prim_store.pictures[backdrop_pic_index.0].spatial_node_index;
3358         let mut instance = self.create_primitive(
3359             info,
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,
3365             clip_chain_id,
3366             Backdrop {
3367                 pic_index: backdrop_pic_index,
3368                 spatial_node_index,
3369                 border_rect: info.rect.into(),
3370             },
3371         );
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();
3383             prim_list.add_prim(
3384                 instance,
3385                 LayoutRect::zero(),
3386                 backdrop_spatial_node_index,
3387                 prim_flags,
3388             );
3390             backdrop_pic_index = PictureIndex(self.prim_store.pictures
3391                 .alloc()
3392                 .init(PicturePrimitive::new_image(
3393                     composite_mode.clone(),
3394                     Picture3DContext::Out,
3395                     true,
3396                     prim_flags,
3397                     prim_list,
3398                     backdrop_spatial_node_index,
3399                     PictureOptions {
3400                        inflate_if_required: false,
3401                     },
3402                 ))
3403             );
3405             instance = create_prim_instance(
3406                 backdrop_pic_index,
3407                 composite_mode.into(),
3408                 clip_chain_id,
3409                 &mut self.interners,
3410             );
3411         }
3413         let mut source = PictureChainBuilder::from_instance(
3414             instance,
3415             info.flags,
3416             backdrop_spatial_node_index,
3417         );
3419         source = self.wrap_prim_with_filters(
3420             source,
3421             filters,
3422             filter_primitives,
3423             filter_datas,
3424             false,
3425         );
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(
3439                 source,
3440                 filters,
3441                 filter_primitives,
3442                 filter_datas,
3443                 false,
3444             );
3445         }
3447         let filtered_instance = source.finalize(
3448             clip_chain_id,
3449             &mut self.interners,
3450             &mut self.prim_store,
3451         );
3453         self.sc_stack
3454             .iter_mut()
3455             .rev()
3456             .find(|sc| sc.is_backdrop_root)
3457             .unwrap()
3458             .prim_list
3459             .add_prim(
3460                 filtered_instance,
3461                 LayoutRect::zero(),
3462                 backdrop_spatial_node_index,
3463                 info.flags,
3464             );
3465     }
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(
3479                     flattened_instance,
3480                     LayoutRect::zero(),
3481                     spatial_node_index,
3482                     prim_flags,
3483                 );
3484             }
3485             flattened_items = sc.cut_item_sequence(
3486                 &mut self.prim_store,
3487                 &mut self.interners,
3488                 None,
3489                 Picture3DContext::Out,
3490             );
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);
3495                 break;
3496             }
3497         }
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")
3502             .prim_list
3503             .add_prim(
3504                 instance,
3505                 LayoutRect::zero(),
3506                 spatial_node_index,
3507                 prim_flags,
3508             );
3510         Some(pic_index)
3511     }
3513     #[must_use]
3514     fn wrap_prim_with_filters(
3515         &mut self,
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 => {
3533                     let filter_data =
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() {
3538                         continue
3539                     } else {
3540                         let filter_data_key = SFilterDataKey {
3541                             data:
3542                                 SFilterData {
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),
3551                                 },
3552                         };
3554                         let handle = self.interners
3555                             .filter_data
3556                             .intern(&filter_data_key, || ());
3557                         PictureCompositeMode::ComponentTransferFilter(handle)
3558                     }
3559                 }
3560                 _ => {
3561                     if filter.is_noop() {
3562                         continue;
3563                     } else {
3564                         PictureCompositeMode::Filter(filter.clone())
3565                     }
3566                 }
3567             };
3569             source = source.add_picture(
3570                 composite_mode,
3571                 Picture3DContext::Out,
3572                 PictureOptions { inflate_if_required },
3573                 &mut self.interners,
3574                 &mut self.prim_store,
3575             );
3576         }
3578         if !filter_primitives.is_empty() {
3579             let filter_datas = filter_datas.iter()
3580                 .map(|filter_data| filter_data.sanitize())
3581                 .map(|filter_data| {
3582                     SFilterData {
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),
3591                     }
3592                 })
3593                 .collect();
3595             // Sanitize filter inputs
3596             for primitive in &mut filter_primitives {
3597                 primitive.sanitize();
3598             }
3600             let composite_mode = PictureCompositeMode::SvgFilter(
3601                 filter_primitives,
3602                 filter_datas,
3603             );
3605             source = source.add_picture(
3606                 composite_mode,
3607                 Picture3DContext::Out,
3608                 PictureOptions { inflate_if_required },
3609                 &mut self.interners,
3610                 &mut self.prim_store,
3611             );
3612         }
3614         source
3615     }
3619 pub trait CreateShadow {
3620     fn create_shadow(
3621         &self,
3622         shadow: &Shadow,
3623         blur_is_noop: bool,
3624         current_raster_space: RasterSpace,
3625     ) -> Self;
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)
3688     is_redundant: bool,
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()
3698     }
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,
3708     ) -> bool {
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) {
3711             return false;
3712         }
3714         // Any 3d context is required
3715         if let Picture3DContext::In { .. } = context_3d {
3716             return false;
3717         }
3719         // If any filters are present that affect the output
3720         if composite_ops.has_valid_filters() {
3721             return false;
3722         }
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() {
3729                     return false;
3730                 }
3731             }
3732         }
3734         // If backface visibility is explicitly set.
3735         if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
3736             return false;
3737         }
3739         // If need to isolate in surface due to clipping / mix-blend-mode
3740         if !blit_reason.is_empty() {
3741             return false;
3742         }
3744         // It is redundant!
3745         true
3746     }
3748     /// Cut the sequence of the immediate children recorded so far and generate a picture from them.
3749     pub fn cut_item_sequence(
3750         &mut self,
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() {
3757             return None
3758         }
3760         let pic_index = PictureIndex(prim_store.pictures
3761             .alloc()
3762             .init(PicturePrimitive::new_image(
3763                 composite_mode.clone(),
3764                 flat_items_context_3d,
3765                 true,
3766                 self.prim_flags,
3767                 mem::replace(&mut self.prim_list, PrimitiveList::empty()),
3768                 self.spatial_node_index,
3769                 PictureOptions::default(),
3770             ))
3771         );
3773         let prim_instance = create_prim_instance(
3774             pic_index,
3775             composite_mode.into(),
3776             self.clip_chain_id,
3777             interners,
3778         );
3780         Some((pic_index, prim_instance))
3781     }
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,
3791     prim: T,
3794 /// As shadows are pushed, they are stored as pending
3795 /// shadows, and handled at once during pop_all_shadows.
3796 pub struct PendingShadow {
3797     shadow: Shadow,
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)
3815     }
3818 impl From<PendingPrimitive<LineDecoration>> for ShadowItem {
3819     fn from(line_dec: PendingPrimitive<LineDecoration>) -> Self {
3820         ShadowItem::LineDecoration(line_dec)
3821     }
3824 impl From<PendingPrimitive<NormalBorderPrim>> for ShadowItem {
3825     fn from(border: PendingPrimitive<NormalBorderPrim>) -> Self {
3826         ShadowItem::NormalBorder(border)
3827     }
3830 impl From<PendingPrimitive<PrimitiveKeyKind>> for ShadowItem {
3831     fn from(container: PendingPrimitive<PrimitiveKeyKind>) -> Self {
3832         ShadowItem::Primitive(container)
3833     }
3836 impl From<PendingPrimitive<TextRun>> for ShadowItem {
3837     fn from(text_run: PendingPrimitive<TextRun>) -> Self {
3838         ShadowItem::TextRun(text_run)
3839     }
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 },
3850     );
3852     let data_handle = interners
3853         .picture
3854         .intern(&pic_key, || ());
3856     PrimitiveInstance::new(
3857         LayoutRect::max_rect(),
3858         PrimitiveInstanceKind::Picture {
3859             data_handle,
3860             pic_index,
3861             segment_instance_index: SegmentInstanceIndex::INVALID,
3862         },
3863         clip_chain_id,
3864     )
3867 fn filter_ops_for_compositing(
3868     input_filters: ItemRange<FilterOp>,
3869 ) -> Vec<Filter> {
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(),
3895         });
3896     }
3897     filter_datas
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,
3914 ) -> 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;
3923     LayoutSize::new(
3924         if repeat_size.width.approx_eq_eps(&unsnapped_rect.size.width, &EPSILON) {
3925             snapped_rect.size.width
3926         } else {
3927             repeat_size.width
3928         },
3929         if repeat_size.height.approx_eq_eps(&unsnapped_rect.size.height, &EPSILON) {
3930             snapped_rect.size.height
3931         } else {
3932             repeat_size.height
3933         },
3934     )
3937 fn read_gradient_stops(stops: ItemRange<GradientStop>) -> Vec<GradientStopKey> {
3938     stops.iter().map(|stop| {
3939         GradientStopKey {
3940             offset: stop.offset,
3941             color: stop.color.into(),
3942         }
3943     }).collect()