scene_building.rs (198957B)
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/. */ 4 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 //! 37 38 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, BuiltDisplayListIter, PrimitiveFlags, SnapshotInfo}; 39 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace}; 40 use api::{DebugFlags, DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData}; 41 use api::{FilterOp, 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, ScrollFrameDescriptor}; 45 use api::{APZScrollGeneration, HasScrollLinkedEffect, Shadow, SpatialId, StickyFrameDescriptor, ImageMask, ItemTag}; 46 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData}; 47 use api::{ReferenceTransformBinding, Rotation, FillRule, SpatialTreeItem, ReferenceFrameDescriptor}; 48 use api::{FilterOpGraphPictureBufferId, SVGFE_GRAPH_MAX}; 49 use api::channel::{unbounded_channel, Receiver, Sender}; 50 use api::units::*; 51 use crate::image_tiling::simplify_repeated_primitive; 52 use crate::box_shadow::BLUR_SAMPLE_SCALE; 53 use crate::clip::{ClipIntern, ClipItemKey, ClipItemKeyKind, ClipStore}; 54 use crate::clip::{ClipInternData, ClipNodeId, ClipLeafId}; 55 use crate::clip::{PolygonDataHandle, ClipTreeBuilder}; 56 use crate::gpu_types::BlurEdgeMode; 57 use crate::segment::EdgeAaSegmentMask; 58 use crate::spatial_tree::{SceneSpatialTree, SpatialNodeContainer, SpatialNodeIndex, get_external_scroll_offset}; 59 use crate::frame_builder::FrameBuilderConfig; 60 use glyph_rasterizer::{FontInstance, SharedFontResources}; 61 use crate::hit_test::HitTestingScene; 62 use crate::intern::Interner; 63 use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter, PlaneSplitterIndex, PipelineInstanceId}; 64 use crate::svg_filter::{FilterGraphNode, FilterGraphOp, FilterGraphPictureReference}; 65 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive}; 66 use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, SurfaceInfo, PictureFlags}; 67 use crate::picture_graph::PictureGraph; 68 use crate::prim_store::{PrimitiveInstance, PrimitiveStoreStats}; 69 use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore}; 70 use crate::prim_store::{InternablePrimitive, PictureIndex}; 71 use crate::prim_store::PolygonKey; 72 use crate::prim_store::backdrop::{BackdropCapture, BackdropRender}; 73 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; 74 use crate::prim_store::gradient::{ 75 GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, 76 ConicGradientParams, optimize_radial_gradient, apply_gradient_local_clip, 77 optimize_linear_gradient, self, 78 }; 79 use crate::prim_store::image::{Image, YuvImage}; 80 use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey, get_line_decoration_size}; 81 use crate::prim_store::picture::{Picture, PictureKey}; 82 use crate::picture_composite_mode::PictureCompositeKey; 83 use crate::prim_store::text_run::TextRun; 84 use crate::render_backend::SceneView; 85 use crate::resource_cache::ImageRequest; 86 use crate::scene::{BuiltScene, Scene, ScenePipeline, SceneStats, StackingContextHelpers}; 87 use crate::scene_builder_thread::Interners; 88 use crate::space::SpaceSnapper; 89 use crate::spatial_node::{ 90 ReferenceFrameInfo, StickyFrameInfo, ScrollFrameKind, SpatialNodeUid, SpatialNodeType 91 }; 92 use crate::tile_cache::TileCacheBuilder; 93 use euclid::approxeq::ApproxEq; 94 use std::{f32, mem, usize}; 95 use std::collections::vec_deque::VecDeque; 96 use std::sync::Arc; 97 use crate::util::{VecHelper, MaxRect}; 98 use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey}; 99 use log::Level; 100 101 /// Offsets primitives (and clips) by the external scroll offset 102 /// supplied to scroll nodes. 103 pub struct ScrollOffsetMapper { 104 pub current_spatial_node: SpatialNodeIndex, 105 pub current_offset: LayoutVector2D, 106 } 107 108 impl ScrollOffsetMapper { 109 fn new() -> Self { 110 ScrollOffsetMapper { 111 current_spatial_node: SpatialNodeIndex::INVALID, 112 current_offset: LayoutVector2D::zero(), 113 } 114 } 115 116 /// Return the accumulated external scroll offset for a spatial 117 /// node. This caches the last result, which is the common case, 118 /// or defers to the spatial tree to build the value. 119 fn external_scroll_offset( 120 &mut self, 121 spatial_node_index: SpatialNodeIndex, 122 spatial_tree: &SceneSpatialTree, 123 ) -> LayoutVector2D { 124 if spatial_node_index != self.current_spatial_node { 125 self.current_spatial_node = spatial_node_index; 126 self.current_offset = get_external_scroll_offset(spatial_tree, spatial_node_index); 127 } 128 129 self.current_offset 130 } 131 } 132 133 /// A data structure that keeps track of mapping between API Ids for spatials and the indices 134 /// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives 135 /// and clips during frame building. 136 #[derive(Default)] 137 pub struct NodeIdToIndexMapper { 138 spatial_node_map: FastHashMap<SpatialId, SpatialNodeIndex>, 139 } 140 141 impl NodeIdToIndexMapper { 142 fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) { 143 let _old_value = self.spatial_node_map.insert(id, index); 144 assert!(_old_value.is_none()); 145 } 146 147 fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex { 148 self.spatial_node_map[&id] 149 } 150 } 151 152 #[derive(Debug, Clone, Default)] 153 pub struct CompositeOps { 154 // Requires only a single texture as input (e.g. most filters) 155 pub filters: Vec<Filter>, 156 pub filter_datas: Vec<FilterData>, 157 pub snapshot: Option<SnapshotInfo>, 158 159 // Requires two source textures (e.g. mix-blend-mode) 160 pub mix_blend_mode: Option<MixBlendMode>, 161 } 162 163 impl CompositeOps { 164 pub fn new( 165 filters: Vec<Filter>, 166 filter_datas: Vec<FilterData>, 167 mix_blend_mode: Option<MixBlendMode>, 168 snapshot: Option<SnapshotInfo>, 169 ) -> Self { 170 CompositeOps { 171 filters, 172 filter_datas, 173 mix_blend_mode, 174 snapshot, 175 } 176 } 177 178 pub fn is_empty(&self) -> bool { 179 self.filters.is_empty() && 180 self.mix_blend_mode.is_none() && 181 self.snapshot.is_none() 182 } 183 184 /// Returns true if this CompositeOps contains any filters that affect 185 /// the content (false if no filters, or filters are all no-ops). 186 fn has_valid_filters(&self) -> bool { 187 // For each filter, create a new image with that composite mode. 188 let mut current_filter_data_index = 0; 189 for filter in &self.filters { 190 match filter { 191 Filter::ComponentTransfer => { 192 let filter_data = 193 &self.filter_datas[current_filter_data_index]; 194 let filter_data = filter_data.sanitize(); 195 current_filter_data_index = current_filter_data_index + 1; 196 if filter_data.is_identity() { 197 continue 198 } else { 199 return true; 200 } 201 } 202 Filter::SVGGraphNode(..) => {return true;} 203 _ => { 204 if filter.is_noop() { 205 continue; 206 } else { 207 return true; 208 } 209 } 210 } 211 } 212 213 false 214 } 215 } 216 217 /// Represents the current input for a picture chain builder (either a 218 /// prim list from the stacking context, or a wrapped picture instance). 219 enum PictureSource { 220 PrimitiveList { 221 prim_list: PrimitiveList, 222 }, 223 WrappedPicture { 224 instance: PrimitiveInstance, 225 }, 226 } 227 228 /// Helper struct to build picture chains during scene building from 229 /// a flattened stacking context struct. 230 struct PictureChainBuilder { 231 /// The current input source for the next picture 232 current: PictureSource, 233 234 /// Positioning node for this picture chain 235 spatial_node_index: SpatialNodeIndex, 236 /// Prim flags for any pictures in this chain 237 flags: PrimitiveFlags, 238 /// Requested raster space for enclosing stacking context 239 raster_space: RasterSpace, 240 /// If true, set first picture as a resolve target 241 set_resolve_target: bool, 242 /// If true, mark the last picture as a sub-graph 243 establishes_sub_graph: bool, 244 } 245 246 impl PictureChainBuilder { 247 /// Create a new picture chain builder, from a primitive list 248 fn from_prim_list( 249 prim_list: PrimitiveList, 250 flags: PrimitiveFlags, 251 spatial_node_index: SpatialNodeIndex, 252 raster_space: RasterSpace, 253 is_sub_graph: bool, 254 ) -> Self { 255 PictureChainBuilder { 256 current: PictureSource::PrimitiveList { 257 prim_list, 258 }, 259 spatial_node_index, 260 flags, 261 raster_space, 262 establishes_sub_graph: is_sub_graph, 263 set_resolve_target: is_sub_graph, 264 } 265 } 266 267 /// Create a new picture chain builder, from a picture wrapper instance 268 fn from_instance( 269 instance: PrimitiveInstance, 270 flags: PrimitiveFlags, 271 spatial_node_index: SpatialNodeIndex, 272 raster_space: RasterSpace, 273 ) -> Self { 274 PictureChainBuilder { 275 current: PictureSource::WrappedPicture { 276 instance, 277 }, 278 flags, 279 spatial_node_index, 280 raster_space, 281 establishes_sub_graph: false, 282 set_resolve_target: false, 283 } 284 } 285 286 /// Wrap the existing content with a new picture with the given parameters 287 #[must_use] 288 fn add_picture( 289 self, 290 composite_mode: PictureCompositeMode, 291 clip_node_id: ClipNodeId, 292 context_3d: Picture3DContext<OrderedPictureChild>, 293 interners: &mut Interners, 294 prim_store: &mut PrimitiveStore, 295 prim_instances: &mut Vec<PrimitiveInstance>, 296 clip_tree_builder: &mut ClipTreeBuilder, 297 ) -> PictureChainBuilder { 298 let prim_list = match self.current { 299 PictureSource::PrimitiveList { prim_list } => { 300 prim_list 301 } 302 PictureSource::WrappedPicture { instance } => { 303 let mut prim_list = PrimitiveList::empty(); 304 305 prim_list.add_prim( 306 instance, 307 LayoutRect::zero(), 308 self.spatial_node_index, 309 self.flags, 310 prim_instances, 311 clip_tree_builder, 312 ); 313 314 prim_list 315 } 316 }; 317 318 let flags = if self.set_resolve_target { 319 PictureFlags::IS_RESOLVE_TARGET 320 } else { 321 PictureFlags::empty() 322 }; 323 324 let pic_index = PictureIndex(prim_store.pictures 325 .alloc() 326 .init(PicturePrimitive::new_image( 327 Some(composite_mode.clone()), 328 context_3d, 329 self.flags, 330 prim_list, 331 self.spatial_node_index, 332 self.raster_space, 333 flags, 334 None, 335 )) 336 ); 337 338 let instance = create_prim_instance( 339 pic_index, 340 Some(composite_mode).into(), 341 self.raster_space, 342 clip_node_id, 343 interners, 344 clip_tree_builder, 345 ); 346 347 PictureChainBuilder { 348 current: PictureSource::WrappedPicture { 349 instance, 350 }, 351 spatial_node_index: self.spatial_node_index, 352 flags: self.flags, 353 raster_space: self.raster_space, 354 // We are now on a subsequent picture, so set_resolve_target has been handled 355 set_resolve_target: false, 356 establishes_sub_graph: self.establishes_sub_graph, 357 } 358 } 359 360 /// Finish building this picture chain. Set the clip chain on the outermost picture 361 fn finalize( 362 self, 363 clip_node_id: ClipNodeId, 364 interners: &mut Interners, 365 prim_store: &mut PrimitiveStore, 366 clip_tree_builder: &mut ClipTreeBuilder, 367 snapshot: Option<SnapshotInfo>, 368 ) -> PrimitiveInstance { 369 let mut flags = PictureFlags::empty(); 370 if self.establishes_sub_graph { 371 flags |= PictureFlags::IS_SUB_GRAPH; 372 } 373 374 match self.current { 375 PictureSource::WrappedPicture { instance } => { 376 let pic_index = instance.kind.as_pic(); 377 let picture = &mut prim_store.pictures[pic_index.0]; 378 picture.flags |= flags; 379 picture.snapshot = snapshot; 380 381 instance 382 } 383 PictureSource::PrimitiveList { prim_list } => { 384 if self.set_resolve_target { 385 flags |= PictureFlags::IS_RESOLVE_TARGET; 386 } 387 388 // If no picture was created for this stacking context, create a 389 // pass-through wrapper now. This is only needed in 1-2 edge cases 390 // now, and will be removed as a follow up. 391 392 // If the picture is snapshotted, it needs to have a surface rather 393 // than being pass-through. 394 let composite_mode = snapshot.map(|_| PictureCompositeMode::Blit(BlitReason::SNAPSHOT)); 395 396 let pic_index = PictureIndex(prim_store.pictures 397 .alloc() 398 .init(PicturePrimitive::new_image( 399 composite_mode, 400 Picture3DContext::Out, 401 self.flags, 402 prim_list, 403 self.spatial_node_index, 404 self.raster_space, 405 flags, 406 snapshot, 407 )) 408 ); 409 410 create_prim_instance( 411 pic_index, 412 None.into(), 413 self.raster_space, 414 clip_node_id, 415 interners, 416 clip_tree_builder, 417 ) 418 } 419 } 420 } 421 422 /// Returns true if this builder wraps a picture 423 #[allow(dead_code)] 424 fn has_picture(&self) -> bool { 425 match self.current { 426 PictureSource::WrappedPicture { .. } => true, 427 PictureSource::PrimitiveList { .. } => false, 428 } 429 } 430 } 431 432 bitflags! { 433 /// Slice flags 434 #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 435 pub struct SliceFlags : u8 { 436 /// Slice created by a prim that has PrimitiveFlags::IS_SCROLLBAR_CONTAINER 437 const IS_SCROLLBAR = 1; 438 /// Represents an atomic container (can't split out compositor surfaces in this slice) 439 const IS_ATOMIC = 2; 440 } 441 } 442 443 /// A structure that converts a serialized display list into a form that WebRender 444 /// can use to later build a frame. This structure produces a BuiltScene. Public 445 /// members are typically those that are destructured into the BuiltScene. 446 pub struct SceneBuilder<'a> { 447 /// The scene that we are currently building. 448 scene: &'a Scene, 449 450 /// The map of all font instances. 451 fonts: SharedFontResources, 452 453 /// The data structure that converts between ClipId/SpatialId and the various 454 /// index types that the SpatialTree uses. 455 id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>, 456 457 /// A stack of stacking context properties. 458 sc_stack: Vec<FlattenedStackingContext>, 459 460 /// Stack of spatial node indices forming containing block for 3d contexts 461 containing_block_stack: Vec<SpatialNodeIndex>, 462 463 /// Stack of requested raster spaces for stacking contexts 464 raster_space_stack: Vec<RasterSpace>, 465 466 /// Maintains state for any currently active shadows 467 pending_shadow_items: VecDeque<ShadowItem>, 468 469 /// The SpatialTree that we are currently building during building. 470 pub spatial_tree: &'a mut SceneSpatialTree, 471 472 /// The store of primitives. 473 pub prim_store: PrimitiveStore, 474 475 /// Information about all primitives involved in hit testing. 476 pub hit_testing_scene: HitTestingScene, 477 478 /// The store which holds all complex clipping information. 479 pub clip_store: ClipStore, 480 481 /// The configuration to use for the FrameBuilder. We consult this in 482 /// order to determine the default font. 483 pub config: FrameBuilderConfig, 484 485 /// Reference to the set of data that is interned across display lists. 486 pub interners: &'a mut Interners, 487 488 /// Helper struct to map spatial nodes to external scroll offsets. 489 external_scroll_mapper: ScrollOffsetMapper, 490 491 /// The current recursion depth of iframes encountered. Used to restrict picture 492 /// caching slices to only the top-level content frame. 493 iframe_size: Vec<LayoutSize>, 494 495 /// Clip-chain for root iframes applied to any tile caches created within this iframe 496 root_iframe_clip: Option<ClipId>, 497 498 /// The current quality / performance settings for this scene. 499 quality_settings: QualitySettings, 500 501 /// Maintains state about the list of tile caches being built for this scene. 502 tile_cache_builder: TileCacheBuilder, 503 504 /// A helper struct to snap local rects in device space. During frame 505 /// building we may establish new raster roots, however typically that is in 506 /// cases where we won't be applying snapping (e.g. has perspective), or in 507 /// edge cases (e.g. SVG filter) where we can accept slightly incorrect 508 /// behaviour in favour of getting the common case right. 509 snap_to_device: SpaceSnapper, 510 511 /// A DAG that represents dependencies between picture primitives. This builds 512 /// a set of passes to run various picture processing passes in during frame 513 /// building, in a way that pictures are processed before (or after) their 514 /// dependencies, without relying on recursion for those passes. 515 picture_graph: PictureGraph, 516 517 /// Keep track of snapshot pictures to ensure that they are rendered even if they 518 /// are off-screen and the visibility traversal does not reach them. 519 snapshot_pictures: Vec<PictureIndex>, 520 521 /// Keep track of allocated plane splitters for this scene. A plane 522 /// splitter is allocated whenever we encounter a new 3d rendering context. 523 /// They are stored outside the picture since it makes it easier for them 524 /// to be referenced by both the owning 3d rendering context and the child 525 /// pictures that contribute to the splitter. 526 /// During scene building "allocating" a splitter is just incrementing an index. 527 /// Splitter objects themselves are allocated and recycled in the frame builder. 528 next_plane_splitter_index: usize, 529 530 /// A list of all primitive instances in the scene. We store them as a single 531 /// array so that multiple different systems (e.g. tile-cache, visibility, property 532 /// animation bindings) can store index buffers to prim instances. 533 prim_instances: Vec<PrimitiveInstance>, 534 535 /// A map of pipeline ids encountered during scene build - used to create unique 536 /// pipeline instance ids as they are encountered. 537 pipeline_instance_ids: FastHashMap<PipelineId, u32>, 538 539 /// A list of surfaces (backing textures) that are relevant for this scene. 540 /// Every picture is assigned to a surface (either a new surface if the picture 541 /// has a composite mode, or the parent surface if it's a pass-through). 542 surfaces: Vec<SurfaceInfo>, 543 544 /// Used to build a ClipTree from the clip-chains, clips and state during scene building. 545 clip_tree_builder: ClipTreeBuilder, 546 547 /// Some primitives need to nest two stacking contexts instead of one 548 /// (see push_stacking_context). We keep track of the extra stacking context info 549 /// here and set a boolean on the inner stacking context info to remember to 550 /// pop from this stack (see StackingContextInfo::needs_extra_stacking_context) 551 extra_stacking_context_stack: Vec<StackingContextInfo>, 552 } 553 554 impl<'a> SceneBuilder<'a> { 555 pub fn build( 556 scene: &Scene, 557 root_pipeline: Option<PipelineId>, 558 fonts: SharedFontResources, 559 view: &SceneView, 560 frame_builder_config: &FrameBuilderConfig, 561 interners: &mut Interners, 562 spatial_tree: &mut SceneSpatialTree, 563 recycler: &mut SceneRecycler, 564 stats: &SceneStats, 565 debug_flags: DebugFlags, 566 ) -> BuiltScene { 567 profile_scope!("build_scene"); 568 569 // We checked that the root pipeline is available on the render backend. 570 let root_pipeline_id = root_pipeline.or(scene.root_pipeline_id).unwrap(); 571 let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap(); 572 let root_reference_frame_index = spatial_tree.root_reference_frame_index(); 573 574 // During scene building, we assume a 1:1 picture -> raster pixel scale 575 let snap_to_device = SpaceSnapper::new( 576 root_reference_frame_index, 577 RasterPixelScale::new(1.0), 578 ); 579 580 let mut builder = SceneBuilder { 581 scene, 582 spatial_tree, 583 fonts, 584 config: *frame_builder_config, 585 id_to_index_mapper_stack: mem::take(&mut recycler.id_to_index_mapper_stack), 586 hit_testing_scene: recycler.hit_testing_scene.take().unwrap_or_else(|| HitTestingScene::new(&stats.hit_test_stats)), 587 pending_shadow_items: mem::take(&mut recycler.pending_shadow_items), 588 sc_stack: mem::take(&mut recycler.sc_stack), 589 containing_block_stack: mem::take(&mut recycler.containing_block_stack), 590 raster_space_stack: mem::take(&mut recycler.raster_space_stack), 591 prim_store: mem::take(&mut recycler.prim_store), 592 clip_store: mem::take(&mut recycler.clip_store), 593 interners, 594 external_scroll_mapper: ScrollOffsetMapper::new(), 595 iframe_size: mem::take(&mut recycler.iframe_size), 596 root_iframe_clip: None, 597 quality_settings: view.quality_settings, 598 tile_cache_builder: TileCacheBuilder::new( 599 root_reference_frame_index, 600 frame_builder_config.background_color, 601 debug_flags, 602 ), 603 snap_to_device, 604 picture_graph: mem::take(&mut recycler.picture_graph), 605 // This vector is empty most of the time, don't bother with recycling it for now. 606 snapshot_pictures: Vec::new(), 607 next_plane_splitter_index: 0, 608 prim_instances: mem::take(&mut recycler.prim_instances), 609 pipeline_instance_ids: FastHashMap::default(), 610 surfaces: mem::take(&mut recycler.surfaces), 611 clip_tree_builder: recycler.clip_tree_builder.take().unwrap_or_else(|| ClipTreeBuilder::new()), 612 extra_stacking_context_stack: Vec::new(), 613 }; 614 615 // Reset 616 builder.hit_testing_scene.reset(); 617 builder.prim_store.reset(); 618 builder.clip_store.reset(); 619 builder.picture_graph.reset(); 620 builder.prim_instances.clear(); 621 builder.surfaces.clear(); 622 builder.sc_stack.clear(); 623 builder.containing_block_stack.clear(); 624 builder.id_to_index_mapper_stack.clear(); 625 builder.pending_shadow_items.clear(); 626 builder.iframe_size.clear(); 627 628 builder.raster_space_stack.clear(); 629 builder.raster_space_stack.push(RasterSpace::Screen); 630 631 builder.clip_tree_builder.begin(); 632 633 builder.build_all( 634 root_pipeline_id, 635 &root_pipeline, 636 ); 637 638 // Construct the picture cache primitive instance(s) from the tile cache builder 639 let (tile_cache_config, tile_cache_pictures) = builder.tile_cache_builder.build( 640 &builder.config, 641 &mut builder.prim_store, 642 &builder.spatial_tree, 643 &builder.prim_instances, 644 &mut builder.clip_tree_builder, 645 &builder.interners, 646 ); 647 648 for pic_index in &builder.snapshot_pictures { 649 builder.picture_graph.add_root(*pic_index); 650 } 651 652 // Add all the tile cache pictures as roots of the picture graph 653 for pic_index in &tile_cache_pictures { 654 builder.picture_graph.add_root(*pic_index); 655 SceneBuilder::finalize_picture( 656 *pic_index, 657 None, 658 &mut builder.prim_store.pictures, 659 None, 660 &builder.clip_tree_builder, 661 &builder.prim_instances, 662 &builder.interners.clip, 663 ); 664 } 665 666 let clip_tree = builder.clip_tree_builder.finalize(); 667 668 recycler.clip_tree_builder = Some(builder.clip_tree_builder); 669 recycler.sc_stack = builder.sc_stack; 670 recycler.id_to_index_mapper_stack = builder.id_to_index_mapper_stack; 671 recycler.containing_block_stack = builder.containing_block_stack; 672 recycler.raster_space_stack = builder.raster_space_stack; 673 recycler.pending_shadow_items = builder.pending_shadow_items; 674 recycler.iframe_size = builder.iframe_size; 675 676 BuiltScene { 677 has_root_pipeline: scene.has_root_pipeline(), 678 pipeline_epochs: scene.pipeline_epochs.clone(), 679 output_rect: view.device_rect.size().into(), 680 hit_testing_scene: Arc::new(builder.hit_testing_scene), 681 prim_store: builder.prim_store, 682 clip_store: builder.clip_store, 683 config: builder.config, 684 tile_cache_config, 685 snapshot_pictures: builder.snapshot_pictures, 686 tile_cache_pictures, 687 picture_graph: builder.picture_graph, 688 num_plane_splitters: builder.next_plane_splitter_index, 689 prim_instances: builder.prim_instances, 690 surfaces: builder.surfaces, 691 clip_tree, 692 recycler_tx: Some(recycler.tx.clone()), 693 } 694 } 695 696 /// Traverse the picture prim list and update any late-set spatial nodes. 697 /// Also, for each picture primitive, store the lowest-common-ancestor 698 /// of all of the contained primitives' clips. 699 // TODO(gw): This is somewhat hacky - it's unfortunate we need to do this, but it's 700 // because we can't determine the scroll root until we have checked all the 701 // primitives in the slice. Perhaps we could simplify this by doing some 702 // work earlier in the DL builder, so we know what scroll root will be picked? 703 fn finalize_picture( 704 pic_index: PictureIndex, 705 prim_index: Option<usize>, 706 pictures: &mut [PicturePrimitive], 707 parent_spatial_node_index: Option<SpatialNodeIndex>, 708 clip_tree_builder: &ClipTreeBuilder, 709 prim_instances: &[PrimitiveInstance], 710 clip_interner: &Interner<ClipIntern>, 711 ) { 712 // Extract the prim_list (borrow check) and select the spatial node to 713 // assign to unknown clusters 714 let (mut prim_list, spatial_node_index) = { 715 let pic = &mut pictures[pic_index.0]; 716 assert_ne!(pic.spatial_node_index, SpatialNodeIndex::UNKNOWN); 717 718 if pic.flags.contains(PictureFlags::IS_RESOLVE_TARGET) { 719 pic.flags |= PictureFlags::DISABLE_SNAPPING; 720 } 721 722 // If we're a surface, use that spatial node, otherwise the parent 723 let spatial_node_index = match pic.composite_mode { 724 Some(_) => pic.spatial_node_index, 725 None => parent_spatial_node_index.expect("bug: no parent"), 726 }; 727 728 ( 729 mem::replace(&mut pic.prim_list, PrimitiveList::empty()), 730 spatial_node_index, 731 ) 732 }; 733 734 // Update the spatial node of any unknown clusters 735 for cluster in &mut prim_list.clusters { 736 if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN { 737 cluster.spatial_node_index = spatial_node_index; 738 } 739 } 740 741 // Work out the lowest common clip which is shared by all the 742 // primitives in this picture. If it is the same as the picture clip 743 // then store it as the clip tree root for the picture so that it is 744 // applied later as part of picture compositing. Gecko gives every 745 // primitive a viewport clip which, if applied within the picture, 746 // will mess up tile caching and mean we have to redraw on every 747 // scroll event (for tile caching to work usefully we specifically 748 // want to draw things even if they are outside the viewport). 749 let mut shared_clip_node_id = None; 750 751 // Snapshot picture are special. All clips belonging to parents 752 // *must* be extracted from the snapshot, so we rely on this optimization 753 // taking out parent clips and it overrides other conditions. 754 // In addition we need to ensure that only parent clips are extracted. 755 let is_snapshot = pictures[pic_index.0].snapshot.is_some(); 756 757 if is_snapshot { 758 // In the general case, if all of the children of a picture share the 759 // same clips, then these clips are hoisted up in the parent picture, 760 // however we rely on child clips of snapshotted pictures to be baked 761 // into the snapshot. 762 // Snapshotted pictures use the parent of their clip node (if any) 763 // as the clip root, to ensure that the parent clip hierarchy is 764 // extracted from clip chains inside the snapshot, and to make sure 765 // that child clips of the snapshots are not hoisted out of the 766 // snapshot even when all children of the snapshotted picture share 767 // a clip. 768 if let Some(idx) = prim_index { 769 let clip_node = clip_tree_builder.get_leaf(prim_instances[idx].clip_leaf_id).node_id; 770 shared_clip_node_id = clip_tree_builder.get_parent(clip_node); 771 } 772 } else { 773 for cluster in &prim_list.clusters { 774 for prim_instance in &prim_instances[cluster.prim_range()] { 775 let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); 776 777 shared_clip_node_id = match shared_clip_node_id { 778 Some(current) => { 779 Some(clip_tree_builder.find_lowest_common_ancestor( 780 current, 781 leaf.node_id, 782 )) 783 } 784 None => Some(leaf.node_id) 785 }; 786 } 787 } 788 } 789 790 let lca_tree_node = shared_clip_node_id 791 .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id)) 792 .map(|node_id| clip_tree_builder.get_node(node_id)); 793 let lca_node = lca_tree_node 794 .map(|tree_node| &clip_interner[tree_node.handle]); 795 let pic_node_id = prim_index 796 .map(|prim_index| clip_tree_builder.get_leaf(prim_instances[prim_index].clip_leaf_id).node_id) 797 .and_then(|node_id| (node_id != ClipNodeId::NONE).then_some(node_id)); 798 let pic_node = pic_node_id 799 .map(|node_id| clip_tree_builder.get_node(node_id)) 800 .map(|tree_node| &clip_interner[tree_node.handle]); 801 802 // The logic behind this optimisation is that there's no need to clip 803 // the contents of a picture when the crop will be applied anyway as 804 // part of compositing the picture. However, this is not true if the 805 // picture includes a blur filter as the blur result depends on the 806 // offscreen pixels which may or may not be cropped away. 807 let has_blur = match &pictures[pic_index.0].composite_mode { 808 Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true, 809 Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true, 810 Some(PictureCompositeMode::SVGFEGraph( .. )) => true, 811 _ => false, 812 }; 813 814 // It is only safe to apply this optimisation if the old pic clip node 815 // is the direct parent of the new LCA node. If this is not the case 816 // then there could be other more restrictive clips in between the two 817 // which we would ignore by changing the clip root. See Bug 1854062 818 // for an example of this. 819 let direct_parent = lca_tree_node 820 .zip(pic_node_id) 821 .map(|(lca_tree_node, pic_node_id)| lca_tree_node.parent == pic_node_id) 822 .unwrap_or(false); 823 824 let should_set_clip_root = is_snapshot || lca_node.zip(pic_node).map_or(false, |(lca_node, pic_node)| { 825 // It is only safe to ignore the LCA clip (by making it the clip 826 // root) if it is equal to or larger than the picture clip. But 827 // this comparison also needs to take into account spatial nodes 828 // as the two clips may in general be on different spatial nodes. 829 // For this specific Gecko optimisation we expect the the two 830 // clips to be identical and have the same spatial node so it's 831 // simplest to just test for ClipItemKey equality (which includes 832 // both spatial node and the actual clip). 833 lca_node.key == pic_node.key && !has_blur && direct_parent 834 }); 835 836 if should_set_clip_root { 837 pictures[pic_index.0].clip_root = shared_clip_node_id; 838 } 839 840 // Update the spatial node of any child pictures 841 for cluster in &prim_list.clusters { 842 for prim_instance_index in cluster.prim_range() { 843 if let PrimitiveInstanceKind::Picture { pic_index: child_pic_index, .. } = prim_instances[prim_instance_index].kind { 844 let child_pic = &mut pictures[child_pic_index.0]; 845 846 if child_pic.spatial_node_index == SpatialNodeIndex::UNKNOWN { 847 child_pic.spatial_node_index = spatial_node_index; 848 } 849 850 // Recurse into child pictures which may also have unknown spatial nodes 851 SceneBuilder::finalize_picture( 852 child_pic_index, 853 Some(prim_instance_index), 854 pictures, 855 Some(spatial_node_index), 856 clip_tree_builder, 857 prim_instances, 858 clip_interner, 859 ); 860 861 if pictures[child_pic_index.0].flags.contains(PictureFlags::DISABLE_SNAPPING) { 862 pictures[pic_index.0].flags |= PictureFlags::DISABLE_SNAPPING; 863 } 864 } 865 } 866 } 867 868 // Restore the prim_list 869 pictures[pic_index.0].prim_list = prim_list; 870 } 871 872 /// Retrieve the current external scroll offset on the provided spatial node. 873 fn current_external_scroll_offset( 874 &mut self, 875 spatial_node_index: SpatialNodeIndex, 876 ) -> LayoutVector2D { 877 // Get the external scroll offset, if applicable. 878 self.external_scroll_mapper 879 .external_scroll_offset( 880 spatial_node_index, 881 self.spatial_tree, 882 ) 883 } 884 885 fn build_spatial_tree_for_display_list( 886 &mut self, 887 dl: &BuiltDisplayList, 888 pipeline_id: PipelineId, 889 instance_id: PipelineInstanceId, 890 ) { 891 dl.iter_spatial_tree(|item| { 892 match item { 893 SpatialTreeItem::ScrollFrame(descriptor) => { 894 let parent_space = self.get_space(descriptor.parent_space); 895 self.build_scroll_frame( 896 descriptor, 897 parent_space, 898 pipeline_id, 899 instance_id, 900 ); 901 } 902 SpatialTreeItem::ReferenceFrame(descriptor) => { 903 let parent_space = self.get_space(descriptor.parent_spatial_id); 904 self.build_reference_frame( 905 descriptor, 906 parent_space, 907 pipeline_id, 908 instance_id, 909 ); 910 } 911 SpatialTreeItem::StickyFrame(descriptor) => { 912 let parent_space = self.get_space(descriptor.parent_spatial_id); 913 self.build_sticky_frame( 914 descriptor, 915 parent_space, 916 instance_id, 917 ); 918 } 919 SpatialTreeItem::Invalid => { 920 unreachable!(); 921 } 922 } 923 }); 924 } 925 926 fn build_all( 927 &mut self, 928 root_pipeline_id: PipelineId, 929 root_pipeline: &ScenePipeline, 930 ) { 931 enum ContextKind<'a> { 932 Root, 933 StackingContext { 934 sc_info: StackingContextInfo, 935 }, 936 ReferenceFrame, 937 Iframe { 938 parent_traversal: BuiltDisplayListIter<'a>, 939 } 940 } 941 struct BuildContext<'a> { 942 pipeline_id: PipelineId, 943 kind: ContextKind<'a>, 944 } 945 946 self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default()); 947 948 let instance_id = self.get_next_instance_id_for_pipeline(root_pipeline_id); 949 950 self.push_root( 951 root_pipeline_id, 952 instance_id, 953 ); 954 self.build_spatial_tree_for_display_list( 955 &root_pipeline.display_list.display_list, 956 root_pipeline_id, 957 instance_id, 958 ); 959 960 let mut stack = vec![BuildContext { 961 pipeline_id: root_pipeline_id, 962 kind: ContextKind::Root, 963 }]; 964 let mut traversal = root_pipeline.display_list.iter(); 965 966 'outer: while let Some(bc) = stack.pop() { 967 loop { 968 let item = match traversal.next() { 969 Some(item) => item, 970 None => break, 971 }; 972 973 match item.item() { 974 DisplayItem::PushStackingContext(ref info) => { 975 profile_scope!("build_stacking_context"); 976 let spatial_node_index = self.get_space(info.spatial_id); 977 let mut subtraversal = item.sub_iter(); 978 // Avoid doing unnecessary work for empty stacking contexts. 979 // We still have to process it if it has filters, they 980 // may be things like SVGFEFlood or various specific 981 // ways to use ComponentTransfer, ColorMatrix, Composite 982 // which are still visible on an empty stacking context 983 if subtraversal.current_stacking_context_empty() && item.filters().is_empty() { 984 subtraversal.skip_current_stacking_context(); 985 traversal = subtraversal; 986 continue; 987 } 988 989 let snapshot = info.snapshot.map(|snapshot| { 990 // Offset the snapshot area by the stacking context origin 991 // so that the area is expressed in the same coordinate space 992 // as the items in the stacking context. 993 SnapshotInfo { 994 area: snapshot.area.translate(info.origin.to_vector()), 995 .. snapshot 996 } 997 }); 998 999 let composition_operations = CompositeOps::new( 1000 filter_ops_for_compositing(item.filters()), 1001 filter_datas_for_compositing(item.filter_datas()), 1002 info.stacking_context.mix_blend_mode_for_compositing(), 1003 snapshot, 1004 ); 1005 1006 let sc_info = self.push_stacking_context( 1007 composition_operations, 1008 info.stacking_context.transform_style, 1009 info.prim_flags, 1010 spatial_node_index, 1011 info.stacking_context.clip_chain_id, 1012 info.stacking_context.raster_space, 1013 info.stacking_context.flags, 1014 info.ref_frame_offset + info.origin.to_vector(), 1015 ); 1016 1017 let new_context = BuildContext { 1018 pipeline_id: bc.pipeline_id, 1019 kind: ContextKind::StackingContext { 1020 sc_info, 1021 }, 1022 }; 1023 stack.push(bc); 1024 stack.push(new_context); 1025 1026 subtraversal.merge_debug_stats_from(&mut traversal); 1027 traversal = subtraversal; 1028 continue 'outer; 1029 } 1030 DisplayItem::PushReferenceFrame(..) => { 1031 profile_scope!("build_reference_frame"); 1032 let mut subtraversal = item.sub_iter(); 1033 1034 let new_context = BuildContext { 1035 pipeline_id: bc.pipeline_id, 1036 kind: ContextKind::ReferenceFrame, 1037 }; 1038 stack.push(bc); 1039 stack.push(new_context); 1040 1041 subtraversal.merge_debug_stats_from(&mut traversal); 1042 traversal = subtraversal; 1043 continue 'outer; 1044 } 1045 DisplayItem::PopReferenceFrame | 1046 DisplayItem::PopStackingContext => break, 1047 DisplayItem::Iframe(ref info) => { 1048 profile_scope!("iframe"); 1049 1050 let space = self.get_space(info.space_and_clip.spatial_id); 1051 let subtraversal = match self.push_iframe(info, space) { 1052 Some(pair) => pair, 1053 None => continue, 1054 }; 1055 1056 let new_context = BuildContext { 1057 pipeline_id: info.pipeline_id, 1058 kind: ContextKind::Iframe { 1059 parent_traversal: mem::replace(&mut traversal, subtraversal), 1060 }, 1061 }; 1062 stack.push(bc); 1063 stack.push(new_context); 1064 continue 'outer; 1065 } 1066 _ => { 1067 self.build_item(item); 1068 } 1069 }; 1070 } 1071 1072 match bc.kind { 1073 ContextKind::Root => {} 1074 ContextKind::StackingContext { sc_info } => { 1075 self.pop_stacking_context(sc_info); 1076 } 1077 ContextKind::ReferenceFrame => { 1078 } 1079 ContextKind::Iframe { parent_traversal } => { 1080 self.iframe_size.pop(); 1081 self.clip_tree_builder.pop_clip(); 1082 self.clip_tree_builder.pop_clip(); 1083 1084 if self.iframe_size.is_empty() { 1085 assert!(self.root_iframe_clip.is_some()); 1086 self.root_iframe_clip = None; 1087 self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); 1088 } 1089 1090 self.id_to_index_mapper_stack.pop().unwrap(); 1091 1092 traversal = parent_traversal; 1093 } 1094 } 1095 1096 // TODO: factor this out to be part of capture 1097 if cfg!(feature = "display_list_stats") { 1098 let stats = traversal.debug_stats(); 1099 let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum(); 1100 debug!("item, total count, total bytes, % of DL bytes, bytes per item"); 1101 for (label, stats) in stats { 1102 debug!("{}, {}, {}kb, {}%, {}", 1103 label, 1104 stats.total_count, 1105 stats.num_bytes / 1000, 1106 ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize, 1107 stats.num_bytes / stats.total_count.max(1)); 1108 } 1109 debug!(""); 1110 } 1111 } 1112 1113 debug_assert!(self.sc_stack.is_empty()); 1114 1115 self.id_to_index_mapper_stack.pop().unwrap(); 1116 assert!(self.id_to_index_mapper_stack.is_empty()); 1117 } 1118 1119 fn build_sticky_frame( 1120 &mut self, 1121 info: &StickyFrameDescriptor, 1122 parent_node_index: SpatialNodeIndex, 1123 instance_id: PipelineInstanceId, 1124 ) { 1125 let external_scroll_offset = self.current_external_scroll_offset(parent_node_index); 1126 1127 let sticky_frame_info = StickyFrameInfo::new( 1128 info.bounds.translate(external_scroll_offset), 1129 info.margins, 1130 info.vertical_offset_bounds, 1131 info.horizontal_offset_bounds, 1132 info.previously_applied_offset, 1133 info.transform, 1134 ); 1135 1136 let index = self.spatial_tree.add_sticky_frame( 1137 parent_node_index, 1138 sticky_frame_info, 1139 info.id.pipeline_id(), 1140 info.key, 1141 instance_id, 1142 ); 1143 self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(info.id, index); 1144 } 1145 1146 fn build_reference_frame( 1147 &mut self, 1148 info: &ReferenceFrameDescriptor, 1149 parent_space: SpatialNodeIndex, 1150 pipeline_id: PipelineId, 1151 instance_id: PipelineInstanceId, 1152 ) { 1153 let transform = match info.reference_frame.transform { 1154 ReferenceTransformBinding::Static { binding } => binding, 1155 ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => { 1156 let content_size = &self.iframe_size.last().unwrap(); 1157 1158 let mut transform = if let Some(scale_from) = scale_from { 1159 // If we have a 90/270 degree rotation, then scale_from 1160 // and content_size are in different coordinate spaces and 1161 // we need to swap width/height for them to be correct. 1162 match rotation { 1163 Rotation::Degree0 | 1164 Rotation::Degree180 => { 1165 LayoutTransform::scale( 1166 content_size.width / scale_from.width, 1167 content_size.height / scale_from.height, 1168 1.0 1169 ) 1170 }, 1171 Rotation::Degree90 | 1172 Rotation::Degree270 => { 1173 LayoutTransform::scale( 1174 content_size.height / scale_from.width, 1175 content_size.width / scale_from.height, 1176 1.0 1177 ) 1178 1179 } 1180 } 1181 } else { 1182 LayoutTransform::identity() 1183 }; 1184 1185 if vertical_flip { 1186 let content_size = &self.iframe_size.last().unwrap(); 1187 let content_height = match rotation { 1188 Rotation::Degree0 | Rotation::Degree180 => content_size.height, 1189 Rotation::Degree90 | Rotation::Degree270 => content_size.width, 1190 }; 1191 transform = transform 1192 .then_translate(LayoutVector3D::new(0.0, content_height, 0.0)) 1193 .pre_scale(1.0, -1.0, 1.0); 1194 } 1195 1196 let rotate = rotation.to_matrix(**content_size); 1197 let transform = transform.then(&rotate); 1198 1199 PropertyBinding::Value(transform) 1200 }, 1201 }; 1202 1203 let snap_origin = match info.reference_frame.kind { 1204 ReferenceFrameKind::Transform { should_snap, .. } => should_snap, 1205 ReferenceFrameKind::Perspective { .. } => false, 1206 }; 1207 1208 let origin = if snap_origin { 1209 info.origin.round() 1210 } else { 1211 info.origin 1212 }; 1213 1214 let external_scroll_offset = self.current_external_scroll_offset(parent_space); 1215 1216 self.push_reference_frame( 1217 info.reference_frame.id, 1218 parent_space, 1219 pipeline_id, 1220 info.reference_frame.transform_style, 1221 transform, 1222 info.reference_frame.kind, 1223 (origin + external_scroll_offset).to_vector(), 1224 SpatialNodeUid::external(info.reference_frame.key, pipeline_id, instance_id), 1225 ); 1226 } 1227 1228 fn build_scroll_frame( 1229 &mut self, 1230 info: &ScrollFrameDescriptor, 1231 parent_node_index: SpatialNodeIndex, 1232 pipeline_id: PipelineId, 1233 instance_id: PipelineInstanceId, 1234 ) { 1235 // This is useful when calculating scroll extents for the 1236 // SpatialNode::scroll(..) API as well as for properly setting sticky 1237 // positioning offsets. 1238 let content_size = info.content_rect.size(); 1239 let external_scroll_offset = self.current_external_scroll_offset(parent_node_index); 1240 1241 self.add_scroll_frame( 1242 info.scroll_frame_id, 1243 parent_node_index, 1244 info.external_id, 1245 pipeline_id, 1246 &info.frame_rect.translate(external_scroll_offset), 1247 &content_size, 1248 ScrollFrameKind::Explicit, 1249 info.external_scroll_offset, 1250 info.scroll_offset_generation, 1251 info.has_scroll_linked_effect, 1252 SpatialNodeUid::external(info.key, pipeline_id, instance_id), 1253 ); 1254 } 1255 1256 /// Advance and return the next instance id for a given pipeline id 1257 fn get_next_instance_id_for_pipeline( 1258 &mut self, 1259 pipeline_id: PipelineId, 1260 ) -> PipelineInstanceId { 1261 let next_instance = self.pipeline_instance_ids 1262 .entry(pipeline_id) 1263 .or_insert(0); 1264 1265 let instance_id = PipelineInstanceId::new(*next_instance); 1266 *next_instance += 1; 1267 1268 instance_id 1269 } 1270 1271 fn push_iframe( 1272 &mut self, 1273 info: &IframeDisplayItem, 1274 spatial_node_index: SpatialNodeIndex, 1275 ) -> Option<BuiltDisplayListIter<'a>> { 1276 let iframe_pipeline_id = info.pipeline_id; 1277 let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) { 1278 Some(pipeline) => pipeline, 1279 None => { 1280 debug_assert!(info.ignore_missing_pipeline); 1281 return None 1282 }, 1283 }; 1284 1285 self.clip_tree_builder.push_clip_chain(Some(info.space_and_clip.clip_chain_id), false, false); 1286 1287 // TODO(gw): This is the only remaining call site that relies on ClipId parenting, remove me! 1288 self.add_rect_clip_node( 1289 ClipId::root(iframe_pipeline_id), 1290 info.space_and_clip.spatial_id, 1291 &info.clip_rect, 1292 ); 1293 1294 self.clip_tree_builder.push_clip_id(ClipId::root(iframe_pipeline_id)); 1295 1296 let instance_id = self.get_next_instance_id_for_pipeline(iframe_pipeline_id); 1297 1298 self.id_to_index_mapper_stack.push(NodeIdToIndexMapper::default()); 1299 1300 let bounds = self.normalize_scroll_offset_and_snap_rect( 1301 &info.bounds, 1302 spatial_node_index, 1303 ); 1304 1305 let spatial_node_index = self.push_reference_frame( 1306 SpatialId::root_reference_frame(iframe_pipeline_id), 1307 spatial_node_index, 1308 iframe_pipeline_id, 1309 TransformStyle::Flat, 1310 PropertyBinding::Value(LayoutTransform::identity()), 1311 ReferenceFrameKind::Transform { 1312 is_2d_scale_translation: true, 1313 should_snap: true, 1314 paired_with_perspective: false, 1315 }, 1316 bounds.min.to_vector(), 1317 SpatialNodeUid::root_reference_frame(iframe_pipeline_id, instance_id), 1318 ); 1319 1320 let iframe_rect = LayoutRect::from_size(bounds.size()); 1321 let is_root_pipeline = self.iframe_size.is_empty(); 1322 1323 self.add_scroll_frame( 1324 SpatialId::root_scroll_node(iframe_pipeline_id), 1325 spatial_node_index, 1326 ExternalScrollId(0, iframe_pipeline_id), 1327 iframe_pipeline_id, 1328 &iframe_rect, 1329 &bounds.size(), 1330 ScrollFrameKind::PipelineRoot { 1331 is_root_pipeline, 1332 }, 1333 LayoutVector2D::zero(), 1334 APZScrollGeneration::default(), 1335 HasScrollLinkedEffect::No, 1336 SpatialNodeUid::root_scroll_frame(iframe_pipeline_id, instance_id), 1337 ); 1338 1339 // If this is a root iframe, force a new tile cache both before and after 1340 // adding primitives for this iframe. 1341 if self.iframe_size.is_empty() { 1342 assert!(self.root_iframe_clip.is_none()); 1343 self.root_iframe_clip = Some(ClipId::root(iframe_pipeline_id)); 1344 self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); 1345 } 1346 self.iframe_size.push(bounds.size()); 1347 1348 self.build_spatial_tree_for_display_list( 1349 &pipeline.display_list.display_list, 1350 iframe_pipeline_id, 1351 instance_id, 1352 ); 1353 1354 Some(pipeline.display_list.iter()) 1355 } 1356 1357 fn get_space( 1358 &self, 1359 spatial_id: SpatialId, 1360 ) -> SpatialNodeIndex { 1361 self.id_to_index_mapper_stack.last().unwrap().get_spatial_node_index(spatial_id) 1362 } 1363 1364 fn get_clip_node( 1365 &mut self, 1366 clip_chain_id: api::ClipChainId, 1367 ) -> ClipNodeId { 1368 self.clip_tree_builder.build_clip_set( 1369 clip_chain_id, 1370 ) 1371 } 1372 1373 fn process_common_properties( 1374 &mut self, 1375 common: &CommonItemProperties, 1376 bounds: Option<LayoutRect>, 1377 ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) { 1378 let spatial_node_index = self.get_space(common.spatial_id); 1379 1380 // If no bounds rect is given, default to clip rect. 1381 let mut clip_rect = common.clip_rect; 1382 let mut prim_rect = bounds.unwrap_or(clip_rect); 1383 let unsnapped_rect = self.normalize_rect_scroll_offset(&prim_rect, spatial_node_index); 1384 1385 // If antialiased, no need to snap but we still need to remove the 1386 // external scroll offset (it's applied later during frame building, 1387 // so that we don't intern to a different hash and invalidate content 1388 // in picture caches unnecessarily). 1389 if common.flags.contains(PrimitiveFlags::ANTIALISED) { 1390 prim_rect = self.normalize_rect_scroll_offset(&prim_rect, spatial_node_index); 1391 clip_rect = self.normalize_rect_scroll_offset(&clip_rect, spatial_node_index); 1392 } else { 1393 clip_rect = self.normalize_scroll_offset_and_snap_rect( 1394 &clip_rect, 1395 spatial_node_index, 1396 ); 1397 1398 prim_rect = self.normalize_scroll_offset_and_snap_rect( 1399 &prim_rect, 1400 spatial_node_index, 1401 ); 1402 } 1403 1404 let clip_node_id = self.get_clip_node( 1405 common.clip_chain_id, 1406 ); 1407 1408 let layout = LayoutPrimitiveInfo { 1409 rect: prim_rect, 1410 clip_rect, 1411 flags: common.flags, 1412 }; 1413 1414 (layout, unsnapped_rect, spatial_node_index, clip_node_id) 1415 } 1416 1417 fn process_common_properties_with_bounds( 1418 &mut self, 1419 common: &CommonItemProperties, 1420 bounds: LayoutRect, 1421 ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipNodeId) { 1422 self.process_common_properties( 1423 common, 1424 Some(bounds), 1425 ) 1426 } 1427 1428 // Remove the effect of the external scroll offset embedded in the display list 1429 // coordinates by Gecko. This ensures that we don't necessarily invalidate picture 1430 // cache tiles due to the embedded scroll offsets. 1431 fn normalize_rect_scroll_offset( 1432 &mut self, 1433 rect: &LayoutRect, 1434 spatial_node_index: SpatialNodeIndex, 1435 ) -> LayoutRect { 1436 let current_offset = self.current_external_scroll_offset(spatial_node_index); 1437 1438 rect.translate(current_offset) 1439 } 1440 1441 // Remove external scroll offset and snap a rect. The external scroll offset must 1442 // be removed first, as it may be fractional (which we don't want to affect the 1443 // snapping behavior during scene building). 1444 fn normalize_scroll_offset_and_snap_rect( 1445 &mut self, 1446 rect: &LayoutRect, 1447 target_spatial_node: SpatialNodeIndex, 1448 ) -> LayoutRect { 1449 let rect = self.normalize_rect_scroll_offset(rect, target_spatial_node); 1450 1451 self.snap_to_device.set_target_spatial_node( 1452 target_spatial_node, 1453 self.spatial_tree, 1454 ); 1455 self.snap_to_device.snap_rect(&rect) 1456 } 1457 1458 fn build_item<'b>( 1459 &'b mut self, 1460 item: DisplayItemRef, 1461 ) { 1462 match *item.item() { 1463 DisplayItem::Image(ref info) => { 1464 profile_scope!("image"); 1465 1466 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1467 &info.common, 1468 info.bounds, 1469 ); 1470 1471 self.add_image( 1472 spatial_node_index, 1473 clip_node_id, 1474 &layout, 1475 layout.rect.size(), 1476 LayoutSize::zero(), 1477 info.image_key, 1478 info.image_rendering, 1479 info.alpha_type, 1480 info.color, 1481 ); 1482 } 1483 DisplayItem::RepeatingImage(ref info) => { 1484 profile_scope!("repeating_image"); 1485 1486 let (layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1487 &info.common, 1488 info.bounds, 1489 ); 1490 1491 let stretch_size = process_repeat_size( 1492 &layout.rect, 1493 &unsnapped_rect, 1494 info.stretch_size, 1495 ); 1496 1497 self.add_image( 1498 spatial_node_index, 1499 clip_node_id, 1500 &layout, 1501 stretch_size, 1502 info.tile_spacing, 1503 info.image_key, 1504 info.image_rendering, 1505 info.alpha_type, 1506 info.color, 1507 ); 1508 } 1509 DisplayItem::YuvImage(ref info) => { 1510 profile_scope!("yuv_image"); 1511 1512 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1513 &info.common, 1514 info.bounds, 1515 ); 1516 1517 self.add_yuv_image( 1518 spatial_node_index, 1519 clip_node_id, 1520 &layout, 1521 info.yuv_data, 1522 info.color_depth, 1523 info.color_space, 1524 info.color_range, 1525 info.image_rendering, 1526 ); 1527 } 1528 DisplayItem::Text(ref info) => { 1529 profile_scope!("text"); 1530 1531 // TODO(aosmond): Snapping text primitives does not make much sense, given the 1532 // primitive bounds and clip are supposed to be conservative, not definitive. 1533 // E.g. they should be able to grow and not impact the output. However there 1534 // are subtle interactions between the primitive origin and the glyph offset 1535 // which appear to be significant (presumably due to some sort of accumulated 1536 // error throughout the layers). We should fix this at some point. 1537 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1538 &info.common, 1539 info.bounds, 1540 ); 1541 1542 self.add_text( 1543 spatial_node_index, 1544 clip_node_id, 1545 &layout, 1546 &info.font_key, 1547 &info.color, 1548 item.glyphs(), 1549 info.glyph_options, 1550 info.ref_frame_offset, 1551 ); 1552 } 1553 DisplayItem::Rectangle(ref info) => { 1554 profile_scope!("rect"); 1555 1556 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1557 &info.common, 1558 info.bounds, 1559 ); 1560 1561 self.add_primitive( 1562 spatial_node_index, 1563 clip_node_id, 1564 &layout, 1565 Vec::new(), 1566 PrimitiveKeyKind::Rectangle { 1567 color: info.color.into(), 1568 }, 1569 ); 1570 1571 if info.common.flags.contains(PrimitiveFlags::CHECKERBOARD_BACKGROUND) { 1572 self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); 1573 } 1574 } 1575 DisplayItem::HitTest(ref info) => { 1576 profile_scope!("hit_test"); 1577 1578 let spatial_node_index = self.get_space(info.spatial_id); 1579 1580 let rect = self.normalize_scroll_offset_and_snap_rect( 1581 &info.rect, 1582 spatial_node_index, 1583 ); 1584 1585 let layout = LayoutPrimitiveInfo { 1586 rect, 1587 clip_rect: rect, 1588 flags: info.flags, 1589 }; 1590 1591 let spatial_node = self.spatial_tree.get_node_info(spatial_node_index); 1592 let anim_id: u64 = match spatial_node.node_type { 1593 SpatialNodeType::ReferenceFrame(ReferenceFrameInfo { 1594 source_transform: PropertyBinding::Binding(key, _), 1595 .. 1596 }) => key.clone().into(), 1597 SpatialNodeType::StickyFrame(StickyFrameInfo { 1598 transform: Some(PropertyBinding::Binding(key, _)), 1599 .. 1600 }) => key.clone().into(), 1601 _ => 0, 1602 }; 1603 1604 let clip_node_id = self.get_clip_node(info.clip_chain_id); 1605 1606 self.add_primitive_to_hit_testing_list( 1607 &layout, 1608 spatial_node_index, 1609 clip_node_id, 1610 info.tag, 1611 anim_id, 1612 ); 1613 } 1614 DisplayItem::Line(ref info) => { 1615 profile_scope!("line"); 1616 1617 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1618 &info.common, 1619 info.area, 1620 ); 1621 1622 self.add_line( 1623 spatial_node_index, 1624 clip_node_id, 1625 &layout, 1626 info.wavy_line_thickness, 1627 info.orientation, 1628 info.color, 1629 info.style, 1630 ); 1631 } 1632 DisplayItem::Gradient(ref info) => { 1633 profile_scope!("gradient"); 1634 1635 if !info.gradient.is_valid() { 1636 return; 1637 } 1638 1639 let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1640 &info.common, 1641 info.bounds, 1642 ); 1643 1644 let mut tile_size = process_repeat_size( 1645 &layout.rect, 1646 &unsnapped_rect, 1647 info.tile_size, 1648 ); 1649 1650 let mut stops = read_gradient_stops(item.gradient_stops()); 1651 let mut start = info.gradient.start_point; 1652 let mut end = info.gradient.end_point; 1653 let flags = layout.flags; 1654 let optimized = optimize_linear_gradient( 1655 &mut layout.rect, 1656 &mut tile_size, 1657 info.tile_spacing, 1658 &layout.clip_rect, 1659 &mut start, 1660 &mut end, 1661 info.gradient.extend_mode, 1662 &mut stops, 1663 self.config.enable_dithering, 1664 &mut |rect, start, end, stops, edge_aa_mask| { 1665 let layout = LayoutPrimitiveInfo { rect: *rect, clip_rect: *rect, flags }; 1666 if let Some(prim_key_kind) = self.create_linear_gradient_prim( 1667 &layout, 1668 start, 1669 end, 1670 stops.to_vec(), 1671 ExtendMode::Clamp, 1672 rect.size(), 1673 LayoutSize::zero(), 1674 None, 1675 edge_aa_mask, 1676 ) { 1677 self.add_nonshadowable_primitive( 1678 spatial_node_index, 1679 clip_node_id, 1680 &layout, 1681 Vec::new(), 1682 prim_key_kind, 1683 ); 1684 } 1685 } 1686 ); 1687 1688 if !optimized && !tile_size.ceil().is_empty() { 1689 if let Some(prim_key_kind) = self.create_linear_gradient_prim( 1690 &layout, 1691 start, 1692 end, 1693 stops, 1694 info.gradient.extend_mode, 1695 tile_size, 1696 info.tile_spacing, 1697 None, 1698 EdgeAaSegmentMask::all(), 1699 ) { 1700 self.add_nonshadowable_primitive( 1701 spatial_node_index, 1702 clip_node_id, 1703 &layout, 1704 Vec::new(), 1705 prim_key_kind, 1706 ); 1707 } 1708 } 1709 } 1710 DisplayItem::RadialGradient(ref info) => { 1711 profile_scope!("radial"); 1712 1713 if !info.gradient.is_valid() { 1714 return; 1715 } 1716 1717 let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1718 &info.common, 1719 info.bounds, 1720 ); 1721 1722 let mut center = info.gradient.center; 1723 1724 let stops = read_gradient_stops(item.gradient_stops()); 1725 1726 let mut tile_size = process_repeat_size( 1727 &layout.rect, 1728 &unsnapped_rect, 1729 info.tile_size, 1730 ); 1731 1732 let mut prim_rect = layout.rect; 1733 let mut tile_spacing = info.tile_spacing; 1734 optimize_radial_gradient( 1735 &mut prim_rect, 1736 &mut tile_size, 1737 &mut center, 1738 &mut tile_spacing, 1739 &layout.clip_rect, 1740 info.gradient.radius, 1741 info.gradient.end_offset, 1742 info.gradient.extend_mode, 1743 &stops, 1744 &mut |solid_rect, color| { 1745 self.add_nonshadowable_primitive( 1746 spatial_node_index, 1747 clip_node_id, 1748 &LayoutPrimitiveInfo { 1749 rect: *solid_rect, 1750 .. layout 1751 }, 1752 Vec::new(), 1753 PrimitiveKeyKind::Rectangle { color: PropertyBinding::Value(color) }, 1754 ); 1755 } 1756 ); 1757 1758 // TODO: create_radial_gradient_prim already calls 1759 // this, but it leaves the info variable that is 1760 // passed to add_nonshadowable_primitive unmodified 1761 // which can cause issues. 1762 simplify_repeated_primitive(&tile_size, &mut tile_spacing, &mut prim_rect); 1763 1764 if !tile_size.ceil().is_empty() { 1765 layout.rect = prim_rect; 1766 let prim_key_kind = self.create_radial_gradient_prim( 1767 &layout, 1768 center, 1769 info.gradient.start_offset * info.gradient.radius.width, 1770 info.gradient.end_offset * info.gradient.radius.width, 1771 info.gradient.radius.width / info.gradient.radius.height, 1772 stops, 1773 info.gradient.extend_mode, 1774 tile_size, 1775 tile_spacing, 1776 None, 1777 ); 1778 1779 self.add_nonshadowable_primitive( 1780 spatial_node_index, 1781 clip_node_id, 1782 &layout, 1783 Vec::new(), 1784 prim_key_kind, 1785 ); 1786 } 1787 } 1788 DisplayItem::ConicGradient(ref info) => { 1789 profile_scope!("conic"); 1790 1791 if !info.gradient.is_valid() { 1792 return; 1793 } 1794 1795 let (mut layout, unsnapped_rect, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1796 &info.common, 1797 info.bounds, 1798 ); 1799 1800 let tile_size = process_repeat_size( 1801 &layout.rect, 1802 &unsnapped_rect, 1803 info.tile_size, 1804 ); 1805 1806 let offset = apply_gradient_local_clip( 1807 &mut layout.rect, 1808 &tile_size, 1809 &info.tile_spacing, 1810 &layout.clip_rect, 1811 ); 1812 let center = info.gradient.center + offset; 1813 1814 if !tile_size.ceil().is_empty() { 1815 let prim_key_kind = self.create_conic_gradient_prim( 1816 &layout, 1817 center, 1818 info.gradient.angle, 1819 info.gradient.start_offset, 1820 info.gradient.end_offset, 1821 item.gradient_stops(), 1822 info.gradient.extend_mode, 1823 tile_size, 1824 info.tile_spacing, 1825 None, 1826 ); 1827 1828 self.add_nonshadowable_primitive( 1829 spatial_node_index, 1830 clip_node_id, 1831 &layout, 1832 Vec::new(), 1833 prim_key_kind, 1834 ); 1835 } 1836 } 1837 DisplayItem::BoxShadow(ref info) => { 1838 profile_scope!("box_shadow"); 1839 1840 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1841 &info.common, 1842 info.box_bounds, 1843 ); 1844 1845 self.add_box_shadow( 1846 spatial_node_index, 1847 clip_node_id, 1848 &layout, 1849 &info.offset, 1850 info.color, 1851 info.blur_radius, 1852 info.spread_radius, 1853 info.border_radius, 1854 info.clip_mode, 1855 self.spatial_tree.is_root_coord_system(spatial_node_index), 1856 ); 1857 } 1858 DisplayItem::Border(ref info) => { 1859 profile_scope!("border"); 1860 1861 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties_with_bounds( 1862 &info.common, 1863 info.bounds, 1864 ); 1865 1866 self.add_border( 1867 spatial_node_index, 1868 clip_node_id, 1869 &layout, 1870 info, 1871 item.gradient_stops(), 1872 ); 1873 } 1874 DisplayItem::ImageMaskClip(ref info) => { 1875 profile_scope!("image_clip"); 1876 1877 self.add_image_mask_clip_node( 1878 info.id, 1879 info.spatial_id, 1880 &info.image_mask, 1881 info.fill_rule, 1882 item.points(), 1883 ); 1884 } 1885 DisplayItem::RoundedRectClip(ref info) => { 1886 profile_scope!("rounded_clip"); 1887 1888 self.add_rounded_rect_clip_node( 1889 info.id, 1890 info.spatial_id, 1891 &info.clip, 1892 ); 1893 } 1894 DisplayItem::RectClip(ref info) => { 1895 profile_scope!("rect_clip"); 1896 1897 self.add_rect_clip_node( 1898 info.id, 1899 info.spatial_id, 1900 &info.clip_rect, 1901 ); 1902 } 1903 DisplayItem::ClipChain(ref info) => { 1904 profile_scope!("clip_chain"); 1905 1906 self.clip_tree_builder.define_clip_chain( 1907 info.id, 1908 info.parent, 1909 item.clip_chain_items().into_iter(), 1910 ); 1911 }, 1912 DisplayItem::BackdropFilter(ref info) => { 1913 profile_scope!("backdrop"); 1914 1915 let (layout, _, spatial_node_index, clip_node_id) = self.process_common_properties( 1916 &info.common, 1917 None, 1918 ); 1919 1920 let filters = filter_ops_for_compositing(item.filters()); 1921 let filter_datas = filter_datas_for_compositing(item.filter_datas()); 1922 1923 self.add_backdrop_filter( 1924 spatial_node_index, 1925 clip_node_id, 1926 &layout, 1927 filters, 1928 filter_datas, 1929 ); 1930 } 1931 1932 // Do nothing; these are dummy items for the display list parser 1933 DisplayItem::SetGradientStops | 1934 DisplayItem::SetFilterOps | 1935 DisplayItem::SetFilterData | 1936 DisplayItem::SetPoints => {} 1937 1938 // Special items that are handled in the parent method 1939 DisplayItem::PushStackingContext(..) | 1940 DisplayItem::PushReferenceFrame(..) | 1941 DisplayItem::PopReferenceFrame | 1942 DisplayItem::PopStackingContext | 1943 DisplayItem::Iframe(_) => { 1944 unreachable!("Handled in `build_all`") 1945 } 1946 1947 DisplayItem::ReuseItems(key) | 1948 DisplayItem::RetainedItems(key) => { 1949 unreachable!("Iterator logic error: {:?}", key); 1950 } 1951 1952 DisplayItem::PushShadow(info) => { 1953 profile_scope!("push_shadow"); 1954 1955 let spatial_node_index = self.get_space(info.space_and_clip.spatial_id); 1956 1957 self.push_shadow( 1958 info.shadow, 1959 spatial_node_index, 1960 info.space_and_clip.clip_chain_id, 1961 info.should_inflate, 1962 ); 1963 } 1964 DisplayItem::PopAllShadows => { 1965 profile_scope!("pop_all_shadows"); 1966 1967 self.pop_all_shadows(); 1968 } 1969 DisplayItem::DebugMarker(..) => {} 1970 } 1971 } 1972 1973 /// Create a primitive and add it to the prim store. This method doesn't 1974 /// add the primitive to the draw list, so can be used for creating 1975 /// sub-primitives. 1976 /// 1977 /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list` 1978 fn create_primitive<P>( 1979 &mut self, 1980 info: &LayoutPrimitiveInfo, 1981 clip_leaf_id: ClipLeafId, 1982 prim: P, 1983 ) -> PrimitiveInstance 1984 where 1985 P: InternablePrimitive, 1986 Interners: AsMut<Interner<P>>, 1987 { 1988 // Build a primitive key. 1989 let prim_key = prim.into_key(info); 1990 1991 let interner = self.interners.as_mut(); 1992 let prim_data_handle = interner 1993 .intern(&prim_key, || ()); 1994 1995 let instance_kind = P::make_instance_kind( 1996 prim_key, 1997 prim_data_handle, 1998 &mut self.prim_store, 1999 ); 2000 2001 PrimitiveInstance::new( 2002 instance_kind, 2003 clip_leaf_id, 2004 ) 2005 } 2006 2007 fn add_primitive_to_hit_testing_list( 2008 &mut self, 2009 info: &LayoutPrimitiveInfo, 2010 spatial_node_index: SpatialNodeIndex, 2011 clip_node_id: ClipNodeId, 2012 tag: ItemTag, 2013 anim_id: u64, 2014 ) { 2015 self.hit_testing_scene.add_item( 2016 tag, 2017 anim_id, 2018 info, 2019 spatial_node_index, 2020 clip_node_id, 2021 &self.clip_tree_builder, 2022 self.interners, 2023 ); 2024 } 2025 2026 /// Add an already created primitive to the draw lists. 2027 pub fn add_primitive_to_draw_list( 2028 &mut self, 2029 prim_instance: PrimitiveInstance, 2030 prim_rect: LayoutRect, 2031 spatial_node_index: SpatialNodeIndex, 2032 flags: PrimitiveFlags, 2033 ) { 2034 // Add primitive to the top-most stacking context on the stack. 2035 2036 // If we have a valid stacking context, the primitive gets added to that. 2037 // Otherwise, it gets added to a top-level picture cache slice. 2038 2039 match self.sc_stack.last_mut() { 2040 Some(stacking_context) => { 2041 stacking_context.prim_list.add_prim( 2042 prim_instance, 2043 prim_rect, 2044 spatial_node_index, 2045 flags, 2046 &mut self.prim_instances, 2047 &self.clip_tree_builder, 2048 ); 2049 } 2050 None => { 2051 self.tile_cache_builder.add_prim( 2052 prim_instance, 2053 prim_rect, 2054 spatial_node_index, 2055 flags, 2056 self.spatial_tree, 2057 self.interners, 2058 &self.quality_settings, 2059 &mut self.prim_instances, 2060 &self.clip_tree_builder, 2061 ); 2062 } 2063 } 2064 } 2065 2066 /// Convenience interface that creates a primitive entry and adds it 2067 /// to the draw list. 2068 pub fn add_nonshadowable_primitive<P>( 2069 &mut self, 2070 spatial_node_index: SpatialNodeIndex, 2071 clip_node_id: ClipNodeId, 2072 info: &LayoutPrimitiveInfo, 2073 clip_items: Vec<ClipItemKey>, 2074 prim: P, 2075 ) 2076 where 2077 P: InternablePrimitive + IsVisible, 2078 Interners: AsMut<Interner<P>>, 2079 { 2080 if prim.is_visible() { 2081 let clip_leaf_id = self.clip_tree_builder.build_for_prim( 2082 clip_node_id, 2083 info, 2084 &clip_items, 2085 &mut self.interners, 2086 ); 2087 2088 self.add_prim_to_draw_list( 2089 info, 2090 spatial_node_index, 2091 clip_leaf_id, 2092 prim, 2093 ); 2094 } 2095 } 2096 2097 pub fn add_primitive<P>( 2098 &mut self, 2099 spatial_node_index: SpatialNodeIndex, 2100 clip_node_id: ClipNodeId, 2101 info: &LayoutPrimitiveInfo, 2102 clip_items: Vec<ClipItemKey>, 2103 prim: P, 2104 ) 2105 where 2106 P: InternablePrimitive + IsVisible, 2107 Interners: AsMut<Interner<P>>, 2108 ShadowItem: From<PendingPrimitive<P>> 2109 { 2110 // If a shadow context is not active, then add the primitive 2111 // directly to the parent picture. 2112 if self.pending_shadow_items.is_empty() { 2113 self.add_nonshadowable_primitive( 2114 spatial_node_index, 2115 clip_node_id, 2116 info, 2117 clip_items, 2118 prim, 2119 ); 2120 } else { 2121 debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives"); 2122 2123 // There is an active shadow context. Store as a pending primitive 2124 // for processing during pop_all_shadows. 2125 self.pending_shadow_items.push_back(PendingPrimitive { 2126 spatial_node_index, 2127 clip_node_id, 2128 info: *info, 2129 prim, 2130 }.into()); 2131 } 2132 } 2133 2134 fn add_prim_to_draw_list<P>( 2135 &mut self, 2136 info: &LayoutPrimitiveInfo, 2137 spatial_node_index: SpatialNodeIndex, 2138 clip_leaf_id: ClipLeafId, 2139 prim: P, 2140 ) 2141 where 2142 P: InternablePrimitive, 2143 Interners: AsMut<Interner<P>>, 2144 { 2145 let prim_instance = self.create_primitive( 2146 info, 2147 clip_leaf_id, 2148 prim, 2149 ); 2150 self.add_primitive_to_draw_list( 2151 prim_instance, 2152 info.rect, 2153 spatial_node_index, 2154 info.flags, 2155 ); 2156 } 2157 2158 fn make_current_slice_atomic_if_required(&mut self) { 2159 let has_non_wrapping_sc = self.sc_stack 2160 .iter() 2161 .position(|sc| { 2162 !sc.flags.contains(StackingContextFlags::WRAPS_BACKDROP_FILTER) 2163 }) 2164 .is_some(); 2165 2166 if has_non_wrapping_sc { 2167 return; 2168 } 2169 2170 // Shadows can only exist within a stacking context 2171 assert!(self.pending_shadow_items.is_empty()); 2172 self.tile_cache_builder.make_current_slice_atomic(); 2173 } 2174 2175 /// If no stacking contexts are present (i.e. we are adding prims to a tile 2176 /// cache), set a barrier to force creation of a slice before the next prim 2177 fn add_tile_cache_barrier_if_needed( 2178 &mut self, 2179 slice_flags: SliceFlags, 2180 ) { 2181 if self.sc_stack.is_empty() { 2182 // Shadows can only exist within a stacking context 2183 assert!(self.pending_shadow_items.is_empty()); 2184 2185 self.tile_cache_builder.add_tile_cache_barrier( 2186 slice_flags, 2187 self.root_iframe_clip, 2188 ); 2189 } 2190 } 2191 2192 /// Push a new stacking context. Returns context that must be passed to pop_stacking_context(). 2193 fn push_stacking_context( 2194 &mut self, 2195 mut composite_ops: CompositeOps, 2196 transform_style: TransformStyle, 2197 prim_flags: PrimitiveFlags, 2198 spatial_node_index: SpatialNodeIndex, 2199 clip_chain_id: Option<api::ClipChainId>, 2200 requested_raster_space: RasterSpace, 2201 flags: StackingContextFlags, 2202 subregion_offset: LayoutVector2D, 2203 ) -> StackingContextInfo { 2204 profile_scope!("push_stacking_context"); 2205 2206 // Filters have to be baked into the snapshot. Most filters are applied 2207 // when rendering the picture into its parent, so if the stacking context 2208 // needs to be snapshotted, we nest it into an extra stacking context and 2209 // capture the outer stacking context into which the filter is drawn. 2210 // Note: blur filters don't actually need an extra stacking context 2211 // since the blur is baked into a render task instead of being applied 2212 // when compositing the picture into its parent. This case is fairly rare 2213 // so we pay the cost of the extra render pass for now. 2214 let needs_extra_stacking_context = composite_ops.snapshot.is_some() 2215 && composite_ops.has_valid_filters(); 2216 2217 if needs_extra_stacking_context { 2218 let snapshot = mem::take(&mut composite_ops.snapshot); 2219 let mut info = self.push_stacking_context( 2220 CompositeOps { 2221 filters: Vec::new(), 2222 filter_datas: Vec::new(), 2223 mix_blend_mode: None, 2224 snapshot, 2225 }, 2226 TransformStyle::Flat, 2227 prim_flags, 2228 spatial_node_index, 2229 clip_chain_id, 2230 requested_raster_space, 2231 flags, 2232 LayoutVector2D::zero(), 2233 ); 2234 info.pop_stacking_context = true; 2235 self.extra_stacking_context_stack.push(info); 2236 } 2237 2238 let clip_node_id = match clip_chain_id { 2239 Some(id) => { 2240 self.clip_tree_builder.build_clip_set(id) 2241 } 2242 None => { 2243 self.clip_tree_builder.build_clip_set(api::ClipChainId::INVALID) 2244 } 2245 }; 2246 2247 self.clip_tree_builder.push_clip_chain( 2248 clip_chain_id, 2249 !composite_ops.is_empty(), 2250 composite_ops.snapshot.is_some(), 2251 ); 2252 2253 let new_space = match (self.raster_space_stack.last(), requested_raster_space) { 2254 // If no parent space, just use the requested space 2255 (None, _) => requested_raster_space, 2256 // If screen, use the parent 2257 (Some(parent_space), RasterSpace::Screen) => *parent_space, 2258 // If currently screen, select the requested 2259 (Some(RasterSpace::Screen), space) => space, 2260 // If both local, take the maximum scale 2261 (Some(RasterSpace::Local(parent_scale)), RasterSpace::Local(scale)) => RasterSpace::Local(parent_scale.max(scale)), 2262 }; 2263 self.raster_space_stack.push(new_space); 2264 2265 // Get the transform-style of the parent stacking context, 2266 // which determines if we *might* need to draw this on 2267 // an intermediate surface for plane splitting purposes. 2268 let (parent_is_3d, extra_3d_instance, plane_splitter_index) = match self.sc_stack.last_mut() { 2269 Some(ref mut sc) if sc.is_3d() => { 2270 let (flat_items_context_3d, plane_splitter_index) = match sc.context_3d { 2271 Picture3DContext::In { ancestor_index, plane_splitter_index, .. } => { 2272 ( 2273 Picture3DContext::In { 2274 root_data: None, 2275 ancestor_index, 2276 plane_splitter_index, 2277 }, 2278 plane_splitter_index, 2279 ) 2280 } 2281 Picture3DContext::Out => panic!("Unexpected out of 3D context"), 2282 }; 2283 // Cut the sequence of flat children before starting a child stacking context, 2284 // so that the relative order between them and our current SC is preserved. 2285 let extra_instance = sc.cut_item_sequence( 2286 &mut self.prim_store, 2287 &mut self.interners, 2288 Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)), 2289 flat_items_context_3d, 2290 &mut self.clip_tree_builder, 2291 ); 2292 let extra_instance = extra_instance.map(|(_, instance)| { 2293 ExtendedPrimitiveInstance { 2294 instance, 2295 spatial_node_index: sc.spatial_node_index, 2296 flags: sc.prim_flags, 2297 } 2298 }); 2299 (true, extra_instance, Some(plane_splitter_index)) 2300 }, 2301 _ => (false, None, None), 2302 }; 2303 2304 if let Some(instance) = extra_3d_instance { 2305 self.add_primitive_instance_to_3d_root(instance); 2306 } 2307 2308 // If this is preserve-3d *or* the parent is, then this stacking 2309 // context is participating in the 3d rendering context. In that 2310 // case, hoist the picture up to the 3d rendering context 2311 // container, so that it's rendered as a sibling with other 2312 // elements in this context. 2313 let participating_in_3d_context = 2314 composite_ops.is_empty() && 2315 (parent_is_3d || transform_style == TransformStyle::Preserve3D); 2316 2317 let context_3d = if participating_in_3d_context { 2318 // Get the spatial node index of the containing block, which 2319 // defines the context of backface-visibility. 2320 let ancestor_index = self.containing_block_stack 2321 .last() 2322 .cloned() 2323 .unwrap_or(self.spatial_tree.root_reference_frame_index()); 2324 2325 let plane_splitter_index = plane_splitter_index.unwrap_or_else(|| { 2326 let index = self.next_plane_splitter_index; 2327 self.next_plane_splitter_index += 1; 2328 PlaneSplitterIndex(index) 2329 }); 2330 2331 Picture3DContext::In { 2332 root_data: if parent_is_3d { 2333 None 2334 } else { 2335 Some(Vec::new()) 2336 }, 2337 plane_splitter_index, 2338 ancestor_index, 2339 } 2340 } else { 2341 Picture3DContext::Out 2342 }; 2343 2344 // Force an intermediate surface if the stacking context has a 2345 // complex clip node. In the future, we may decide during 2346 // prepare step to skip the intermediate surface if the 2347 // clip node doesn't affect the stacking context rect. 2348 let mut blit_reason = BlitReason::empty(); 2349 2350 // If we are forcing a backdrop root here, isolate this context 2351 // by using an intermediate surface. 2352 if flags.contains(StackingContextFlags::FORCED_ISOLATION) { 2353 blit_reason = BlitReason::FORCED_ISOLATION; 2354 } 2355 2356 // Stacking context snapshots are offscreen surfaces. 2357 if composite_ops.snapshot.is_some() { 2358 blit_reason = BlitReason::SNAPSHOT; 2359 } 2360 2361 // If this stacking context has any complex clips, we need to draw it 2362 // to an off-screen surface. 2363 if let Some(clip_chain_id) = clip_chain_id { 2364 if self.clip_tree_builder.clip_chain_has_complex_clips(clip_chain_id, &self.interners) { 2365 blit_reason |= BlitReason::CLIP; 2366 } 2367 } 2368 2369 // Check if we know this stacking context is redundant (doesn't need a surface) 2370 // The check for blend-container redundancy is more involved so it's handled below. 2371 let mut is_redundant = FlattenedStackingContext::is_redundant( 2372 &context_3d, 2373 &composite_ops, 2374 blit_reason, 2375 self.sc_stack.last(), 2376 prim_flags, 2377 ); 2378 2379 // If the stacking context is a blend container, and if we're at the top level 2380 // of the stacking context tree, we may be able to make this blend container into a tile 2381 // cache. This means that we get caching and correct scrolling invalidation for 2382 // root level blend containers. For these cases, the readbacks of the backdrop 2383 // are handled by doing partial reads of the picture cache tiles during rendering. 2384 if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) { 2385 // Check if we're inside a stacking context hierarchy with an existing surface 2386 if !self.sc_stack.is_empty() { 2387 // If we are already inside a stacking context hierarchy with a surface, then we 2388 // need to do the normal isolate of this blend container as a regular surface 2389 blit_reason |= BlitReason::BLEND_MODE; 2390 is_redundant = false; 2391 } else { 2392 // If the current slice is empty, then we can just mark the slice as 2393 // atomic (so that compositor surfaces don't get promoted within it) 2394 // and use that slice as the backing surface for the blend container 2395 if self.tile_cache_builder.is_current_slice_empty() && 2396 self.spatial_tree.is_root_coord_system(spatial_node_index) && 2397 !self.clip_tree_builder.clip_node_has_complex_clips(clip_node_id, &self.interners) 2398 { 2399 self.add_tile_cache_barrier_if_needed(SliceFlags::IS_ATOMIC); 2400 self.tile_cache_builder.make_current_slice_atomic(); 2401 } else { 2402 // If the slice wasn't empty, we need to isolate a separate surface 2403 // to ensure that the content already in the slice is not used as 2404 // an input to the mix-blend composite 2405 blit_reason |= BlitReason::BLEND_MODE; 2406 is_redundant = false; 2407 } 2408 } 2409 } 2410 2411 // If stacking context is a scrollbar, force a new slice for the primitives 2412 // within. The stacking context will be redundant and removed by above check. 2413 let set_tile_cache_barrier = prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER); 2414 2415 if set_tile_cache_barrier { 2416 self.add_tile_cache_barrier_if_needed(SliceFlags::IS_SCROLLBAR); 2417 } 2418 2419 let mut sc_info = StackingContextInfo { 2420 pop_stacking_context: false, 2421 pop_containing_block: false, 2422 set_tile_cache_barrier, 2423 needs_extra_stacking_context, 2424 }; 2425 2426 // If this is not 3d, then it establishes an ancestor root for child 3d contexts. 2427 if !participating_in_3d_context { 2428 sc_info.pop_containing_block = true; 2429 self.containing_block_stack.push(spatial_node_index); 2430 } 2431 2432 // If not redundant, create a stacking context to hold primitive clusters 2433 if !is_redundant { 2434 sc_info.pop_stacking_context = true; 2435 2436 // Push the SC onto the stack, so we know how to handle things in 2437 // pop_stacking_context. 2438 self.sc_stack.push(FlattenedStackingContext { 2439 prim_list: PrimitiveList::empty(), 2440 prim_flags, 2441 spatial_node_index, 2442 clip_node_id, 2443 composite_ops, 2444 blit_reason, 2445 transform_style, 2446 context_3d, 2447 flags, 2448 raster_space: new_space, 2449 subregion_offset, 2450 }); 2451 } 2452 2453 sc_info 2454 } 2455 2456 fn pop_stacking_context( 2457 &mut self, 2458 info: StackingContextInfo, 2459 ) { 2460 profile_scope!("pop_stacking_context"); 2461 2462 self.clip_tree_builder.pop_clip(); 2463 2464 // Pop off current raster space (pushed unconditionally in push_stacking_context) 2465 self.raster_space_stack.pop().unwrap(); 2466 2467 // If the stacking context formed a containing block, pop off the stack 2468 if info.pop_containing_block { 2469 self.containing_block_stack.pop().unwrap(); 2470 } 2471 2472 if info.set_tile_cache_barrier { 2473 self.add_tile_cache_barrier_if_needed(SliceFlags::empty()); 2474 } 2475 2476 // If the stacking context was otherwise redundant, early exit 2477 if !info.pop_stacking_context { 2478 return; 2479 } 2480 2481 let stacking_context = self.sc_stack.pop().unwrap(); 2482 2483 let mut source = match stacking_context.context_3d { 2484 // TODO(gw): For now, as soon as this picture is in 2485 // a 3D context, we draw it to an intermediate 2486 // surface and apply plane splitting. However, 2487 // there is a large optimization opportunity here. 2488 // During culling, we can check if there is actually 2489 // perspective present, and skip the plane splitting 2490 // completely when that is not the case. 2491 Picture3DContext::In { ancestor_index, plane_splitter_index, .. } => { 2492 let composite_mode = Some( 2493 PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason) 2494 ); 2495 2496 // Add picture for this actual stacking context contents to render into. 2497 let pic_index = PictureIndex(self.prim_store.pictures 2498 .alloc() 2499 .init(PicturePrimitive::new_image( 2500 composite_mode.clone(), 2501 Picture3DContext::In { root_data: None, ancestor_index, plane_splitter_index }, 2502 stacking_context.prim_flags, 2503 stacking_context.prim_list, 2504 stacking_context.spatial_node_index, 2505 stacking_context.raster_space, 2506 PictureFlags::empty(), 2507 None, 2508 )) 2509 ); 2510 2511 let instance = create_prim_instance( 2512 pic_index, 2513 composite_mode.into(), 2514 stacking_context.raster_space, 2515 stacking_context.clip_node_id, 2516 &mut self.interners, 2517 &mut self.clip_tree_builder, 2518 ); 2519 2520 PictureChainBuilder::from_instance( 2521 instance, 2522 stacking_context.prim_flags, 2523 stacking_context.spatial_node_index, 2524 stacking_context.raster_space, 2525 ) 2526 } 2527 Picture3DContext::Out => { 2528 if stacking_context.blit_reason.is_empty() { 2529 PictureChainBuilder::from_prim_list( 2530 stacking_context.prim_list, 2531 stacking_context.prim_flags, 2532 stacking_context.spatial_node_index, 2533 stacking_context.raster_space, 2534 false, 2535 ) 2536 } else { 2537 let composite_mode = Some( 2538 PictureCompositeMode::Blit(stacking_context.blit_reason) 2539 ); 2540 2541 // Add picture for this actual stacking context contents to render into. 2542 let pic_index = PictureIndex(self.prim_store.pictures 2543 .alloc() 2544 .init(PicturePrimitive::new_image( 2545 composite_mode.clone(), 2546 Picture3DContext::Out, 2547 stacking_context.prim_flags, 2548 stacking_context.prim_list, 2549 stacking_context.spatial_node_index, 2550 stacking_context.raster_space, 2551 PictureFlags::empty(), 2552 None, 2553 )) 2554 ); 2555 2556 let instance = create_prim_instance( 2557 pic_index, 2558 composite_mode.into(), 2559 stacking_context.raster_space, 2560 stacking_context.clip_node_id, 2561 &mut self.interners, 2562 &mut self.clip_tree_builder, 2563 ); 2564 2565 PictureChainBuilder::from_instance( 2566 instance, 2567 stacking_context.prim_flags, 2568 stacking_context.spatial_node_index, 2569 stacking_context.raster_space, 2570 ) 2571 } 2572 } 2573 }; 2574 2575 // If establishing a 3d context, the `cur_instance` represents 2576 // a picture with all the *trailing* immediate children elements. 2577 // We append this to the preserve-3D picture set and make a container picture of them. 2578 if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index, plane_splitter_index } = stacking_context.context_3d { 2579 let instance = source.finalize( 2580 ClipNodeId::NONE, 2581 &mut self.interners, 2582 &mut self.prim_store, 2583 &mut self.clip_tree_builder, 2584 None, 2585 ); 2586 2587 prims.push(ExtendedPrimitiveInstance { 2588 instance, 2589 spatial_node_index: stacking_context.spatial_node_index, 2590 flags: stacking_context.prim_flags, 2591 }); 2592 2593 let mut prim_list = PrimitiveList::empty(); 2594 2595 // Web content often specifies `preserve-3d` on pages that don't actually need 2596 // a 3d rendering context (as a hint / hack to convince other browsers to 2597 // layerize these elements to an off-screen surface). Detect cases where the 2598 // preserve-3d has no effect on correctness and convert them to pass-through 2599 // pictures instead. This has two benefits for WR: 2600 // 2601 // (1) We get correct subpixel-snapping behavior between preserve-3d elements 2602 // that don't have complex transforms without additional complexity of 2603 // handling subpixel-snapping across different surfaces. 2604 // (2) We can draw this content directly in to the parent surface / tile cache, 2605 // which is a performance win by avoiding allocating, drawing, 2606 // plane-splitting and blitting an off-screen surface. 2607 let mut needs_3d_context = false; 2608 2609 for ext_prim in prims.drain(..) { 2610 // If all the preserve-3d elements are in the root coordinate system, we 2611 // know that there is no need for a true 3d rendering context / plane-split. 2612 // TODO(gw): We can expand this in future to handle this in more cases 2613 // (e.g. a non-root coord system that is 2d within the 3d context). 2614 if !self.spatial_tree.is_root_coord_system(ext_prim.spatial_node_index) { 2615 needs_3d_context = true; 2616 } 2617 2618 prim_list.add_prim( 2619 ext_prim.instance, 2620 LayoutRect::zero(), 2621 ext_prim.spatial_node_index, 2622 ext_prim.flags, 2623 &mut self.prim_instances, 2624 &self.clip_tree_builder, 2625 ); 2626 } 2627 2628 let context_3d = if needs_3d_context { 2629 Picture3DContext::In { 2630 root_data: Some(Vec::new()), 2631 ancestor_index, 2632 plane_splitter_index, 2633 } 2634 } else { 2635 // If we didn't need a 3d rendering context, walk the child pictures 2636 // that make up this context and disable the off-screen surface and 2637 // 3d render context. 2638 for child_pic_index in &prim_list.child_pictures { 2639 let child_pic = &mut self.prim_store.pictures[child_pic_index.0]; 2640 let needs_surface = child_pic.snapshot.is_some(); 2641 if !needs_surface { 2642 child_pic.composite_mode = None; 2643 } 2644 child_pic.context_3d = Picture3DContext::Out; 2645 } 2646 2647 Picture3DContext::Out 2648 }; 2649 2650 // This is the acttual picture representing our 3D hierarchy root. 2651 let pic_index = PictureIndex(self.prim_store.pictures 2652 .alloc() 2653 .init(PicturePrimitive::new_image( 2654 None, 2655 context_3d, 2656 stacking_context.prim_flags, 2657 prim_list, 2658 stacking_context.spatial_node_index, 2659 stacking_context.raster_space, 2660 PictureFlags::empty(), 2661 None, 2662 )) 2663 ); 2664 2665 let instance = create_prim_instance( 2666 pic_index, 2667 PictureCompositeKey::Identity, 2668 stacking_context.raster_space, 2669 stacking_context.clip_node_id, 2670 &mut self.interners, 2671 &mut self.clip_tree_builder, 2672 ); 2673 2674 source = PictureChainBuilder::from_instance( 2675 instance, 2676 stacking_context.prim_flags, 2677 stacking_context.spatial_node_index, 2678 stacking_context.raster_space, 2679 ); 2680 } 2681 2682 let has_filters = stacking_context.composite_ops.has_valid_filters(); 2683 2684 let spatial_node_context_offset = 2685 stacking_context.subregion_offset + 2686 self.current_external_scroll_offset(stacking_context.spatial_node_index); 2687 source = self.wrap_prim_with_filters( 2688 source, 2689 stacking_context.clip_node_id, 2690 stacking_context.composite_ops.filters, 2691 stacking_context.composite_ops.filter_datas, 2692 false, 2693 spatial_node_context_offset, 2694 ); 2695 2696 // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent 2697 // stacking context. 2698 // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is: 2699 // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs) 2700 // where 2701 // Cs = Source color 2702 // ab = Backdrop alpha 2703 // Cb = Backdrop color 2704 // 2705 // If we're the first primitive within a stacking context, then we can guarantee that the 2706 // backdrop alpha will be 0, and then the blend equation collapses to just 2707 // Cs = Cs, and the blend mode isn't taken into account at all. 2708 if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode { 2709 let composite_mode = PictureCompositeMode::MixBlend(mix_blend_mode); 2710 2711 source = source.add_picture( 2712 composite_mode, 2713 stacking_context.clip_node_id, 2714 Picture3DContext::Out, 2715 &mut self.interners, 2716 &mut self.prim_store, 2717 &mut self.prim_instances, 2718 &mut self.clip_tree_builder, 2719 ); 2720 } 2721 2722 // Set the stacking context clip on the outermost picture in the chain, 2723 // unless we already set it on the leaf picture. 2724 let cur_instance = source.finalize( 2725 stacking_context.clip_node_id, 2726 &mut self.interners, 2727 &mut self.prim_store, 2728 &mut self.clip_tree_builder, 2729 stacking_context.composite_ops.snapshot, 2730 ); 2731 2732 if stacking_context.composite_ops.snapshot.is_some() { 2733 let pic_index = cur_instance.kind.as_pic(); 2734 self.snapshot_pictures.push(pic_index); 2735 } 2736 2737 // The primitive instance for the remainder of flat children of this SC 2738 // if it's a part of 3D hierarchy but not the root of it. 2739 let trailing_children_instance = match self.sc_stack.last_mut() { 2740 // Preserve3D path (only relevant if there are no filters/mix-blend modes) 2741 Some(ref parent_sc) if !has_filters && parent_sc.is_3d() => { 2742 Some(cur_instance) 2743 } 2744 // Regular parenting path 2745 Some(ref mut parent_sc) => { 2746 parent_sc.prim_list.add_prim( 2747 cur_instance, 2748 LayoutRect::zero(), 2749 stacking_context.spatial_node_index, 2750 stacking_context.prim_flags, 2751 &mut self.prim_instances, 2752 &self.clip_tree_builder, 2753 ); 2754 None 2755 } 2756 // This must be the root stacking context 2757 None => { 2758 self.add_primitive_to_draw_list( 2759 cur_instance, 2760 LayoutRect::zero(), 2761 stacking_context.spatial_node_index, 2762 stacking_context.prim_flags, 2763 ); 2764 2765 None 2766 } 2767 }; 2768 2769 // finally, if there any outstanding 3D primitive instances, 2770 // find the 3D hierarchy root and add them there. 2771 if let Some(instance) = trailing_children_instance { 2772 self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance { 2773 instance, 2774 spatial_node_index: stacking_context.spatial_node_index, 2775 flags: stacking_context.prim_flags, 2776 }); 2777 } 2778 2779 assert!( 2780 self.pending_shadow_items.is_empty(), 2781 "Found unpopped shadows when popping stacking context!" 2782 ); 2783 2784 if info.needs_extra_stacking_context { 2785 let inner_info = self.extra_stacking_context_stack.pop().unwrap(); 2786 self.pop_stacking_context(inner_info); 2787 } 2788 } 2789 2790 pub fn push_reference_frame( 2791 &mut self, 2792 reference_frame_id: SpatialId, 2793 parent_index: SpatialNodeIndex, 2794 pipeline_id: PipelineId, 2795 transform_style: TransformStyle, 2796 source_transform: PropertyBinding<LayoutTransform>, 2797 kind: ReferenceFrameKind, 2798 origin_in_parent_reference_frame: LayoutVector2D, 2799 uid: SpatialNodeUid, 2800 ) -> SpatialNodeIndex { 2801 let index = self.spatial_tree.add_reference_frame( 2802 parent_index, 2803 transform_style, 2804 source_transform, 2805 kind, 2806 origin_in_parent_reference_frame, 2807 pipeline_id, 2808 uid, 2809 ); 2810 self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(reference_frame_id, index); 2811 2812 index 2813 } 2814 2815 fn push_root( 2816 &mut self, 2817 pipeline_id: PipelineId, 2818 instance: PipelineInstanceId, 2819 ) { 2820 let spatial_node_index = self.push_reference_frame( 2821 SpatialId::root_reference_frame(pipeline_id), 2822 self.spatial_tree.root_reference_frame_index(), 2823 pipeline_id, 2824 TransformStyle::Flat, 2825 PropertyBinding::Value(LayoutTransform::identity()), 2826 ReferenceFrameKind::Transform { 2827 is_2d_scale_translation: true, 2828 should_snap: true, 2829 paired_with_perspective: false, 2830 }, 2831 LayoutVector2D::zero(), 2832 SpatialNodeUid::root_reference_frame(pipeline_id, instance), 2833 ); 2834 2835 let viewport_rect = LayoutRect::max_rect(); 2836 2837 self.add_scroll_frame( 2838 SpatialId::root_scroll_node(pipeline_id), 2839 spatial_node_index, 2840 ExternalScrollId(0, pipeline_id), 2841 pipeline_id, 2842 &viewport_rect, 2843 &viewport_rect.size(), 2844 ScrollFrameKind::PipelineRoot { 2845 is_root_pipeline: true, 2846 }, 2847 LayoutVector2D::zero(), 2848 APZScrollGeneration::default(), 2849 HasScrollLinkedEffect::No, 2850 SpatialNodeUid::root_scroll_frame(pipeline_id, instance), 2851 ); 2852 } 2853 2854 fn add_image_mask_clip_node( 2855 &mut self, 2856 new_node_id: ClipId, 2857 spatial_id: SpatialId, 2858 image_mask: &ImageMask, 2859 fill_rule: FillRule, 2860 points_range: ItemRange<LayoutPoint>, 2861 ) { 2862 let spatial_node_index = self.get_space(spatial_id); 2863 2864 let snapped_mask_rect = self.normalize_scroll_offset_and_snap_rect( 2865 &image_mask.rect, 2866 spatial_node_index, 2867 ); 2868 2869 let points: Vec<LayoutPoint> = points_range.iter().collect(); 2870 2871 // If any points are provided, then intern a polygon with the points and fill rule. 2872 let mut polygon_handle: Option<PolygonDataHandle> = None; 2873 if points.len() > 0 { 2874 let item = PolygonKey::new(&points, fill_rule); 2875 2876 let handle = self 2877 .interners 2878 .polygon 2879 .intern(&item, || item); 2880 polygon_handle = Some(handle); 2881 } 2882 2883 let item = ClipItemKey { 2884 kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect, polygon_handle), 2885 spatial_node_index, 2886 }; 2887 2888 let handle = self 2889 .interners 2890 .clip 2891 .intern(&item, || { 2892 ClipInternData { 2893 key: item, 2894 } 2895 }); 2896 2897 self.clip_tree_builder.define_image_mask_clip( 2898 new_node_id, 2899 handle, 2900 ); 2901 } 2902 2903 /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`. 2904 fn add_rect_clip_node( 2905 &mut self, 2906 new_node_id: ClipId, 2907 spatial_id: SpatialId, 2908 clip_rect: &LayoutRect, 2909 ) { 2910 let spatial_node_index = self.get_space(spatial_id); 2911 2912 let snapped_clip_rect = self.normalize_scroll_offset_and_snap_rect( 2913 clip_rect, 2914 spatial_node_index, 2915 ); 2916 2917 let item = ClipItemKey { 2918 kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), 2919 spatial_node_index, 2920 }; 2921 let handle = self 2922 .interners 2923 .clip 2924 .intern(&item, || { 2925 ClipInternData { 2926 key: item, 2927 } 2928 }); 2929 2930 self.clip_tree_builder.define_rect_clip( 2931 new_node_id, 2932 handle, 2933 ); 2934 } 2935 2936 fn add_rounded_rect_clip_node( 2937 &mut self, 2938 new_node_id: ClipId, 2939 spatial_id: SpatialId, 2940 clip: &ComplexClipRegion, 2941 ) { 2942 let spatial_node_index = self.get_space(spatial_id); 2943 2944 let snapped_region_rect = self.normalize_scroll_offset_and_snap_rect( 2945 &clip.rect, 2946 spatial_node_index, 2947 ); 2948 2949 let item = ClipItemKey { 2950 kind: ClipItemKeyKind::rounded_rect( 2951 snapped_region_rect, 2952 clip.radii, 2953 clip.mode, 2954 ), 2955 spatial_node_index, 2956 }; 2957 2958 let handle = self 2959 .interners 2960 .clip 2961 .intern(&item, || { 2962 ClipInternData { 2963 key: item, 2964 } 2965 }); 2966 2967 self.clip_tree_builder.define_rounded_rect_clip( 2968 new_node_id, 2969 handle, 2970 ); 2971 } 2972 2973 pub fn add_scroll_frame( 2974 &mut self, 2975 new_node_id: SpatialId, 2976 parent_node_index: SpatialNodeIndex, 2977 external_id: ExternalScrollId, 2978 pipeline_id: PipelineId, 2979 frame_rect: &LayoutRect, 2980 content_size: &LayoutSize, 2981 frame_kind: ScrollFrameKind, 2982 external_scroll_offset: LayoutVector2D, 2983 scroll_offset_generation: APZScrollGeneration, 2984 has_scroll_linked_effect: HasScrollLinkedEffect, 2985 uid: SpatialNodeUid, 2986 ) -> SpatialNodeIndex { 2987 let node_index = self.spatial_tree.add_scroll_frame( 2988 parent_node_index, 2989 external_id, 2990 pipeline_id, 2991 frame_rect, 2992 content_size, 2993 frame_kind, 2994 external_scroll_offset, 2995 scroll_offset_generation, 2996 has_scroll_linked_effect, 2997 uid, 2998 ); 2999 self.id_to_index_mapper_stack.last_mut().unwrap().add_spatial_node(new_node_id, node_index); 3000 node_index 3001 } 3002 3003 pub fn push_shadow( 3004 &mut self, 3005 shadow: Shadow, 3006 spatial_node_index: SpatialNodeIndex, 3007 clip_chain_id: api::ClipChainId, 3008 should_inflate: bool, 3009 ) { 3010 self.clip_tree_builder.push_clip_chain(Some(clip_chain_id), false, false); 3011 3012 // Store this shadow in the pending list, for processing 3013 // during pop_all_shadows. 3014 self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow { 3015 shadow, 3016 spatial_node_index, 3017 should_inflate, 3018 })); 3019 } 3020 3021 pub fn pop_all_shadows( 3022 &mut self, 3023 ) { 3024 assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present"); 3025 3026 let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new()); 3027 3028 // 3029 // The pending_shadow_items queue contains a list of shadows and primitives 3030 // that were pushed during the active shadow context. To process these, we: 3031 // 3032 // Iterate the list, popping an item from the front each iteration. 3033 // 3034 // If the item is a shadow: 3035 // - Create a shadow picture primitive. 3036 // - Add *any* primitives that remain in the item list to this shadow. 3037 // If the item is a primitive: 3038 // - Add that primitive as a normal item (if alpha > 0) 3039 // 3040 3041 while let Some(item) = items.pop_front() { 3042 match item { 3043 ShadowItem::Shadow(pending_shadow) => { 3044 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur 3045 // "the image that would be generated by applying to the shadow a 3046 // Gaussian blur with a standard deviation equal to half the blur radius." 3047 let std_deviation = pending_shadow.shadow.blur_radius * 0.5; 3048 3049 // Add any primitives that come after this shadow in the item 3050 // list to this shadow. 3051 let mut prim_list = PrimitiveList::empty(); 3052 let blur_filter = Filter::Blur { 3053 width: std_deviation, 3054 height: std_deviation, 3055 should_inflate: pending_shadow.should_inflate, 3056 edge_mode: BlurEdgeMode::Duplicate, 3057 }; 3058 let blur_is_noop = blur_filter.is_noop(); 3059 3060 for item in &items { 3061 let (instance, info, spatial_node_index) = match item { 3062 ShadowItem::Image(ref pending_image) => { 3063 self.create_shadow_prim( 3064 &pending_shadow, 3065 pending_image, 3066 blur_is_noop, 3067 ) 3068 } 3069 ShadowItem::LineDecoration(ref pending_line_dec) => { 3070 self.create_shadow_prim( 3071 &pending_shadow, 3072 pending_line_dec, 3073 blur_is_noop, 3074 ) 3075 } 3076 ShadowItem::NormalBorder(ref pending_border) => { 3077 self.create_shadow_prim( 3078 &pending_shadow, 3079 pending_border, 3080 blur_is_noop, 3081 ) 3082 } 3083 ShadowItem::Primitive(ref pending_primitive) => { 3084 self.create_shadow_prim( 3085 &pending_shadow, 3086 pending_primitive, 3087 blur_is_noop, 3088 ) 3089 } 3090 ShadowItem::TextRun(ref pending_text_run) => { 3091 self.create_shadow_prim( 3092 &pending_shadow, 3093 pending_text_run, 3094 blur_is_noop, 3095 ) 3096 } 3097 _ => { 3098 continue; 3099 } 3100 }; 3101 3102 if blur_is_noop { 3103 self.add_primitive_to_draw_list( 3104 instance, 3105 info.rect, 3106 spatial_node_index, 3107 info.flags, 3108 ); 3109 } else { 3110 prim_list.add_prim( 3111 instance, 3112 info.rect, 3113 spatial_node_index, 3114 info.flags, 3115 &mut self.prim_instances, 3116 &self.clip_tree_builder, 3117 ); 3118 } 3119 } 3120 3121 // No point in adding a shadow here if there were no primitives 3122 // added to the shadow. 3123 if !prim_list.is_empty() { 3124 // Create a picture that the shadow primitives will be added to. If the 3125 // blur radius is 0, the code in Picture::prepare_for_render will 3126 // detect this and mark the picture to be drawn directly into the 3127 // parent picture, which avoids an intermediate surface and blur. 3128 assert!(!blur_filter.is_noop()); 3129 let composite_mode = Some(PictureCompositeMode::Filter(blur_filter)); 3130 let composite_mode_key = composite_mode.clone().into(); 3131 let raster_space = RasterSpace::Screen; 3132 3133 // Create the primitive to draw the shadow picture into the scene. 3134 let shadow_pic_index = PictureIndex(self.prim_store.pictures 3135 .alloc() 3136 .init(PicturePrimitive::new_image( 3137 composite_mode, 3138 Picture3DContext::Out, 3139 PrimitiveFlags::IS_BACKFACE_VISIBLE, 3140 prim_list, 3141 pending_shadow.spatial_node_index, 3142 raster_space, 3143 PictureFlags::empty(), 3144 None, 3145 )) 3146 ); 3147 3148 let shadow_pic_key = PictureKey::new( 3149 Picture { composite_mode_key, raster_space }, 3150 ); 3151 3152 let shadow_prim_data_handle = self.interners 3153 .picture 3154 .intern(&shadow_pic_key, || ()); 3155 3156 let clip_node_id = self.clip_tree_builder.build_clip_set(api::ClipChainId::INVALID); 3157 3158 let shadow_prim_instance = PrimitiveInstance::new( 3159 PrimitiveInstanceKind::Picture { 3160 data_handle: shadow_prim_data_handle, 3161 pic_index: shadow_pic_index, 3162 }, 3163 self.clip_tree_builder.build_for_picture(clip_node_id), 3164 ); 3165 3166 // Add the shadow primitive. This must be done before pushing this 3167 // picture on to the shadow stack, to avoid infinite recursion! 3168 self.add_primitive_to_draw_list( 3169 shadow_prim_instance, 3170 LayoutRect::zero(), 3171 pending_shadow.spatial_node_index, 3172 PrimitiveFlags::IS_BACKFACE_VISIBLE, 3173 ); 3174 } 3175 3176 self.clip_tree_builder.pop_clip(); 3177 } 3178 ShadowItem::Image(pending_image) => { 3179 self.add_shadow_prim_to_draw_list( 3180 pending_image, 3181 ) 3182 }, 3183 ShadowItem::LineDecoration(pending_line_dec) => { 3184 self.add_shadow_prim_to_draw_list( 3185 pending_line_dec, 3186 ) 3187 }, 3188 ShadowItem::NormalBorder(pending_border) => { 3189 self.add_shadow_prim_to_draw_list( 3190 pending_border, 3191 ) 3192 }, 3193 ShadowItem::Primitive(pending_primitive) => { 3194 self.add_shadow_prim_to_draw_list( 3195 pending_primitive, 3196 ) 3197 }, 3198 ShadowItem::TextRun(pending_text_run) => { 3199 self.add_shadow_prim_to_draw_list( 3200 pending_text_run, 3201 ) 3202 }, 3203 } 3204 } 3205 3206 debug_assert!(items.is_empty()); 3207 self.pending_shadow_items = items; 3208 } 3209 3210 fn create_shadow_prim<P>( 3211 &mut self, 3212 pending_shadow: &PendingShadow, 3213 pending_primitive: &PendingPrimitive<P>, 3214 blur_is_noop: bool, 3215 ) -> (PrimitiveInstance, LayoutPrimitiveInfo, SpatialNodeIndex) 3216 where 3217 P: InternablePrimitive + CreateShadow, 3218 Interners: AsMut<Interner<P>>, 3219 { 3220 // Offset the local rect and clip rect by the shadow offset. The pending 3221 // primitive has already been snapped, but we will need to snap the 3222 // shadow after translation. We don't need to worry about the size 3223 // changing because the shadow has the same raster space as the 3224 // primitive, and thus we know the size is already rounded. 3225 let mut info = pending_primitive.info.clone(); 3226 info.rect = info.rect.translate(pending_shadow.shadow.offset); 3227 info.clip_rect = info.clip_rect.translate(pending_shadow.shadow.offset); 3228 3229 let clip_set = self.clip_tree_builder.build_for_prim( 3230 pending_primitive.clip_node_id, 3231 &info, 3232 &[], 3233 &mut self.interners, 3234 ); 3235 3236 // Construct and add a primitive for the given shadow. 3237 let shadow_prim_instance = self.create_primitive( 3238 &info, 3239 clip_set, 3240 pending_primitive.prim.create_shadow( 3241 &pending_shadow.shadow, 3242 blur_is_noop, 3243 self.raster_space_stack.last().cloned().unwrap(), 3244 ), 3245 ); 3246 3247 (shadow_prim_instance, info, pending_primitive.spatial_node_index) 3248 } 3249 3250 fn add_shadow_prim_to_draw_list<P>( 3251 &mut self, 3252 pending_primitive: PendingPrimitive<P>, 3253 ) where 3254 P: InternablePrimitive + IsVisible, 3255 Interners: AsMut<Interner<P>>, 3256 { 3257 // For a normal primitive, if it has alpha > 0, then we add this 3258 // as a normal primitive to the parent picture. 3259 if pending_primitive.prim.is_visible() { 3260 let clip_set = self.clip_tree_builder.build_for_prim( 3261 pending_primitive.clip_node_id, 3262 &pending_primitive.info, 3263 &[], 3264 &mut self.interners, 3265 ); 3266 3267 self.add_prim_to_draw_list( 3268 &pending_primitive.info, 3269 pending_primitive.spatial_node_index, 3270 clip_set, 3271 pending_primitive.prim, 3272 ); 3273 } 3274 } 3275 3276 pub fn add_line( 3277 &mut self, 3278 spatial_node_index: SpatialNodeIndex, 3279 clip_node_id: ClipNodeId, 3280 info: &LayoutPrimitiveInfo, 3281 wavy_line_thickness: f32, 3282 orientation: LineOrientation, 3283 color: ColorF, 3284 style: LineStyle, 3285 ) { 3286 // For line decorations, we can construct the render task cache key 3287 // here during scene building, since it doesn't depend on device 3288 // pixel ratio or transform. 3289 let size = get_line_decoration_size( 3290 &info.rect.size(), 3291 orientation, 3292 style, 3293 wavy_line_thickness, 3294 ); 3295 3296 let cache_key = size.map(|size| { 3297 LineDecorationCacheKey { 3298 style, 3299 orientation, 3300 wavy_line_thickness: Au::from_f32_px(wavy_line_thickness), 3301 size: size.to_au(), 3302 } 3303 }); 3304 3305 self.add_primitive( 3306 spatial_node_index, 3307 clip_node_id, 3308 &info, 3309 Vec::new(), 3310 LineDecoration { 3311 cache_key, 3312 color: color.into(), 3313 }, 3314 ); 3315 } 3316 3317 pub fn add_border( 3318 &mut self, 3319 spatial_node_index: SpatialNodeIndex, 3320 clip_node_id: ClipNodeId, 3321 info: &LayoutPrimitiveInfo, 3322 border_item: &BorderDisplayItem, 3323 gradient_stops: ItemRange<GradientStop>, 3324 ) { 3325 match border_item.details { 3326 BorderDetails::NinePatch(ref border) => { 3327 let nine_patch = NinePatchDescriptor { 3328 width: border.width, 3329 height: border.height, 3330 slice: border.slice, 3331 fill: border.fill, 3332 repeat_horizontal: border.repeat_horizontal, 3333 repeat_vertical: border.repeat_vertical, 3334 widths: border_item.widths.into(), 3335 }; 3336 3337 match border.source { 3338 NinePatchBorderSource::Image(key, rendering) => { 3339 let prim = ImageBorder { 3340 request: ImageRequest { 3341 key, 3342 rendering, 3343 tile: None, 3344 }, 3345 nine_patch, 3346 }; 3347 3348 self.add_nonshadowable_primitive( 3349 spatial_node_index, 3350 clip_node_id, 3351 info, 3352 Vec::new(), 3353 prim, 3354 ); 3355 } 3356 NinePatchBorderSource::Gradient(gradient) => { 3357 let prim = match self.create_linear_gradient_prim( 3358 &info, 3359 gradient.start_point, 3360 gradient.end_point, 3361 read_gradient_stops(gradient_stops), 3362 gradient.extend_mode, 3363 LayoutSize::new(border.height as f32, border.width as f32), 3364 LayoutSize::zero(), 3365 Some(Box::new(nine_patch)), 3366 EdgeAaSegmentMask::all(), 3367 ) { 3368 Some(prim) => prim, 3369 None => return, 3370 }; 3371 3372 self.add_nonshadowable_primitive( 3373 spatial_node_index, 3374 clip_node_id, 3375 info, 3376 Vec::new(), 3377 prim, 3378 ); 3379 } 3380 NinePatchBorderSource::RadialGradient(gradient) => { 3381 let prim = self.create_radial_gradient_prim( 3382 &info, 3383 gradient.center, 3384 gradient.start_offset * gradient.radius.width, 3385 gradient.end_offset * gradient.radius.width, 3386 gradient.radius.width / gradient.radius.height, 3387 read_gradient_stops(gradient_stops), 3388 gradient.extend_mode, 3389 LayoutSize::new(border.height as f32, border.width as f32), 3390 LayoutSize::zero(), 3391 Some(Box::new(nine_patch)), 3392 ); 3393 3394 self.add_nonshadowable_primitive( 3395 spatial_node_index, 3396 clip_node_id, 3397 info, 3398 Vec::new(), 3399 prim, 3400 ); 3401 } 3402 NinePatchBorderSource::ConicGradient(gradient) => { 3403 let prim = self.create_conic_gradient_prim( 3404 &info, 3405 gradient.center, 3406 gradient.angle, 3407 gradient.start_offset, 3408 gradient.end_offset, 3409 gradient_stops, 3410 gradient.extend_mode, 3411 LayoutSize::new(border.height as f32, border.width as f32), 3412 LayoutSize::zero(), 3413 Some(Box::new(nine_patch)), 3414 ); 3415 3416 self.add_nonshadowable_primitive( 3417 spatial_node_index, 3418 clip_node_id, 3419 info, 3420 Vec::new(), 3421 prim, 3422 ); 3423 } 3424 }; 3425 } 3426 BorderDetails::Normal(ref border) => { 3427 self.add_normal_border( 3428 info, 3429 border, 3430 border_item.widths, 3431 spatial_node_index, 3432 clip_node_id, 3433 ); 3434 } 3435 } 3436 } 3437 3438 pub fn create_linear_gradient_prim( 3439 &mut self, 3440 info: &LayoutPrimitiveInfo, 3441 start_point: LayoutPoint, 3442 end_point: LayoutPoint, 3443 stops: Vec<GradientStopKey>, 3444 extend_mode: ExtendMode, 3445 stretch_size: LayoutSize, 3446 mut tile_spacing: LayoutSize, 3447 nine_patch: Option<Box<NinePatchDescriptor>>, 3448 edge_aa_mask: EdgeAaSegmentMask, 3449 ) -> Option<LinearGradient> { 3450 let mut prim_rect = info.rect; 3451 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); 3452 3453 let mut has_hard_stops = false; 3454 let mut is_entirely_transparent = true; 3455 let mut prev_stop = None; 3456 for stop in &stops { 3457 if Some(stop.offset) == prev_stop { 3458 has_hard_stops = true; 3459 } 3460 prev_stop = Some(stop.offset); 3461 if stop.color.a > 0 { 3462 is_entirely_transparent = false; 3463 } 3464 } 3465 3466 // If all the stops have no alpha, then this 3467 // gradient can't contribute to the scene. 3468 if is_entirely_transparent { 3469 return None; 3470 } 3471 3472 // Try to ensure that if the gradient is specified in reverse, then so long as the stops 3473 // are also supplied in reverse that the rendered result will be equivalent. To do this, 3474 // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so 3475 // just designate the reference orientation as start < end. Aligned gradient rendering 3476 // manages to produce the same result regardless of orientation, so don't worry about 3477 // reversing in that case. 3478 let reverse_stops = start_point.x > end_point.x || 3479 (start_point.x == end_point.x && start_point.y > end_point.y); 3480 3481 // To get reftests exactly matching with reverse start/end 3482 // points, it's necessary to reverse the gradient 3483 // line in some cases. 3484 let (sp, ep) = if reverse_stops { 3485 (end_point, start_point) 3486 } else { 3487 (start_point, end_point) 3488 }; 3489 3490 // We set a limit to the resolution at which cached gradients are rendered. 3491 // For most gradients this is fine but when there are hard stops this causes 3492 // noticeable artifacts. If so, fall back to non-cached gradients. 3493 let max = gradient::LINEAR_MAX_CACHED_SIZE; 3494 let caching_causes_artifacts = has_hard_stops && (stretch_size.width > max || stretch_size.height > max); 3495 3496 let is_tiled = prim_rect.width() > stretch_size.width 3497 || prim_rect.height() > stretch_size.height; 3498 // SWGL has a fast-path that can render gradients faster than it can sample from the 3499 // texture cache so we disable caching in this configuration. Cached gradients are 3500 // faster on hardware. 3501 let cached = (!self.config.is_software || is_tiled) && !caching_causes_artifacts; 3502 3503 Some(LinearGradient { 3504 extend_mode, 3505 start_point: sp.into(), 3506 end_point: ep.into(), 3507 stretch_size: stretch_size.into(), 3508 tile_spacing: tile_spacing.into(), 3509 stops, 3510 reverse_stops, 3511 nine_patch, 3512 cached, 3513 edge_aa_mask, 3514 enable_dithering: self.config.enable_dithering, 3515 }) 3516 } 3517 3518 pub fn create_radial_gradient_prim( 3519 &mut self, 3520 info: &LayoutPrimitiveInfo, 3521 center: LayoutPoint, 3522 start_radius: f32, 3523 end_radius: f32, 3524 ratio_xy: f32, 3525 stops: Vec<GradientStopKey>, 3526 extend_mode: ExtendMode, 3527 stretch_size: LayoutSize, 3528 mut tile_spacing: LayoutSize, 3529 nine_patch: Option<Box<NinePatchDescriptor>>, 3530 ) -> RadialGradient { 3531 let mut prim_rect = info.rect; 3532 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); 3533 3534 let params = RadialGradientParams { 3535 start_radius, 3536 end_radius, 3537 ratio_xy, 3538 }; 3539 3540 RadialGradient { 3541 extend_mode, 3542 center: center.into(), 3543 params, 3544 stretch_size: stretch_size.into(), 3545 tile_spacing: tile_spacing.into(), 3546 nine_patch, 3547 stops, 3548 } 3549 } 3550 3551 pub fn create_conic_gradient_prim( 3552 &mut self, 3553 info: &LayoutPrimitiveInfo, 3554 center: LayoutPoint, 3555 angle: f32, 3556 start_offset: f32, 3557 end_offset: f32, 3558 stops: ItemRange<GradientStop>, 3559 extend_mode: ExtendMode, 3560 stretch_size: LayoutSize, 3561 mut tile_spacing: LayoutSize, 3562 nine_patch: Option<Box<NinePatchDescriptor>>, 3563 ) -> ConicGradient { 3564 let mut prim_rect = info.rect; 3565 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); 3566 3567 let stops = stops.iter().map(|stop| { 3568 GradientStopKey { 3569 offset: stop.offset, 3570 color: stop.color.into(), 3571 } 3572 }).collect(); 3573 3574 ConicGradient { 3575 extend_mode, 3576 center: center.into(), 3577 params: ConicGradientParams { angle, start_offset, end_offset }, 3578 stretch_size: stretch_size.into(), 3579 tile_spacing: tile_spacing.into(), 3580 nine_patch, 3581 stops, 3582 } 3583 } 3584 3585 pub fn add_text( 3586 &mut self, 3587 spatial_node_index: SpatialNodeIndex, 3588 clip_node_id: ClipNodeId, 3589 prim_info: &LayoutPrimitiveInfo, 3590 font_instance_key: &FontInstanceKey, 3591 text_color: &ColorF, 3592 glyph_range: ItemRange<GlyphInstance>, 3593 glyph_options: Option<GlyphOptions>, 3594 ref_frame_offset: LayoutVector2D, 3595 ) { 3596 let offset = self.current_external_scroll_offset(spatial_node_index) + ref_frame_offset; 3597 3598 let text_run = { 3599 let shared_key = self.fonts.instance_keys.map_key(font_instance_key); 3600 let font_instance = match self.fonts.instances.get_font_instance(shared_key) { 3601 Some(instance) => instance, 3602 None => { 3603 warn!("Unknown font instance key"); 3604 debug!("key={:?} shared={:?}", font_instance_key, shared_key); 3605 return; 3606 } 3607 }; 3608 3609 // Trivial early out checks 3610 if font_instance.size <= FontSize::zero() { 3611 return; 3612 } 3613 3614 // TODO(gw): Use a proper algorithm to select 3615 // whether this item should be rendered with 3616 // subpixel AA! 3617 let mut render_mode = self.config 3618 .default_font_render_mode 3619 .limit_by(font_instance.render_mode); 3620 let mut flags = font_instance.flags; 3621 if let Some(options) = glyph_options { 3622 render_mode = render_mode.limit_by(options.render_mode); 3623 flags |= options.flags; 3624 } 3625 3626 let font = FontInstance::new( 3627 font_instance, 3628 (*text_color).into(), 3629 render_mode, 3630 flags, 3631 ); 3632 3633 // TODO(gw): It'd be nice not to have to allocate here for creating 3634 // the primitive key, when the common case is that the 3635 // hash will match and we won't end up creating a new 3636 // primitive template. 3637 let prim_offset = prim_info.rect.min.to_vector() - offset; 3638 let glyphs = glyph_range 3639 .iter() 3640 .map(|glyph| { 3641 GlyphInstance { 3642 index: glyph.index, 3643 point: glyph.point - prim_offset, 3644 } 3645 }) 3646 .collect(); 3647 3648 // Query the current requested raster space (stack handled by push/pop 3649 // stacking context). 3650 let requested_raster_space = self.raster_space_stack 3651 .last() 3652 .cloned() 3653 .unwrap(); 3654 3655 TextRun { 3656 glyphs, 3657 font, 3658 shadow: false, 3659 requested_raster_space, 3660 reference_frame_offset: ref_frame_offset, 3661 } 3662 }; 3663 3664 self.add_primitive( 3665 spatial_node_index, 3666 clip_node_id, 3667 prim_info, 3668 Vec::new(), 3669 text_run, 3670 ); 3671 } 3672 3673 pub fn add_image( 3674 &mut self, 3675 spatial_node_index: SpatialNodeIndex, 3676 clip_node_id: ClipNodeId, 3677 info: &LayoutPrimitiveInfo, 3678 stretch_size: LayoutSize, 3679 mut tile_spacing: LayoutSize, 3680 image_key: ImageKey, 3681 image_rendering: ImageRendering, 3682 alpha_type: AlphaType, 3683 color: ColorF, 3684 ) { 3685 let mut prim_rect = info.rect; 3686 simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); 3687 let info = LayoutPrimitiveInfo { 3688 rect: prim_rect, 3689 .. *info 3690 }; 3691 3692 self.add_primitive( 3693 spatial_node_index, 3694 clip_node_id, 3695 &info, 3696 Vec::new(), 3697 Image { 3698 key: image_key, 3699 tile_spacing: tile_spacing.into(), 3700 stretch_size: stretch_size.into(), 3701 color: color.into(), 3702 image_rendering, 3703 alpha_type, 3704 }, 3705 ); 3706 } 3707 3708 pub fn add_yuv_image( 3709 &mut self, 3710 spatial_node_index: SpatialNodeIndex, 3711 clip_node_id: ClipNodeId, 3712 info: &LayoutPrimitiveInfo, 3713 yuv_data: YuvData, 3714 color_depth: ColorDepth, 3715 color_space: YuvColorSpace, 3716 color_range: ColorRange, 3717 image_rendering: ImageRendering, 3718 ) { 3719 let format = yuv_data.get_format(); 3720 let yuv_key = match yuv_data { 3721 YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY], 3722 YuvData::P010(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY], 3723 YuvData::NV16(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY], 3724 YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2], 3725 YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY], 3726 }; 3727 3728 self.add_nonshadowable_primitive( 3729 spatial_node_index, 3730 clip_node_id, 3731 info, 3732 Vec::new(), 3733 YuvImage { 3734 color_depth, 3735 yuv_key, 3736 format, 3737 color_space, 3738 color_range, 3739 image_rendering, 3740 }, 3741 ); 3742 } 3743 3744 fn add_primitive_instance_to_3d_root( 3745 &mut self, 3746 prim: ExtendedPrimitiveInstance, 3747 ) { 3748 // find the 3D root and append to the children list 3749 for sc in self.sc_stack.iter_mut().rev() { 3750 match sc.context_3d { 3751 Picture3DContext::In { root_data: Some(ref mut prims), .. } => { 3752 prims.push(prim); 3753 break; 3754 } 3755 Picture3DContext::In { .. } => {} 3756 Picture3DContext::Out => panic!("Unable to find 3D root"), 3757 } 3758 } 3759 } 3760 3761 #[allow(dead_code)] 3762 pub fn add_backdrop_filter( 3763 &mut self, 3764 spatial_node_index: SpatialNodeIndex, 3765 clip_node_id: ClipNodeId, 3766 info: &LayoutPrimitiveInfo, 3767 filters: Vec<Filter>, 3768 filter_datas: Vec<FilterData>, 3769 ) { 3770 // We don't know the spatial node for a backdrop filter, as it's whatever is the 3771 // backdrop root, but we can't know this if the root is a picture cache slice 3772 // (which is the common case). It will get resolved later during `finalize_picture`. 3773 let filter_spatial_node_index = SpatialNodeIndex::UNKNOWN; 3774 3775 self.make_current_slice_atomic_if_required(); 3776 3777 // Ensure we create a clip-chain for the capture primitive that matches 3778 // the render primitive, otherwise one might get culled while the other 3779 // is considered visible. 3780 let clip_leaf_id = self.clip_tree_builder.build_for_prim( 3781 clip_node_id, 3782 info, 3783 &[], 3784 &mut self.interners, 3785 ); 3786 3787 // Create the backdrop prim - this is a placeholder which sets the size of resolve 3788 // picture that reads from the backdrop root 3789 let backdrop_capture_instance = self.create_primitive( 3790 info, 3791 clip_leaf_id, 3792 BackdropCapture { 3793 }, 3794 ); 3795 3796 // Create a prim_list for this backdrop prim and add to a picture chain builder, which 3797 // is needed for the call to `wrap_prim_with_filters` below 3798 let mut prim_list = PrimitiveList::empty(); 3799 prim_list.add_prim( 3800 backdrop_capture_instance, 3801 info.rect, 3802 spatial_node_index, 3803 info.flags, 3804 &mut self.prim_instances, 3805 &self.clip_tree_builder, 3806 ); 3807 3808 let mut source = PictureChainBuilder::from_prim_list( 3809 prim_list, 3810 info.flags, 3811 filter_spatial_node_index, 3812 RasterSpace::Screen, 3813 true, 3814 ); 3815 3816 // Wrap the backdrop primitive picture with the filters that were specified. This 3817 // produces a picture chain with 1+ pictures with the filter composite modes set. 3818 source = self.wrap_prim_with_filters( 3819 source, 3820 clip_node_id, 3821 filters, 3822 filter_datas, 3823 true, 3824 LayoutVector2D::zero(), 3825 ); 3826 3827 // If all the filters were no-ops (e.g. opacity(0)) then we don't get a picture here 3828 // and we can skip adding the backdrop-filter. 3829 if source.has_picture() { 3830 source = source.add_picture( 3831 PictureCompositeMode::IntermediateSurface, 3832 clip_node_id, 3833 Picture3DContext::Out, 3834 &mut self.interners, 3835 &mut self.prim_store, 3836 &mut self.prim_instances, 3837 &mut self.clip_tree_builder, 3838 ); 3839 3840 let filtered_instance = source.finalize( 3841 clip_node_id, 3842 &mut self.interners, 3843 &mut self.prim_store, 3844 &mut self.clip_tree_builder, 3845 None, 3846 ); 3847 3848 // Extract the pic index for the intermediate surface. We need to 3849 // supply this to the capture prim below. 3850 let output_pic_index = match filtered_instance.kind { 3851 PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index, 3852 _ => panic!("bug: not a picture"), 3853 }; 3854 3855 // Find which stacking context (or root tile cache) to add the 3856 // backdrop-filter chain to 3857 let sc_index = self.sc_stack.iter().rposition(|sc| { 3858 !sc.flags.contains(StackingContextFlags::WRAPS_BACKDROP_FILTER) 3859 }); 3860 3861 match sc_index { 3862 Some(sc_index) => { 3863 self.sc_stack[sc_index].prim_list.add_prim( 3864 filtered_instance, 3865 info.rect, 3866 filter_spatial_node_index, 3867 info.flags, 3868 &mut self.prim_instances, 3869 &self.clip_tree_builder, 3870 ); 3871 } 3872 None => { 3873 self.tile_cache_builder.add_prim( 3874 filtered_instance, 3875 info.rect, 3876 filter_spatial_node_index, 3877 info.flags, 3878 self.spatial_tree, 3879 self.interners, 3880 &self.quality_settings, 3881 &mut self.prim_instances, 3882 &self.clip_tree_builder, 3883 ); 3884 } 3885 } 3886 3887 // Add the prim that renders the result of the backdrop filter chain 3888 let mut backdrop_render_instance = self.create_primitive( 3889 info, 3890 clip_leaf_id, 3891 BackdropRender { 3892 }, 3893 ); 3894 3895 // Set up the picture index for the backdrop-filter output in the prim 3896 // that will draw it 3897 match backdrop_render_instance.kind { 3898 PrimitiveInstanceKind::BackdropRender { ref mut pic_index, .. } => { 3899 assert_eq!(*pic_index, PictureIndex::INVALID); 3900 *pic_index = output_pic_index; 3901 } 3902 _ => panic!("bug: unexpected prim kind"), 3903 } 3904 3905 self.add_primitive_to_draw_list( 3906 backdrop_render_instance, 3907 info.rect, 3908 spatial_node_index, 3909 info.flags, 3910 ); 3911 } 3912 } 3913 3914 #[must_use] 3915 fn wrap_prim_with_filters( 3916 &mut self, 3917 mut source: PictureChainBuilder, 3918 clip_node_id: ClipNodeId, 3919 mut filter_ops: Vec<Filter>, 3920 filter_datas: Vec<FilterData>, 3921 is_backdrop_filter: bool, 3922 context_offset: LayoutVector2D, 3923 ) -> PictureChainBuilder { 3924 // For each filter, create a new image with that composite mode. 3925 let mut current_filter_data_index = 0; 3926 // Check if the filter chain is actually an SVGFE filter graph DAG 3927 // 3928 // TODO: We technically could translate all CSS filters to SVGFE here if 3929 // we want to reduce redundant code. 3930 if let Some(Filter::SVGGraphNode(..)) = filter_ops.first() { 3931 // The interesting parts of the handling of SVG filters are: 3932 // * scene_building.rs : wrap_prim_with_filters (you are here) 3933 // * picture.rs : get_coverage_svgfe 3934 // * render_task.rs : new_svg_filter_graph 3935 // * render_target.rs : add_svg_filter_node_instances 3936 3937 // The SVG spec allows us to drop the entire filter graph if it is 3938 // unreasonable, so we limit the number of filters in a graph 3939 const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX; 3940 // Easily tunable for debugging proper handling of inflated rects, 3941 // this should normally be 1 3942 const SVGFE_INFLATE: i16 = 1; 3943 3944 // Validate inputs to all filters. 3945 // 3946 // Several assumptions can be made about the DAG: 3947 // * All filters take a specific number of inputs (feMerge is not 3948 // supported, the code that built the display items had to convert 3949 // any feMerge ops to SVGFECompositeOver already). 3950 // * All input buffer ids are < the output buffer id of the node. 3951 // * If SourceGraphic or SourceAlpha are used, they are standalone 3952 // nodes with no inputs. 3953 // * Whenever subregion of a node is smaller than the subregion 3954 // of the inputs, it is a deliberate clip of those inputs to the 3955 // new rect, this can occur before/after blur and dropshadow for 3956 // example, so we must explicitly handle subregion correctly, but 3957 // we do not have to allocate the unused pixels as the transparent 3958 // black has no efect on any of the filters, only certain filters 3959 // like feFlood can generate something from nothing. 3960 // * Coordinate basis of the graph has to be adjusted by 3961 // context_offset to put the subregions in the same space that the 3962 // primitives are in, as they do that offset as well. 3963 let mut reference_for_buffer_id: [FilterGraphPictureReference; BUFFER_LIMIT] = [ 3964 FilterGraphPictureReference{ 3965 // This value is deliberately invalid, but not a magic 3966 // number, it's just this way to guarantee an assertion 3967 // failure if something goes wrong. 3968 buffer_id: FilterOpGraphPictureBufferId::BufferId(-1), 3969 subregion: LayoutRect::zero(), // Always overridden 3970 offset: LayoutVector2D::zero(), 3971 inflate: 0, 3972 source_padding: LayoutRect::zero(), 3973 target_padding: LayoutRect::zero(), 3974 }; BUFFER_LIMIT]; 3975 let mut filters: Vec<(FilterGraphNode, FilterGraphOp)> = Vec::new(); 3976 filters.reserve(BUFFER_LIMIT); 3977 for (original_id, parsefilter) in filter_ops.iter().enumerate() { 3978 if filters.len() >= BUFFER_LIMIT { 3979 // If the DAG is too large to process, the spec requires 3980 // that we drop all filters and display source image as-is. 3981 return source; 3982 } 3983 3984 let newfilter = match parsefilter { 3985 Filter::SVGGraphNode(parsenode, op) => { 3986 // We need to offset the subregion by the stacking context 3987 // offset or we'd be in the wrong coordinate system, prims 3988 // are already offset by this same amount. 3989 let clip_region = parsenode.subregion 3990 .translate(context_offset); 3991 3992 let mut newnode = FilterGraphNode { 3993 kept_by_optimizer: false, 3994 linear: parsenode.linear, 3995 inflate: SVGFE_INFLATE, 3996 inputs: Vec::new(), 3997 subregion: clip_region, 3998 }; 3999 4000 // Initialize remapped versions of the inputs, this is 4001 // done here to share code between the enum variants. 4002 let mut remapped_inputs: Vec<FilterGraphPictureReference> = Vec::new(); 4003 remapped_inputs.reserve_exact(parsenode.inputs.len()); 4004 for input in &parsenode.inputs { 4005 match input.buffer_id { 4006 FilterOpGraphPictureBufferId::BufferId(buffer_id) => { 4007 // Reference to earlier node output, if this 4008 // is None, it's a bug 4009 let pic = *reference_for_buffer_id 4010 .get(buffer_id as usize) 4011 .expect("BufferId not valid?"); 4012 // We have to adjust the subregion and 4013 // padding based on the input offset for 4014 // feOffset ops, the padding may be inflated 4015 // further by other ops such as blurs below. 4016 let offset = input.offset; 4017 let subregion = pic.subregion 4018 .translate(offset); 4019 let source_padding = LayoutRect::zero() 4020 .translate(-offset); 4021 let target_padding = LayoutRect::zero() 4022 .translate(offset); 4023 remapped_inputs.push( 4024 FilterGraphPictureReference { 4025 buffer_id: pic.buffer_id, 4026 subregion, 4027 offset, 4028 inflate: pic.inflate, 4029 source_padding, 4030 target_padding, 4031 }); 4032 } 4033 FilterOpGraphPictureBufferId::None => panic!("Unsupported FilterOpGraphPictureBufferId"), 4034 } 4035 } 4036 4037 fn union_unchecked(a: LayoutRect, b: LayoutRect) -> LayoutRect { 4038 let mut r = a; 4039 if r.min.x > b.min.x {r.min.x = b.min.x} 4040 if r.min.y > b.min.y {r.min.y = b.min.y} 4041 if r.max.x < b.max.x {r.max.x = b.max.x} 4042 if r.max.y < b.max.y {r.max.y = b.max.y} 4043 r 4044 } 4045 4046 match op { 4047 FilterGraphOp::SVGFEFlood{..} | 4048 FilterGraphOp::SVGFESourceAlpha | 4049 FilterGraphOp::SVGFESourceGraphic | 4050 FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} | 4051 FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} | 4052 FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} | 4053 FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => { 4054 assert!(remapped_inputs.len() == 0); 4055 (newnode.clone(), op.clone()) 4056 } 4057 FilterGraphOp::SVGFEColorMatrix{..} | 4058 FilterGraphOp::SVGFEIdentity | 4059 FilterGraphOp::SVGFEImage{..} | 4060 FilterGraphOp::SVGFEOpacity{..} | 4061 FilterGraphOp::SVGFEToAlpha => { 4062 assert!(remapped_inputs.len() == 1); 4063 newnode.inputs = remapped_inputs; 4064 (newnode.clone(), op.clone()) 4065 } 4066 FilterGraphOp::SVGFEComponentTransfer => { 4067 assert!(remapped_inputs.len() == 1); 4068 // Convert to SVGFEComponentTransferInterned 4069 let filter_data = 4070 &filter_datas[current_filter_data_index]; 4071 let filter_data = filter_data.sanitize(); 4072 current_filter_data_index = current_filter_data_index + 1; 4073 4074 // filter data is 4KiB of gamma ramps used 4075 // only by SVGFEComponentTransferWithHandle. 4076 // 4077 // The gamma ramps are interleaved as RGBA32F 4078 // pixels (unlike in regular ComponentTransfer, 4079 // where the values are not interleaved), so 4080 // r_values[3] is the alpha of the first color, 4081 // not the 4th red value. This layout makes the 4082 // shader more compatible with buggy compilers that 4083 // do not like indexing components on a vec4. 4084 // 4085 // If the alpha value of the lowest alpha index 4086 // is more than 0.5/255.0, then the filter 4087 // creates pixels from nothing. 4088 let creates_pixels = 4089 if let Some(a) = filter_data.r_values.get(3) { 4090 *a >= (0.5/255.0) 4091 } else { 4092 false 4093 }; 4094 let filter_data_key = SFilterDataKey { 4095 data: 4096 SFilterData { 4097 r_func: SFilterDataComponent::from_functype_values( 4098 filter_data.func_r_type, &filter_data.r_values), 4099 g_func: SFilterDataComponent::from_functype_values( 4100 filter_data.func_g_type, &filter_data.g_values), 4101 b_func: SFilterDataComponent::from_functype_values( 4102 filter_data.func_b_type, &filter_data.b_values), 4103 a_func: SFilterDataComponent::from_functype_values( 4104 filter_data.func_a_type, &filter_data.a_values), 4105 }, 4106 }; 4107 4108 let handle = self.interners 4109 .filter_data 4110 .intern(&filter_data_key, || ()); 4111 4112 newnode.inputs = remapped_inputs; 4113 (newnode.clone(), FilterGraphOp::SVGFEComponentTransferInterned{handle, creates_pixels}) 4114 } 4115 FilterGraphOp::SVGFEComponentTransferInterned{..} => unreachable!(), 4116 FilterGraphOp::SVGFETile => { 4117 assert!(remapped_inputs.len() == 1); 4118 // feTile usually uses every pixel of input 4119 remapped_inputs[0].source_padding = 4120 LayoutRect::max_rect(); 4121 remapped_inputs[0].target_padding = 4122 LayoutRect::max_rect(); 4123 newnode.inputs = remapped_inputs; 4124 (newnode.clone(), op.clone()) 4125 } 4126 FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{kernel_unit_length_x, kernel_unit_length_y, ..} | 4127 FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{kernel_unit_length_x, kernel_unit_length_y, ..} | 4128 FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{kernel_unit_length_x, kernel_unit_length_y, ..} | 4129 FilterGraphOp::SVGFEMorphologyDilate{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => { 4130 assert!(remapped_inputs.len() == 1); 4131 let padding = LayoutSize::new( 4132 kernel_unit_length_x.ceil(), 4133 kernel_unit_length_y.ceil(), 4134 ); 4135 // Add source padding to represent the kernel pixels 4136 // needed relative to target pixels 4137 remapped_inputs[0].source_padding = 4138 remapped_inputs[0].source_padding 4139 .inflate(padding.width, padding.height); 4140 // Add target padding to represent the area affected 4141 // by a source pixel 4142 remapped_inputs[0].target_padding = 4143 remapped_inputs[0].target_padding 4144 .inflate(padding.width, padding.height); 4145 newnode.inputs = remapped_inputs; 4146 (newnode.clone(), op.clone()) 4147 }, 4148 FilterGraphOp::SVGFEDiffuseLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} | 4149 FilterGraphOp::SVGFEDiffuseLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} | 4150 FilterGraphOp::SVGFEDiffuseLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} | 4151 FilterGraphOp::SVGFESpecularLightingDistant{kernel_unit_length_x, kernel_unit_length_y, ..} | 4152 FilterGraphOp::SVGFESpecularLightingPoint{kernel_unit_length_x, kernel_unit_length_y, ..} | 4153 FilterGraphOp::SVGFESpecularLightingSpot{kernel_unit_length_x, kernel_unit_length_y, ..} | 4154 FilterGraphOp::SVGFEMorphologyErode{radius_x: kernel_unit_length_x, radius_y: kernel_unit_length_y} => { 4155 assert!(remapped_inputs.len() == 1); 4156 let padding = LayoutSize::new( 4157 kernel_unit_length_x.ceil(), 4158 kernel_unit_length_y.ceil(), 4159 ); 4160 // Add source padding to represent the kernel pixels 4161 // needed relative to target pixels 4162 remapped_inputs[0].source_padding = 4163 remapped_inputs[0].source_padding 4164 .inflate(padding.width, padding.height); 4165 // Add target padding to represent the area affected 4166 // by a source pixel 4167 remapped_inputs[0].target_padding = 4168 remapped_inputs[0].target_padding 4169 .inflate(padding.width, padding.height); 4170 newnode.inputs = remapped_inputs; 4171 (newnode.clone(), op.clone()) 4172 }, 4173 FilterGraphOp::SVGFEDisplacementMap { scale, .. } => { 4174 assert!(remapped_inputs.len() == 2); 4175 let padding = LayoutSize::new( 4176 scale.ceil(), 4177 scale.ceil(), 4178 ); 4179 // Add padding to both inputs for source and target 4180 // rects, we might be able to skip some of these, 4181 // but it's not that important to optimize here, a 4182 // loose fit is fine. 4183 remapped_inputs[0].source_padding = 4184 remapped_inputs[0].source_padding 4185 .inflate(padding.width, padding.height); 4186 remapped_inputs[1].source_padding = 4187 remapped_inputs[1].source_padding 4188 .inflate(padding.width, padding.height); 4189 remapped_inputs[0].target_padding = 4190 remapped_inputs[0].target_padding 4191 .inflate(padding.width, padding.height); 4192 remapped_inputs[1].target_padding = 4193 remapped_inputs[1].target_padding 4194 .inflate(padding.width, padding.height); 4195 newnode.inputs = remapped_inputs; 4196 (newnode.clone(), op.clone()) 4197 }, 4198 FilterGraphOp::SVGFEDropShadow{ dx, dy, std_deviation_x, std_deviation_y, .. } => { 4199 assert!(remapped_inputs.len() == 1); 4200 let padding = LayoutSize::new( 4201 std_deviation_x.ceil() * BLUR_SAMPLE_SCALE, 4202 std_deviation_y.ceil() * BLUR_SAMPLE_SCALE, 4203 ); 4204 // Add source padding to represent the shadow 4205 remapped_inputs[0].source_padding = 4206 union_unchecked( 4207 remapped_inputs[0].source_padding, 4208 remapped_inputs[0].source_padding 4209 .inflate(padding.width, padding.height) 4210 .translate( 4211 LayoutVector2D::new(-dx, -dy) 4212 ) 4213 ); 4214 // Add target padding to represent the area needed 4215 // to calculate pixels of the shadow 4216 remapped_inputs[0].target_padding = 4217 union_unchecked( 4218 remapped_inputs[0].target_padding, 4219 remapped_inputs[0].target_padding 4220 .inflate(padding.width, padding.height) 4221 .translate( 4222 LayoutVector2D::new(*dx, *dy) 4223 ) 4224 ); 4225 newnode.inputs = remapped_inputs; 4226 (newnode.clone(), op.clone()) 4227 }, 4228 FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} => { 4229 assert!(remapped_inputs.len() == 1); 4230 let padding = LayoutSize::new( 4231 std_deviation_x.ceil() * BLUR_SAMPLE_SCALE, 4232 std_deviation_y.ceil() * BLUR_SAMPLE_SCALE, 4233 ); 4234 // Add source padding to represent the blur 4235 remapped_inputs[0].source_padding = 4236 remapped_inputs[0].source_padding 4237 .inflate(padding.width, padding.height); 4238 // Add target padding to represent the blur 4239 remapped_inputs[0].target_padding = 4240 remapped_inputs[0].target_padding 4241 .inflate(padding.width, padding.height); 4242 newnode.inputs = remapped_inputs; 4243 (newnode.clone(), op.clone()) 4244 } 4245 FilterGraphOp::SVGFEBlendColor | 4246 FilterGraphOp::SVGFEBlendColorBurn | 4247 FilterGraphOp::SVGFEBlendColorDodge | 4248 FilterGraphOp::SVGFEBlendDarken | 4249 FilterGraphOp::SVGFEBlendDifference | 4250 FilterGraphOp::SVGFEBlendExclusion | 4251 FilterGraphOp::SVGFEBlendHardLight | 4252 FilterGraphOp::SVGFEBlendHue | 4253 FilterGraphOp::SVGFEBlendLighten | 4254 FilterGraphOp::SVGFEBlendLuminosity| 4255 FilterGraphOp::SVGFEBlendMultiply | 4256 FilterGraphOp::SVGFEBlendNormal | 4257 FilterGraphOp::SVGFEBlendOverlay | 4258 FilterGraphOp::SVGFEBlendSaturation | 4259 FilterGraphOp::SVGFEBlendScreen | 4260 FilterGraphOp::SVGFEBlendSoftLight | 4261 FilterGraphOp::SVGFECompositeArithmetic{..} | 4262 FilterGraphOp::SVGFECompositeATop | 4263 FilterGraphOp::SVGFECompositeIn | 4264 FilterGraphOp::SVGFECompositeLighter | 4265 FilterGraphOp::SVGFECompositeOut | 4266 FilterGraphOp::SVGFECompositeOver | 4267 FilterGraphOp::SVGFECompositeXOR => { 4268 assert!(remapped_inputs.len() == 2); 4269 newnode.inputs = remapped_inputs; 4270 (newnode, op.clone()) 4271 } 4272 } 4273 } 4274 Filter::Opacity(valuebinding, value) => { 4275 // Opacity filter is sometimes appended by 4276 // wr_dp_push_stacking_context before we get here, 4277 // convert to SVGFEOpacity in the graph. Note that 4278 // linear is set to false because it has no meaning for 4279 // opacity (which scales all of the RGBA uniformly). 4280 let pic = reference_for_buffer_id[original_id as usize - 1]; 4281 ( 4282 FilterGraphNode { 4283 kept_by_optimizer: false, 4284 linear: false, 4285 inflate: SVGFE_INFLATE, 4286 inputs: [pic].to_vec(), 4287 subregion: pic.subregion, 4288 }, 4289 FilterGraphOp::SVGFEOpacity{ 4290 valuebinding: *valuebinding, 4291 value: *value, 4292 }, 4293 ) 4294 } 4295 _ => { 4296 log!(Level::Warn, "wrap_prim_with_filters: unexpected filter after SVG filters filter[{:?}]={:?}", original_id, parsefilter); 4297 // If we can't figure out how to process the graph, spec 4298 // requires that we drop all filters and display source 4299 // image as-is. 4300 return source; 4301 } 4302 }; 4303 let id = filters.len(); 4304 filters.push(newfilter); 4305 4306 // Set the reference remapping for the last (or only) node 4307 // that we just pushed 4308 reference_for_buffer_id[original_id] = FilterGraphPictureReference { 4309 buffer_id: FilterOpGraphPictureBufferId::BufferId(id as i16), 4310 subregion: filters[id].0.subregion, 4311 offset: LayoutVector2D::zero(), 4312 inflate: filters[id].0.inflate, 4313 source_padding: LayoutRect::zero(), 4314 target_padding: LayoutRect::zero(), 4315 }; 4316 } 4317 4318 if filters.len() >= BUFFER_LIMIT { 4319 // If the DAG is too large to process, the spec requires 4320 // that we drop all filters and display source image as-is. 4321 return source; 4322 } 4323 4324 // Mark used graph nodes, starting at the last graph node, since 4325 // this is a DAG in sorted order we can just iterate backwards and 4326 // know we will find children before parents in order. 4327 // 4328 // Per SVG spec the last node (which is the first we encounter this 4329 // way) is the final output, so its dependencies are what we want to 4330 // mark as kept_by_optimizer 4331 let mut kept_node_by_buffer_id = [false; BUFFER_LIMIT]; 4332 kept_node_by_buffer_id[filters.len() - 1] = true; 4333 for (index, (node, _op)) in filters.iter_mut().enumerate().rev() { 4334 let mut keep = false; 4335 // Check if this node's output was marked to be kept 4336 if let Some(k) = kept_node_by_buffer_id.get(index) { 4337 if *k { 4338 keep = true; 4339 } 4340 } 4341 if keep { 4342 // If this node contributes to the final output we need 4343 // to mark its inputs as also contributing when they are 4344 // encountered later 4345 node.kept_by_optimizer = true; 4346 for input in &node.inputs { 4347 if let FilterOpGraphPictureBufferId::BufferId(id) = input.buffer_id { 4348 if let Some(k) = kept_node_by_buffer_id.get_mut(id as usize) { 4349 *k = true; 4350 } 4351 } 4352 } 4353 } 4354 } 4355 4356 // Validate the DAG nature of the graph - if we find anything wrong 4357 // here it means the above code is bugged. 4358 let mut invalid_dag = false; 4359 for (id, (node, _op)) in filters.iter().enumerate() { 4360 for input in &node.inputs { 4361 if let FilterOpGraphPictureBufferId::BufferId(buffer_id) = input.buffer_id { 4362 if buffer_id < 0 || buffer_id as usize >= id { 4363 invalid_dag = true; 4364 } 4365 } 4366 } 4367 } 4368 4369 if invalid_dag { 4370 log!(Level::Warn, "List of FilterOp::SVGGraphNode filter primitives appears to be invalid!"); 4371 for (id, (node, op)) in filters.iter().enumerate() { 4372 log!(Level::Warn, " node: buffer=BufferId({}) op={} inflate={} subregion {:?} linear={} kept={}", 4373 id, op.kind(), node.inflate, 4374 node.subregion, 4375 node.linear, 4376 node.kept_by_optimizer, 4377 ); 4378 for input in &node.inputs { 4379 log!(Level::Warn, "input: buffer={} inflate={} subregion {:?} offset {:?} target_padding={:?} source_padding={:?}", 4380 match input.buffer_id { 4381 FilterOpGraphPictureBufferId::BufferId(id) => format!("BufferId({})", id), 4382 FilterOpGraphPictureBufferId::None => "None".into(), 4383 }, 4384 input.inflate, 4385 input.subregion, 4386 input.offset, 4387 input.target_padding, 4388 input.source_padding, 4389 ); 4390 } 4391 } 4392 } 4393 if invalid_dag { 4394 // if the DAG is invalid, we can't render it 4395 return source; 4396 } 4397 4398 let composite_mode = PictureCompositeMode::SVGFEGraph( 4399 filters, 4400 ); 4401 4402 source = source.add_picture( 4403 composite_mode, 4404 clip_node_id, 4405 Picture3DContext::Out, 4406 &mut self.interners, 4407 &mut self.prim_store, 4408 &mut self.prim_instances, 4409 &mut self.clip_tree_builder, 4410 ); 4411 4412 return source; 4413 } 4414 4415 // Handle regular CSS filter chains 4416 for filter in &mut filter_ops { 4417 let composite_mode = match filter { 4418 Filter::ComponentTransfer => { 4419 let filter_data = 4420 &filter_datas[current_filter_data_index]; 4421 let filter_data = filter_data.sanitize(); 4422 current_filter_data_index = current_filter_data_index + 1; 4423 if filter_data.is_identity() { 4424 continue 4425 } else { 4426 let filter_data_key = SFilterDataKey { 4427 data: 4428 SFilterData { 4429 r_func: SFilterDataComponent::from_functype_values( 4430 filter_data.func_r_type, &filter_data.r_values), 4431 g_func: SFilterDataComponent::from_functype_values( 4432 filter_data.func_g_type, &filter_data.g_values), 4433 b_func: SFilterDataComponent::from_functype_values( 4434 filter_data.func_b_type, &filter_data.b_values), 4435 a_func: SFilterDataComponent::from_functype_values( 4436 filter_data.func_a_type, &filter_data.a_values), 4437 }, 4438 }; 4439 4440 let handle = self.interners 4441 .filter_data 4442 .intern(&filter_data_key, || ()); 4443 PictureCompositeMode::ComponentTransferFilter(handle) 4444 } 4445 } 4446 Filter::SVGGraphNode(_, _) => { 4447 // SVG filter graphs were handled above 4448 panic!("SVGGraphNode encountered in regular CSS filter chain?"); 4449 } 4450 _ => { 4451 if filter.is_noop() { 4452 continue; 4453 } else { 4454 let mut filter = filter.clone(); 4455 4456 // backdrop-filter spec says that blurs should assume edgeMode=Mirror 4457 // We can do this by not inflating the bounds and setting the edge 4458 // sampling mode to mirror. 4459 if is_backdrop_filter { 4460 if let Filter::Blur { ref mut should_inflate, ref mut edge_mode, .. } = filter { 4461 *should_inflate = false; 4462 *edge_mode = BlurEdgeMode::Mirror; 4463 } 4464 } 4465 4466 PictureCompositeMode::Filter(filter) 4467 } 4468 } 4469 }; 4470 4471 source = source.add_picture( 4472 composite_mode, 4473 clip_node_id, 4474 Picture3DContext::Out, 4475 &mut self.interners, 4476 &mut self.prim_store, 4477 &mut self.prim_instances, 4478 &mut self.clip_tree_builder, 4479 ); 4480 } 4481 4482 source 4483 } 4484 } 4485 4486 4487 pub trait CreateShadow { 4488 fn create_shadow( 4489 &self, 4490 shadow: &Shadow, 4491 blur_is_noop: bool, 4492 current_raster_space: RasterSpace, 4493 ) -> Self; 4494 } 4495 4496 pub trait IsVisible { 4497 fn is_visible(&self) -> bool; 4498 } 4499 4500 /// A primitive instance + some extra information about the primitive. This is 4501 /// stored when constructing 3d rendering contexts, which involve cutting 4502 /// primitive lists. 4503 struct ExtendedPrimitiveInstance { 4504 instance: PrimitiveInstance, 4505 spatial_node_index: SpatialNodeIndex, 4506 flags: PrimitiveFlags, 4507 } 4508 4509 /// Internal tracking information about the currently pushed stacking context. 4510 /// Used to track what operations need to happen when a stacking context is popped. 4511 struct StackingContextInfo { 4512 /// If true, pop and entry from the containing block stack. 4513 pop_containing_block: bool, 4514 /// If true, pop an entry from the flattened stacking context stack. 4515 pop_stacking_context: bool, 4516 /// If true, set a tile cache barrier when popping the stacking context. 4517 set_tile_cache_barrier: bool, 4518 /// If true, this stacking context was nested into two pushes instead of 4519 /// one, and requires an extra pop to compensate. The info to pop is stored 4520 /// at the top of `extra_stacking_context_stack`. 4521 needs_extra_stacking_context: bool, 4522 } 4523 4524 /// Properties of a stacking context that are maintained 4525 /// during creation of the scene. These structures are 4526 /// not persisted after the initial scene build. 4527 struct FlattenedStackingContext { 4528 /// The list of primitive instances added to this stacking context. 4529 prim_list: PrimitiveList, 4530 4531 /// Primitive instance flags for compositing this stacking context 4532 prim_flags: PrimitiveFlags, 4533 4534 /// The positioning node for this stacking context 4535 spatial_node_index: SpatialNodeIndex, 4536 4537 /// The clip chain for this stacking context 4538 clip_node_id: ClipNodeId, 4539 4540 /// The list of filters / mix-blend-mode for this 4541 /// stacking context. 4542 composite_ops: CompositeOps, 4543 4544 /// Bitfield of reasons this stacking context needs to 4545 /// be an offscreen surface. 4546 blit_reason: BlitReason, 4547 4548 /// CSS transform-style property. 4549 transform_style: TransformStyle, 4550 4551 /// Defines the relationship to a preserve-3D hiearachy. 4552 context_3d: Picture3DContext<ExtendedPrimitiveInstance>, 4553 4554 /// Flags identifying the type of container (among other things) this stacking context is 4555 flags: StackingContextFlags, 4556 4557 /// Requested raster space for this stacking context 4558 raster_space: RasterSpace, 4559 4560 /// Offset to be applied to any filter sub-regions 4561 subregion_offset: LayoutVector2D, 4562 } 4563 4564 impl FlattenedStackingContext { 4565 /// Return true if the stacking context has a valid preserve-3d property 4566 pub fn is_3d(&self) -> bool { 4567 self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty() 4568 } 4569 4570 /// Return true if the stacking context isn't needed. 4571 pub fn is_redundant( 4572 context_3d: &Picture3DContext<ExtendedPrimitiveInstance>, 4573 composite_ops: &CompositeOps, 4574 blit_reason: BlitReason, 4575 parent: Option<&FlattenedStackingContext>, 4576 prim_flags: PrimitiveFlags, 4577 ) -> bool { 4578 // Any 3d context is required 4579 if let Picture3DContext::In { .. } = context_3d { 4580 return false; 4581 } 4582 4583 // If any filters are present that affect the output 4584 if composite_ops.has_valid_filters() { 4585 return false; 4586 } 4587 4588 // If a mix-blend is active, we'll need to apply it in most cases 4589 if composite_ops.mix_blend_mode.is_some() { 4590 match parent { 4591 Some(ref parent) => { 4592 // However, if the parent stacking context is empty, then the mix-blend 4593 // is a no-op, and we can skip it 4594 if !parent.prim_list.is_empty() { 4595 return false; 4596 } 4597 } 4598 None => { 4599 // TODO(gw): For now, we apply mix-blend ops that may be no-ops on a root 4600 // level picture cache slice. We could apply a similar optimization 4601 // to above with a few extra checks here, but it's probably quite rare. 4602 return false; 4603 } 4604 } 4605 } 4606 4607 // If need to isolate in surface due to clipping / mix-blend-mode 4608 if !blit_reason.is_empty() { 4609 return false; 4610 } 4611 4612 // If backface visibility is explicitly set. 4613 if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) { 4614 return false; 4615 } 4616 4617 // It is redundant! 4618 true 4619 } 4620 4621 /// Cut the sequence of the immediate children recorded so far and generate a picture from them. 4622 pub fn cut_item_sequence( 4623 &mut self, 4624 prim_store: &mut PrimitiveStore, 4625 interners: &mut Interners, 4626 composite_mode: Option<PictureCompositeMode>, 4627 flat_items_context_3d: Picture3DContext<OrderedPictureChild>, 4628 clip_tree_builder: &mut ClipTreeBuilder, 4629 ) -> Option<(PictureIndex, PrimitiveInstance)> { 4630 if self.prim_list.is_empty() { 4631 return None 4632 } 4633 4634 let pic_index = PictureIndex(prim_store.pictures 4635 .alloc() 4636 .init(PicturePrimitive::new_image( 4637 composite_mode.clone(), 4638 flat_items_context_3d, 4639 self.prim_flags, 4640 mem::replace(&mut self.prim_list, PrimitiveList::empty()), 4641 self.spatial_node_index, 4642 self.raster_space, 4643 PictureFlags::empty(), 4644 None 4645 )) 4646 ); 4647 4648 let prim_instance = create_prim_instance( 4649 pic_index, 4650 composite_mode.into(), 4651 self.raster_space, 4652 self.clip_node_id, 4653 interners, 4654 clip_tree_builder, 4655 ); 4656 4657 Some((pic_index, prim_instance)) 4658 } 4659 } 4660 4661 /// A primitive that is added while a shadow context is 4662 /// active is stored as a pending primitive and only 4663 /// added to pictures during pop_all_shadows. 4664 pub struct PendingPrimitive<T> { 4665 spatial_node_index: SpatialNodeIndex, 4666 clip_node_id: ClipNodeId, 4667 info: LayoutPrimitiveInfo, 4668 prim: T, 4669 } 4670 4671 /// As shadows are pushed, they are stored as pending 4672 /// shadows, and handled at once during pop_all_shadows. 4673 pub struct PendingShadow { 4674 shadow: Shadow, 4675 should_inflate: bool, 4676 spatial_node_index: SpatialNodeIndex, 4677 } 4678 4679 pub enum ShadowItem { 4680 Shadow(PendingShadow), 4681 Image(PendingPrimitive<Image>), 4682 LineDecoration(PendingPrimitive<LineDecoration>), 4683 NormalBorder(PendingPrimitive<NormalBorderPrim>), 4684 Primitive(PendingPrimitive<PrimitiveKeyKind>), 4685 TextRun(PendingPrimitive<TextRun>), 4686 } 4687 4688 impl From<PendingPrimitive<Image>> for ShadowItem { 4689 fn from(image: PendingPrimitive<Image>) -> Self { 4690 ShadowItem::Image(image) 4691 } 4692 } 4693 4694 impl From<PendingPrimitive<LineDecoration>> for ShadowItem { 4695 fn from(line_dec: PendingPrimitive<LineDecoration>) -> Self { 4696 ShadowItem::LineDecoration(line_dec) 4697 } 4698 } 4699 4700 impl From<PendingPrimitive<NormalBorderPrim>> for ShadowItem { 4701 fn from(border: PendingPrimitive<NormalBorderPrim>) -> Self { 4702 ShadowItem::NormalBorder(border) 4703 } 4704 } 4705 4706 impl From<PendingPrimitive<PrimitiveKeyKind>> for ShadowItem { 4707 fn from(container: PendingPrimitive<PrimitiveKeyKind>) -> Self { 4708 ShadowItem::Primitive(container) 4709 } 4710 } 4711 4712 impl From<PendingPrimitive<TextRun>> for ShadowItem { 4713 fn from(text_run: PendingPrimitive<TextRun>) -> Self { 4714 ShadowItem::TextRun(text_run) 4715 } 4716 } 4717 4718 fn create_prim_instance( 4719 pic_index: PictureIndex, 4720 composite_mode_key: PictureCompositeKey, 4721 raster_space: RasterSpace, 4722 clip_node_id: ClipNodeId, 4723 interners: &mut Interners, 4724 clip_tree_builder: &mut ClipTreeBuilder, 4725 ) -> PrimitiveInstance { 4726 let pic_key = PictureKey::new( 4727 Picture { 4728 composite_mode_key, 4729 raster_space, 4730 }, 4731 ); 4732 4733 let data_handle = interners 4734 .picture 4735 .intern(&pic_key, || ()); 4736 4737 PrimitiveInstance::new( 4738 PrimitiveInstanceKind::Picture { 4739 data_handle, 4740 pic_index, 4741 }, 4742 clip_tree_builder.build_for_picture( 4743 clip_node_id, 4744 ), 4745 ) 4746 } 4747 4748 fn filter_ops_for_compositing( 4749 input_filters: ItemRange<FilterOp>, 4750 ) -> Vec<Filter> { 4751 // TODO(gw): Now that we resolve these later on, 4752 // we could probably make it a bit 4753 // more efficient than cloning these here. 4754 input_filters.iter().map(|filter| filter.into()).collect() 4755 } 4756 4757 fn filter_datas_for_compositing( 4758 input_filter_datas: &[TempFilterData], 4759 ) -> Vec<FilterData> { 4760 // TODO(gw): Now that we resolve these later on, 4761 // we could probably make it a bit 4762 // more efficient than cloning these here. 4763 let mut filter_datas = vec![]; 4764 for temp_filter_data in input_filter_datas { 4765 let func_types : Vec<ComponentTransferFuncType> = temp_filter_data.func_types.iter().collect(); 4766 debug_assert!(func_types.len() == 4); 4767 filter_datas.push( FilterData { 4768 func_r_type: func_types[0], 4769 r_values: temp_filter_data.r_values.iter().collect(), 4770 func_g_type: func_types[1], 4771 g_values: temp_filter_data.g_values.iter().collect(), 4772 func_b_type: func_types[2], 4773 b_values: temp_filter_data.b_values.iter().collect(), 4774 func_a_type: func_types[3], 4775 a_values: temp_filter_data.a_values.iter().collect(), 4776 }); 4777 } 4778 filter_datas 4779 } 4780 4781 fn process_repeat_size( 4782 snapped_rect: &LayoutRect, 4783 unsnapped_rect: &LayoutRect, 4784 repeat_size: LayoutSize, 4785 ) -> LayoutSize { 4786 // FIXME(aosmond): The tile size is calculated based on several parameters 4787 // during display list building. It may produce a slightly different result 4788 // than the bounds due to floating point error accumulation, even though in 4789 // theory they should be the same. We do a fuzzy check here to paper over 4790 // that. It may make more sense to push the original parameters into scene 4791 // building and let it do a saner calculation with more information (e.g. 4792 // the snapped values). 4793 const EPSILON: f32 = 0.001; 4794 LayoutSize::new( 4795 if repeat_size.width.approx_eq_eps(&unsnapped_rect.width(), &EPSILON) { 4796 snapped_rect.width() 4797 } else { 4798 repeat_size.width 4799 }, 4800 if repeat_size.height.approx_eq_eps(&unsnapped_rect.height(), &EPSILON) { 4801 snapped_rect.height() 4802 } else { 4803 repeat_size.height 4804 }, 4805 ) 4806 } 4807 4808 fn read_gradient_stops(stops: ItemRange<GradientStop>) -> Vec<GradientStopKey> { 4809 stops.iter().map(|stop| { 4810 GradientStopKey { 4811 offset: stop.offset, 4812 color: stop.color.into(), 4813 } 4814 }).collect() 4815 } 4816 4817 /// A helper for reusing the scene builder's memory allocations and dropping 4818 /// scene allocations on the scene builder thread to avoid lock contention in 4819 /// jemalloc. 4820 pub struct SceneRecycler { 4821 pub tx: Sender<BuiltScene>, 4822 rx: Receiver<BuiltScene>, 4823 4824 // Allocations recycled from BuiltScene: 4825 4826 pub prim_store: PrimitiveStore, 4827 pub clip_store: ClipStore, 4828 pub picture_graph: PictureGraph, 4829 pub prim_instances: Vec<PrimitiveInstance>, 4830 pub surfaces: Vec<SurfaceInfo>, 4831 pub hit_testing_scene: Option<HitTestingScene>, 4832 pub clip_tree_builder: Option<ClipTreeBuilder>, 4833 //Could also attempt to recycle the following: 4834 //pub tile_cache_config: TileCacheConfig, 4835 //pub pipeline_epochs: FastHashMap<PipelineId, Epoch>, 4836 //pub tile_cache_pictures: Vec<PictureIndex>, 4837 4838 4839 // Allocations recycled from SceneBuilder 4840 4841 id_to_index_mapper_stack: Vec<NodeIdToIndexMapper>, 4842 sc_stack: Vec<FlattenedStackingContext>, 4843 containing_block_stack: Vec<SpatialNodeIndex>, 4844 raster_space_stack: Vec<RasterSpace>, 4845 pending_shadow_items: VecDeque<ShadowItem>, 4846 iframe_size: Vec<LayoutSize>, 4847 } 4848 4849 impl SceneRecycler { 4850 pub fn new() -> Self { 4851 let (tx, rx) = unbounded_channel(); 4852 SceneRecycler { 4853 tx, 4854 rx, 4855 4856 prim_instances: Vec::new(), 4857 surfaces: Vec::new(), 4858 prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()), 4859 clip_store: ClipStore::new(), 4860 picture_graph: PictureGraph::new(), 4861 hit_testing_scene: None, 4862 clip_tree_builder: None, 4863 4864 id_to_index_mapper_stack: Vec::new(), 4865 sc_stack: Vec::new(), 4866 containing_block_stack: Vec::new(), 4867 raster_space_stack: Vec::new(), 4868 pending_shadow_items: VecDeque::new(), 4869 iframe_size: Vec::new(), 4870 } 4871 } 4872 4873 /// Do some bookkeeping of past memory allocations, retaining some of them for 4874 /// reuse and dropping the rest. 4875 /// 4876 /// Should be called once between scene builds, ideally outside of the critical 4877 /// path since deallocations can take some time. 4878 #[inline(never)] 4879 pub fn recycle_built_scene(&mut self) { 4880 let Ok(scene) = self.rx.try_recv() else { 4881 return; 4882 }; 4883 4884 self.prim_store = scene.prim_store; 4885 self.clip_store = scene.clip_store; 4886 // We currently retain top-level allocations but don't attempt to retain leaf 4887 // allocations in the prim store and clip store. We don't have to reset it here 4888 // but doing so avoids dropping the leaf allocations in the 4889 self.prim_store.reset(); 4890 self.clip_store.reset(); 4891 self.hit_testing_scene = Arc::try_unwrap(scene.hit_testing_scene).ok(); 4892 self.picture_graph = scene.picture_graph; 4893 self.prim_instances = scene.prim_instances; 4894 self.surfaces = scene.surfaces; 4895 if let Some(clip_tree_builder) = &mut self.clip_tree_builder { 4896 clip_tree_builder.recycle_tree(scene.clip_tree); 4897 } 4898 4899 while let Ok(_) = self.rx.try_recv() { 4900 // If for some reason more than one scene accumulated in the queue, drop 4901 // the rest. 4902 } 4903 4904 // Note: fields of the scene we don't recycle get dropped here. 4905 } 4906 }