picture.rs (104446B)
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 //! A picture represents a dynamically rendered image. 6 //! 7 //! # Overview 8 //! 9 //! Pictures consists of: 10 //! 11 //! - A number of primitives that are drawn onto the picture. 12 //! - A composite operation describing how to composite this 13 //! picture into its parent. 14 //! - A configuration describing how to draw the primitives on 15 //! this picture (e.g. in screen space or local space). 16 //! 17 //! The tree of pictures are generated during scene building. 18 //! 19 //! Depending on their composite operations pictures can be rendered into 20 //! intermediate targets or folded into their parent picture. 21 //! 22 //! ## Picture caching 23 //! 24 //! Pictures can be cached to reduce the amount of rasterization happening per 25 //! frame. 26 //! 27 //! When picture caching is enabled, the scene is cut into a small number of slices, 28 //! typically: 29 //! 30 //! - content slice 31 //! - UI slice 32 //! - background UI slice which is hidden by the other two slices most of the time. 33 //! 34 //! Each of these slice is made up of fixed-size large tiles of 2048x512 pixels 35 //! (or 128x128 for the UI slice). 36 //! 37 //! Tiles can be either cached rasterized content into a texture or "clear tiles" 38 //! that contain only a solid color rectangle rendered directly during the composite 39 //! pass. 40 //! 41 //! ## Invalidation 42 //! 43 //! Each tile keeps track of the elements that affect it, which can be: 44 //! 45 //! - primitives 46 //! - clips 47 //! - image keys 48 //! - opacity bindings 49 //! - transforms 50 //! 51 //! These dependency lists are built each frame and compared to the previous frame to 52 //! see if the tile changed. 53 //! 54 //! The tile's primitive dependency information is organized in a quadtree, each node 55 //! storing an index buffer of tile primitive dependencies. 56 //! 57 //! The union of the invalidated leaves of each quadtree produces a per-tile dirty rect 58 //! which defines the scissor rect used when replaying the tile's drawing commands and 59 //! can be used for partial present. 60 //! 61 //! ## Display List shape 62 //! 63 //! WR will first look for an iframe item in the root stacking context to apply 64 //! picture caching to. If that's not found, it will apply to the entire root 65 //! stacking context of the display list. Apart from that, the format of the 66 //! display list is not important to picture caching. Each time a new scroll root 67 //! is encountered, a new picture cache slice will be created. If the display 68 //! list contains more than some arbitrary number of slices (currently 8), the 69 //! content will all be squashed into a single slice, in order to save GPU memory 70 //! and compositing performance. 71 //! 72 //! ## Compositor Surfaces 73 //! 74 //! Sometimes, a primitive would prefer to exist as a native compositor surface. 75 //! This allows a large and/or regularly changing primitive (such as a video, or 76 //! webgl canvas) to be updated each frame without invalidating the content of 77 //! tiles, and can provide a significant performance win and battery saving. 78 //! 79 //! Since drawing a primitive as a compositor surface alters the ordering of 80 //! primitives in a tile, we use 'overlay tiles' to ensure correctness. If a 81 //! tile has a compositor surface, _and_ that tile has primitives that overlap 82 //! the compositor surface rect, the tile switches to be drawn in alpha mode. 83 //! 84 //! We rely on only promoting compositor surfaces that are opaque primitives. 85 //! With this assumption, the tile(s) that intersect the compositor surface get 86 //! a 'cutout' in the rectangle where the compositor surface exists (not the 87 //! entire tile), allowing that tile to be drawn as an alpha tile after the 88 //! compositor surface. 89 //! 90 //! Tiles are only drawn in overlay mode if there is content that exists on top 91 //! of the compositor surface. Otherwise, we can draw the tiles in the normal fast 92 //! path before the compositor surface is drawn. Use of the per-tile valid and 93 //! dirty rects ensure that we do a minimal amount of per-pixel work here to 94 //! blend the overlay tile (this is not always optimal right now, but will be 95 //! improved as a follow up). 96 97 use api::RasterSpace; 98 use api::{DebugFlags, ColorF, PrimitiveFlags, SnapshotInfo}; 99 use api::units::*; 100 use crate::command_buffer::PrimitiveCommand; 101 use crate::renderer::GpuBufferBuilderF; 102 use crate::box_shadow::BLUR_SAMPLE_SCALE; 103 use crate::clip::{ClipNodeId, ClipTreeBuilder}; 104 use crate::spatial_tree::{SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace}; 105 use crate::composite::{tile_kind, CompositeTileSurface, CompositorKind, NativeTileId}; 106 use crate::composite::{CompositeTileDescriptor, CompositeTile}; 107 use crate::debug_colors; 108 use euclid::{vec3, Scale, Vector2D, Box2D}; 109 use crate::internal_types::{FastHashMap, PlaneSplitter, Filter}; 110 use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource}; 111 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext}; 112 use plane_split::{Clipper, Polygon}; 113 use crate::prim_store::{PictureIndex, PrimitiveInstance, PrimitiveInstanceKind}; 114 use crate::prim_store::PrimitiveScratchBuffer; 115 use crate::print_tree::PrintTreePrinter; 116 use crate::render_backend::DataStores; 117 use crate::render_task_graph::RenderTaskId; 118 use crate::render_task::{RenderTask, RenderTaskLocation}; 119 use crate::render_task::{StaticRenderTaskSurface, RenderTaskKind}; 120 use crate::renderer::GpuBufferAddress; 121 use crate::resource_cache::ResourceCache; 122 use crate::space::SpaceMapper; 123 use crate::scene::SceneProperties; 124 use crate::spatial_tree::CoordinateSystemId; 125 use crate::surface::{SurfaceDescriptor, SurfaceTileDescriptor, get_surface_rects}; 126 pub use crate::surface::{SurfaceIndex, SurfaceInfo, SubpixelMode}; 127 pub use crate::surface::{calculate_screen_uv, calculate_uv_rect_kind}; 128 use smallvec::SmallVec; 129 use std::{mem, u8, u32}; 130 use std::ops::Range; 131 use crate::picture_textures::PictureCacheTextureHandle; 132 use crate::util::{MaxRect, Recycler, ScaleOffset}; 133 use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo}; 134 use crate::tile_cache::{SliceId, TileCacheInstance, TileSurface, NativeSurface}; 135 use crate::tile_cache::{BackdropKind, BackdropSurface}; 136 use crate::tile_cache::{TileKey, SubSliceIndex}; 137 use crate::invalidation::InvalidationReason; 138 use crate::tile_cache::MAX_SURFACE_SIZE; 139 140 pub use crate::picture_composite_mode::{PictureCompositeMode, prepare_composite_mode}; 141 142 // Maximum blur radius for blur filter (different than box-shadow blur). 143 // Taken from FilterNodeSoftware.cpp in Gecko. 144 pub(crate) const MAX_BLUR_RADIUS: f32 = 100.; 145 146 /// Maximum size of a compositor surface. 147 pub const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0; 148 149 pub fn clamp(value: i32, low: i32, high: i32) -> i32 { 150 value.max(low).min(high) 151 } 152 153 pub fn clampf(value: f32, low: f32, high: f32) -> f32 { 154 value.max(low).min(high) 155 } 156 157 /// A descriptor for the kind of texture that a picture cache tile will 158 /// be drawn into. 159 #[derive(Debug)] 160 pub enum SurfaceTextureDescriptor { 161 /// When using the WR compositor, the tile is drawn into an entry 162 /// in the WR texture cache. 163 TextureCache { 164 handle: Option<PictureCacheTextureHandle>, 165 }, 166 /// When using an OS compositor, the tile is drawn into a native 167 /// surface identified by arbitrary id. 168 Native { 169 /// The arbitrary id of this tile. 170 id: Option<NativeTileId>, 171 }, 172 } 173 174 /// This is the same as a `SurfaceTextureDescriptor` but has been resolved 175 /// into a texture cache handle (if appropriate) that can be used by the 176 /// batching and compositing code in the renderer. 177 #[derive(Clone, Debug, Eq, PartialEq, Hash)] 178 #[cfg_attr(feature = "capture", derive(Serialize))] 179 #[cfg_attr(feature = "replay", derive(Deserialize))] 180 pub enum ResolvedSurfaceTexture { 181 TextureCache { 182 /// The texture ID to draw to. 183 texture: TextureSource, 184 }, 185 Native { 186 /// The arbitrary id of this tile. 187 id: NativeTileId, 188 /// The size of the tile in device pixels. 189 size: DeviceIntSize, 190 } 191 } 192 193 impl SurfaceTextureDescriptor { 194 /// Create a resolved surface texture for this descriptor 195 pub fn resolve( 196 &self, 197 resource_cache: &ResourceCache, 198 size: DeviceIntSize, 199 ) -> ResolvedSurfaceTexture { 200 match self { 201 SurfaceTextureDescriptor::TextureCache { handle } => { 202 let texture = resource_cache 203 .picture_textures 204 .get_texture_source(handle.as_ref().unwrap()); 205 206 ResolvedSurfaceTexture::TextureCache { texture } 207 } 208 SurfaceTextureDescriptor::Native { id } => { 209 ResolvedSurfaceTexture::Native { 210 id: id.expect("bug: native surface not allocated"), 211 size, 212 } 213 } 214 } 215 } 216 } 217 218 pub struct PictureScratchBuffer { 219 surface_stack: Vec<SurfaceIndex>, 220 } 221 222 impl Default for PictureScratchBuffer { 223 fn default() -> Self { 224 PictureScratchBuffer { 225 surface_stack: Vec::new(), 226 } 227 } 228 } 229 230 impl PictureScratchBuffer { 231 pub fn begin_frame(&mut self) { 232 self.surface_stack.clear(); 233 } 234 235 pub fn recycle(&mut self, recycler: &mut Recycler) { 236 recycler.recycle_vec(&mut self.surface_stack); 237 } 238 } 239 240 #[derive(Debug)] 241 #[cfg_attr(feature = "capture", derive(Serialize))] 242 pub struct RasterConfig { 243 /// How this picture should be composited into 244 /// the parent surface. 245 // TODO(gw): We should remove this and just use what is in PicturePrimitive 246 pub composite_mode: PictureCompositeMode, 247 /// Index to the surface descriptor for this 248 /// picture. 249 pub surface_index: SurfaceIndex, 250 } 251 252 bitflags! { 253 /// A set of flags describing why a picture may need a backing surface. 254 #[cfg_attr(feature = "capture", derive(Serialize))] 255 #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 256 pub struct BlitReason: u32 { 257 /// Mix-blend-mode on a child that requires isolation. 258 const BLEND_MODE = 1 << 0; 259 /// Clip node that _might_ require a surface. 260 const CLIP = 1 << 1; 261 /// Preserve-3D requires a surface for plane-splitting. 262 const PRESERVE3D = 1 << 2; 263 /// A forced isolation request from gecko. 264 const FORCED_ISOLATION = 1 << 3; 265 /// We may need to render the picture into an image and cache it. 266 const SNAPSHOT = 1 << 4; 267 } 268 } 269 270 /// Enum value describing the place of a picture in a 3D context. 271 #[derive(Clone, Debug)] 272 #[cfg_attr(feature = "capture", derive(Serialize))] 273 pub enum Picture3DContext<C> { 274 /// The picture is not a part of 3D context sub-hierarchy. 275 Out, 276 /// The picture is a part of 3D context. 277 In { 278 /// Additional data per child for the case of this a root of 3D hierarchy. 279 root_data: Option<Vec<C>>, 280 /// The spatial node index of an "ancestor" element, i.e. one 281 /// that establishes the transformed element's containing block. 282 /// 283 /// See CSS spec draft for more details: 284 /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation 285 ancestor_index: SpatialNodeIndex, 286 /// Index in the built scene's array of plane splitters. 287 plane_splitter_index: PlaneSplitterIndex, 288 }, 289 } 290 291 /// Information about a preserve-3D hierarchy child that has been plane-split 292 /// and ordered according to the view direction. 293 #[derive(Clone, Debug)] 294 #[cfg_attr(feature = "capture", derive(Serialize))] 295 pub struct OrderedPictureChild { 296 pub anchor: PlaneSplitAnchor, 297 pub gpu_address: GpuBufferAddress, 298 } 299 300 bitflags! { 301 /// A set of flags describing why a picture may need a backing surface. 302 #[cfg_attr(feature = "capture", derive(Serialize))] 303 #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 304 pub struct ClusterFlags: u32 { 305 /// Whether this cluster is visible when the position node is a backface. 306 const IS_BACKFACE_VISIBLE = 1; 307 /// This flag is set during the first pass picture traversal, depending on whether 308 /// the cluster is visible or not. It's read during the second pass when primitives 309 /// consult their owning clusters to see if the primitive itself is visible. 310 const IS_VISIBLE = 2; 311 } 312 } 313 314 /// Descriptor for a cluster of primitives. For now, this is quite basic but will be 315 /// extended to handle more spatial clustering of primitives. 316 #[cfg_attr(feature = "capture", derive(Serialize))] 317 pub struct PrimitiveCluster { 318 /// The positioning node for this cluster. 319 pub spatial_node_index: SpatialNodeIndex, 320 /// The bounding rect of the cluster, in the local space of the spatial node. 321 /// This is used to quickly determine the overall bounding rect for a picture 322 /// during the first picture traversal, which is needed for local scale 323 /// determination, and render task size calculations. 324 bounding_rect: LayoutRect, 325 /// a part of the cluster that we know to be opaque if any. Does not always 326 /// describe the entire opaque region, but all content within that rect must 327 /// be opaque. 328 pub opaque_rect: LayoutRect, 329 /// The range of primitive instance indices associated with this cluster. 330 pub prim_range: Range<usize>, 331 /// Various flags / state for this cluster. 332 pub flags: ClusterFlags, 333 } 334 335 impl PrimitiveCluster { 336 /// Construct a new primitive cluster for a given positioning node. 337 fn new( 338 spatial_node_index: SpatialNodeIndex, 339 flags: ClusterFlags, 340 first_instance_index: usize, 341 ) -> Self { 342 PrimitiveCluster { 343 bounding_rect: LayoutRect::zero(), 344 opaque_rect: LayoutRect::zero(), 345 spatial_node_index, 346 flags, 347 prim_range: first_instance_index..first_instance_index 348 } 349 } 350 351 /// Return true if this cluster is compatible with the given params 352 pub fn is_compatible( 353 &self, 354 spatial_node_index: SpatialNodeIndex, 355 flags: ClusterFlags, 356 instance_index: usize, 357 ) -> bool { 358 self.flags == flags && 359 self.spatial_node_index == spatial_node_index && 360 instance_index == self.prim_range.end 361 } 362 363 pub fn prim_range(&self) -> Range<usize> { 364 self.prim_range.clone() 365 } 366 367 /// Add a primitive instance to this cluster, at the start or end 368 fn add_instance( 369 &mut self, 370 culling_rect: &LayoutRect, 371 instance_index: usize, 372 ) { 373 debug_assert_eq!(instance_index, self.prim_range.end); 374 self.bounding_rect = self.bounding_rect.union(culling_rect); 375 self.prim_range.end += 1; 376 } 377 } 378 379 /// A list of primitive instances that are added to a picture 380 /// This ensures we can keep a list of primitives that 381 /// are pictures, for a fast initial traversal of the picture 382 /// tree without walking the instance list. 383 #[cfg_attr(feature = "capture", derive(Serialize))] 384 pub struct PrimitiveList { 385 /// List of primitives grouped into clusters. 386 pub clusters: Vec<PrimitiveCluster>, 387 pub child_pictures: Vec<PictureIndex>, 388 /// The number of Image compositor surfaces that were found when 389 /// adding prims to this list, which might be rendered as overlays. 390 pub image_surface_count: usize, 391 /// The number of YuvImage compositor surfaces that were found when 392 /// adding prims to this list, which might be rendered as overlays. 393 pub yuv_image_surface_count: usize, 394 pub needs_scissor_rect: bool, 395 } 396 397 impl PrimitiveList { 398 /// Construct an empty primitive list. This is 399 /// just used during the take_context / restore_context 400 /// borrow check dance, which will be removed as the 401 /// picture traversal pass is completed. 402 pub fn empty() -> Self { 403 PrimitiveList { 404 clusters: Vec::new(), 405 child_pictures: Vec::new(), 406 image_surface_count: 0, 407 yuv_image_surface_count: 0, 408 needs_scissor_rect: false, 409 } 410 } 411 412 pub fn merge(&mut self, other: PrimitiveList) { 413 self.clusters.extend(other.clusters); 414 self.child_pictures.extend(other.child_pictures); 415 self.image_surface_count += other.image_surface_count; 416 self.yuv_image_surface_count += other.yuv_image_surface_count; 417 self.needs_scissor_rect |= other.needs_scissor_rect; 418 } 419 420 /// Add a primitive instance to the end of the list 421 pub fn add_prim( 422 &mut self, 423 prim_instance: PrimitiveInstance, 424 prim_rect: LayoutRect, 425 spatial_node_index: SpatialNodeIndex, 426 prim_flags: PrimitiveFlags, 427 prim_instances: &mut Vec<PrimitiveInstance>, 428 clip_tree_builder: &ClipTreeBuilder, 429 ) { 430 let mut flags = ClusterFlags::empty(); 431 432 // Pictures are always put into a new cluster, to make it faster to 433 // iterate all pictures in a given primitive list. 434 match prim_instance.kind { 435 PrimitiveInstanceKind::Picture { pic_index, .. } => { 436 self.child_pictures.push(pic_index); 437 } 438 PrimitiveInstanceKind::TextRun { .. } => { 439 self.needs_scissor_rect = true; 440 } 441 PrimitiveInstanceKind::YuvImage { .. } => { 442 // Any YUV image that requests a compositor surface is implicitly 443 // opaque. Though we might treat this prim as an underlay, which 444 // doesn't require an overlay surface, we add to the count anyway 445 // in case we opt to present it as an overlay. This means we may 446 // be allocating more subslices than we actually need, but it 447 // gives us maximum flexibility. 448 if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 449 self.yuv_image_surface_count += 1; 450 } 451 } 452 PrimitiveInstanceKind::Image { .. } => { 453 // For now, we assume that any image that wants a compositor surface 454 // is transparent, and uses the existing overlay compositor surface 455 // infrastructure. In future, we could detect opaque images, however 456 // it's a little bit of work, as scene building doesn't have access 457 // to the opacity state of an image key at this point. 458 if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { 459 self.image_surface_count += 1; 460 } 461 } 462 _ => {} 463 } 464 465 if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) { 466 flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE); 467 } 468 469 let clip_leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id); 470 let culling_rect = clip_leaf.local_clip_rect 471 .intersection(&prim_rect) 472 .unwrap_or_else(LayoutRect::zero); 473 474 let instance_index = prim_instances.len(); 475 prim_instances.push(prim_instance); 476 477 if let Some(cluster) = self.clusters.last_mut() { 478 if cluster.is_compatible(spatial_node_index, flags, instance_index) { 479 cluster.add_instance(&culling_rect, instance_index); 480 return; 481 } 482 } 483 484 // Same idea with clusters, using a different distribution. 485 let clusters_len = self.clusters.len(); 486 if clusters_len == self.clusters.capacity() { 487 let next_alloc = match clusters_len { 488 1 ..= 15 => 16 - clusters_len, 489 16 ..= 127 => 128 - clusters_len, 490 _ => clusters_len * 2, 491 }; 492 493 self.clusters.reserve(next_alloc); 494 } 495 496 let mut cluster = PrimitiveCluster::new( 497 spatial_node_index, 498 flags, 499 instance_index, 500 ); 501 502 cluster.add_instance(&culling_rect, instance_index); 503 self.clusters.push(cluster); 504 } 505 506 /// Returns true if there are no clusters (and thus primitives) 507 pub fn is_empty(&self) -> bool { 508 self.clusters.is_empty() 509 } 510 } 511 512 bitflags! { 513 #[cfg_attr(feature = "capture", derive(Serialize))] 514 /// Flags describing properties for a given PicturePrimitive 515 #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] 516 pub struct PictureFlags : u8 { 517 /// This picture is a resolve target (doesn't actually render content itself, 518 /// will have content copied in to it) 519 const IS_RESOLVE_TARGET = 1 << 0; 520 /// This picture establishes a sub-graph, which affects how SurfaceBuilder will 521 /// set up dependencies in the render task graph 522 const IS_SUB_GRAPH = 1 << 1; 523 /// If set, this picture should not apply snapping via changing the raster root 524 const DISABLE_SNAPPING = 1 << 2; 525 } 526 } 527 528 #[cfg_attr(feature = "capture", derive(Serialize))] 529 pub struct PicturePrimitive { 530 /// List of primitives, and associated info for this picture. 531 pub prim_list: PrimitiveList, 532 533 /// If false and transform ends up showing the back of the picture, 534 /// it will be considered invisible. 535 pub is_backface_visible: bool, 536 537 /// All render tasks have 0-2 input tasks. 538 pub primary_render_task_id: Option<RenderTaskId>, 539 /// If a mix-blend-mode, contains the render task for 540 /// the readback of the framebuffer that we use to sample 541 /// from in the mix-blend-mode shader. 542 /// For drop-shadow filter, this will store the original 543 /// picture task which would be rendered on screen after 544 /// blur pass. 545 /// This is also used by SVGFEBlend, SVGFEComposite and 546 /// SVGFEDisplacementMap filters. 547 pub secondary_render_task_id: Option<RenderTaskId>, 548 /// How this picture should be composited. 549 /// If None, don't composite - just draw directly on parent surface. 550 pub composite_mode: Option<PictureCompositeMode>, 551 552 pub raster_config: Option<RasterConfig>, 553 pub context_3d: Picture3DContext<OrderedPictureChild>, 554 555 // Optional cache handles for storing extra data 556 // in the GPU cache, depending on the type of 557 // picture. 558 pub extra_gpu_data: SmallVec<[GpuBufferAddress; 1]>, 559 560 /// The spatial node index of this picture when it is 561 /// composited into the parent picture. 562 pub spatial_node_index: SpatialNodeIndex, 563 564 /// Store the state of the previous local rect 565 /// for this picture. We need this in order to know when 566 /// to invalidate segments / drop-shadow gpu cache handles. 567 pub prev_local_rect: LayoutRect, 568 569 /// If false, this picture needs to (re)build segments 570 /// if it supports segment rendering. This can occur 571 /// if the local rect of the picture changes due to 572 /// transform animation and/or scrolling. 573 pub segments_are_valid: bool, 574 575 /// Set to true if we know for sure the picture is fully opaque. 576 pub is_opaque: bool, 577 578 /// Requested raster space for this picture 579 pub raster_space: RasterSpace, 580 581 /// Flags for this picture primitive 582 pub flags: PictureFlags, 583 584 /// The lowest common ancestor clip of all of the primitives in this 585 /// picture, to be ignored when clipping those primitives and applied 586 /// later when compositing the picture. 587 pub clip_root: Option<ClipNodeId>, 588 589 /// If provided, cache the content of this picture into an image 590 /// associated with the image key. 591 pub snapshot: Option<SnapshotInfo>, 592 } 593 594 impl PicturePrimitive { 595 pub fn print<T: PrintTreePrinter>( 596 &self, 597 pictures: &[Self], 598 self_index: PictureIndex, 599 pt: &mut T, 600 ) { 601 pt.new_level(format!("{:?}", self_index)); 602 pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len())); 603 pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index)); 604 pt.add_item(format!("raster_config: {:?}", self.raster_config)); 605 pt.add_item(format!("composite_mode: {:?}", self.composite_mode)); 606 pt.add_item(format!("flags: {:?}", self.flags)); 607 608 for child_pic_index in &self.prim_list.child_pictures { 609 pictures[child_pic_index.0].print(pictures, *child_pic_index, pt); 610 } 611 612 pt.end_level(); 613 } 614 615 pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) { 616 match self.composite_mode { 617 Some(PictureCompositeMode::Filter(ref mut filter)) => { 618 match *filter { 619 Filter::Opacity(ref binding, ref mut value) => { 620 *value = properties.resolve_float(binding); 621 } 622 _ => {} 623 } 624 } 625 _ => {} 626 } 627 } 628 629 pub fn is_visible( 630 &self, 631 spatial_tree: &SpatialTree, 632 ) -> bool { 633 if let Some(PictureCompositeMode::Filter(ref filter)) = self.composite_mode { 634 if !filter.is_visible() { 635 return false; 636 } 637 } 638 639 // For out-of-preserve-3d pictures, the backface visibility is determined by 640 // the local transform only. 641 // Note: we aren't taking the transform relative to the parent picture, 642 // since picture tree can be more dense than the corresponding spatial tree. 643 if !self.is_backface_visible { 644 if let Picture3DContext::Out = self.context_3d { 645 match spatial_tree.get_local_visible_face(self.spatial_node_index) { 646 VisibleFace::Front => {} 647 VisibleFace::Back => return false, 648 } 649 } 650 } 651 652 true 653 } 654 655 pub fn new_image( 656 composite_mode: Option<PictureCompositeMode>, 657 context_3d: Picture3DContext<OrderedPictureChild>, 658 prim_flags: PrimitiveFlags, 659 prim_list: PrimitiveList, 660 spatial_node_index: SpatialNodeIndex, 661 raster_space: RasterSpace, 662 flags: PictureFlags, 663 snapshot: Option<SnapshotInfo>, 664 ) -> Self { 665 PicturePrimitive { 666 prim_list, 667 primary_render_task_id: None, 668 secondary_render_task_id: None, 669 composite_mode, 670 raster_config: None, 671 context_3d, 672 extra_gpu_data: SmallVec::new(), 673 is_backface_visible: prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE), 674 spatial_node_index, 675 prev_local_rect: LayoutRect::zero(), 676 segments_are_valid: false, 677 is_opaque: false, 678 raster_space, 679 flags, 680 clip_root: None, 681 snapshot, 682 } 683 } 684 685 pub fn take_context( 686 &mut self, 687 pic_index: PictureIndex, 688 parent_surface_index: Option<SurfaceIndex>, 689 parent_subpixel_mode: SubpixelMode, 690 frame_state: &mut FrameBuildingState, 691 frame_context: &FrameBuildingContext, 692 data_stores: &mut DataStores, 693 scratch: &mut PrimitiveScratchBuffer, 694 tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>, 695 ) -> Option<(PictureContext, PictureState, PrimitiveList)> { 696 frame_state.visited_pictures[pic_index.0] = true; 697 self.primary_render_task_id = None; 698 self.secondary_render_task_id = None; 699 700 let dbg_flags = DebugFlags::PICTURE_CACHING_DBG | DebugFlags::PICTURE_BORDERS; 701 if frame_context.debug_flags.intersects(dbg_flags) { 702 self.draw_debug_overlay( 703 parent_surface_index, 704 frame_state, 705 frame_context, 706 tile_caches, 707 scratch, 708 ); 709 } 710 711 if !self.is_visible(frame_context.spatial_tree) { 712 return None; 713 } 714 715 profile_scope!("take_context"); 716 717 let surface_index = match self.raster_config { 718 Some(ref raster_config) => raster_config.surface_index, 719 None => parent_surface_index.expect("bug: no parent"), 720 }; 721 let surface = &frame_state.surfaces[surface_index.0]; 722 let surface_spatial_node_index = surface.surface_spatial_node_index; 723 724 let map_pic_to_world = SpaceMapper::new_with_target( 725 frame_context.root_spatial_node_index, 726 surface_spatial_node_index, 727 frame_context.global_screen_world_rect, 728 frame_context.spatial_tree, 729 ); 730 731 let map_pic_to_vis = SpaceMapper::new_with_target( 732 // TODO: switch from root to raster space. 733 frame_context.root_spatial_node_index, 734 surface_spatial_node_index, 735 surface.culling_rect, 736 frame_context.spatial_tree, 737 ); 738 739 // TODO: When moving VisRect to raster space, compute the picture 740 // bounds by projecting the parent surface's culling rect into the 741 // current surface's raster space. 742 let pic_bounds = map_pic_to_world 743 .unmap(&map_pic_to_world.bounds) 744 .unwrap_or_else(PictureRect::max_rect); 745 746 let map_local_to_pic = SpaceMapper::new( 747 surface_spatial_node_index, 748 pic_bounds, 749 ); 750 751 match self.raster_config { 752 Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => { 753 prepare_tiled_picture_surface( 754 surface_index, 755 slice_id, 756 surface_spatial_node_index, 757 &map_pic_to_world, 758 frame_context, 759 frame_state, 760 tile_caches, 761 ); 762 } 763 Some(ref mut raster_config) => { 764 let (pic_rect, force_scissor_rect) = { 765 let surface = &frame_state.surfaces[raster_config.surface_index.0]; 766 (surface.clipped_local_rect, surface.force_scissor_rect) 767 }; 768 769 let parent_surface_index = parent_surface_index.expect("bug: no parent for child surface"); 770 771 // Layout space for the picture is picture space from the 772 // perspective of its child primitives. 773 let local_rect = pic_rect * Scale::new(1.0); 774 775 // If the precise rect changed since last frame, we need to invalidate 776 // any segments and gpu cache handles for drop-shadows. 777 // TODO(gw): Requiring storage of the `prev_precise_local_rect` here 778 // is a total hack. It's required because `prev_precise_local_rect` 779 // gets written to twice (during initial vis pass and also during 780 // prepare pass). The proper longer term fix for this is to make 781 // use of the conservative picture rect for segmenting (which should 782 // be done during scene building). 783 if local_rect != self.prev_local_rect { 784 // Invalidate any segments built for this picture, since the local 785 // rect has changed. 786 self.segments_are_valid = false; 787 self.prev_local_rect = local_rect; 788 } 789 790 let max_surface_size = frame_context 791 .fb_config 792 .max_surface_override 793 .unwrap_or(MAX_SURFACE_SIZE) as f32; 794 795 let surface_rects = match get_surface_rects( 796 raster_config.surface_index, 797 &raster_config.composite_mode, 798 parent_surface_index, 799 &mut frame_state.surfaces, 800 frame_context.spatial_tree, 801 max_surface_size, 802 force_scissor_rect, 803 ) { 804 Some(rects) => rects, 805 None => return None, 806 }; 807 808 if let PictureCompositeMode::IntermediateSurface = raster_config.composite_mode { 809 if !scratch.required_sub_graphs.contains(&pic_index) { 810 return None; 811 } 812 } 813 814 let can_use_shared_surface = !self.flags.contains(PictureFlags::IS_RESOLVE_TARGET); 815 let (surface_descriptor, render_tasks) = prepare_composite_mode( 816 &raster_config.composite_mode, 817 surface_index, 818 parent_surface_index, 819 &surface_rects, 820 &self.snapshot, 821 can_use_shared_surface, 822 frame_context, 823 frame_state, 824 data_stores, 825 &mut self.extra_gpu_data, 826 ); 827 828 self.primary_render_task_id = render_tasks[0]; 829 self.secondary_render_task_id = render_tasks[1]; 830 831 let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH); 832 833 frame_state.surface_builder.push_surface( 834 raster_config.surface_index, 835 is_sub_graph, 836 surface_rects.clipped_local, 837 Some(surface_descriptor), 838 frame_state.surfaces, 839 frame_state.rg_builder, 840 ); 841 } 842 None => {} 843 }; 844 845 let state = PictureState { 846 map_local_to_pic, 847 map_pic_to_vis, 848 }; 849 850 let mut dirty_region_count = 0; 851 852 // If this is a picture cache, push the dirty region to ensure any 853 // child primitives are culled and clipped to the dirty rect(s). 854 if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) = self.raster_config { 855 let dirty_region = tile_caches[&slice_id].dirty_region.clone(); 856 frame_state.push_dirty_region(dirty_region); 857 858 dirty_region_count += 1; 859 } 860 861 let subpixel_mode = compute_subpixel_mode( 862 &self.raster_config, 863 tile_caches, 864 parent_subpixel_mode 865 ); 866 867 let context = PictureContext { 868 pic_index, 869 raster_spatial_node_index: frame_state.surfaces[surface_index.0].raster_spatial_node_index, 870 // TODO: switch the visibility spatial node from the root to raster space. 871 visibility_spatial_node_index: frame_context.root_spatial_node_index, 872 surface_spatial_node_index, 873 surface_index, 874 dirty_region_count, 875 subpixel_mode, 876 }; 877 878 let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty()); 879 880 Some((context, state, prim_list)) 881 } 882 883 pub fn restore_context( 884 &mut self, 885 pic_index: PictureIndex, 886 prim_list: PrimitiveList, 887 context: PictureContext, 888 prim_instances: &[PrimitiveInstance], 889 frame_context: &FrameBuildingContext, 890 frame_state: &mut FrameBuildingState, 891 ) { 892 // Pop any dirty regions this picture set 893 for _ in 0 .. context.dirty_region_count { 894 frame_state.pop_dirty_region(); 895 } 896 897 if self.raster_config.is_some() { 898 frame_state.surface_builder.pop_surface( 899 pic_index, 900 frame_state.rg_builder, 901 frame_state.cmd_buffers, 902 ); 903 } 904 905 if let Picture3DContext::In { root_data: Some(ref mut list), plane_splitter_index, .. } = self.context_3d { 906 let splitter = &mut frame_state.plane_splitters[plane_splitter_index.0]; 907 908 // Resolve split planes via BSP 909 PicturePrimitive::resolve_split_planes( 910 splitter, 911 list, 912 &mut frame_state.frame_gpu_data.f32, 913 &frame_context.spatial_tree, 914 ); 915 916 // Add the child prims to the relevant command buffers 917 let mut cmd_buffer_targets = Vec::new(); 918 for child in list { 919 let child_prim_instance = &prim_instances[child.anchor.instance_index.0 as usize]; 920 921 if frame_state.surface_builder.get_cmd_buffer_targets_for_prim( 922 &child_prim_instance.vis, 923 &mut cmd_buffer_targets, 924 ) { 925 let prim_cmd = PrimitiveCommand::complex( 926 child.anchor.instance_index, 927 child.gpu_address 928 ); 929 930 frame_state.push_prim( 931 &prim_cmd, 932 child.anchor.spatial_node_index, 933 &cmd_buffer_targets, 934 ); 935 } 936 } 937 } 938 939 self.prim_list = prim_list; 940 } 941 942 /// Add a primitive instance to the plane splitter. The function would generate 943 /// an appropriate polygon, clip it against the frustum, and register with the 944 /// given plane splitter. 945 pub fn add_split_plane( 946 splitter: &mut PlaneSplitter, 947 spatial_tree: &SpatialTree, 948 prim_spatial_node_index: SpatialNodeIndex, 949 // TODO: this is called "visibility" while transitioning from world to raster 950 // space. 951 visibility_spatial_node_index: SpatialNodeIndex, 952 original_local_rect: LayoutRect, 953 combined_local_clip_rect: &LayoutRect, 954 dirty_rect: VisRect, 955 plane_split_anchor: PlaneSplitAnchor, 956 ) -> bool { 957 let transform = spatial_tree.get_relative_transform( 958 prim_spatial_node_index, 959 visibility_spatial_node_index 960 ); 961 962 let matrix = transform.clone().into_transform().cast().to_untyped(); 963 964 // Apply the local clip rect here, before splitting. This is 965 // because the local clip rect can't be applied in the vertex 966 // shader for split composites, since we are drawing polygons 967 // rather that rectangles. The interpolation still works correctly 968 // since we determine the UVs by doing a bilerp with a factor 969 // from the original local rect. 970 let local_rect = match original_local_rect 971 .intersection(combined_local_clip_rect) 972 { 973 Some(rect) => rect.cast(), 974 None => return false, 975 }; 976 let dirty_rect = dirty_rect.cast(); 977 978 match transform { 979 CoordinateSpaceMapping::Local => { 980 let polygon = Polygon::from_rect( 981 local_rect.to_rect() * Scale::new(1.0), 982 plane_split_anchor, 983 ); 984 splitter.add(polygon); 985 } 986 CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => { 987 let inv_matrix = scale_offset.inverse().to_transform().cast(); 988 let polygon = Polygon::from_transformed_rect_with_inverse( 989 local_rect.to_rect().to_untyped(), 990 &matrix, 991 &inv_matrix, 992 plane_split_anchor, 993 ).unwrap(); 994 splitter.add(polygon); 995 } 996 CoordinateSpaceMapping::ScaleOffset(_) | 997 CoordinateSpaceMapping::Transform(_) => { 998 let mut clipper = Clipper::new(); 999 let results = clipper.clip_transformed( 1000 Polygon::from_rect( 1001 local_rect.to_rect().to_untyped(), 1002 plane_split_anchor, 1003 ), 1004 &matrix, 1005 Some(dirty_rect.to_rect().to_untyped()), 1006 ); 1007 if let Ok(results) = results { 1008 for poly in results { 1009 splitter.add(poly); 1010 } 1011 } 1012 } 1013 } 1014 1015 true 1016 } 1017 1018 fn resolve_split_planes( 1019 splitter: &mut PlaneSplitter, 1020 ordered: &mut Vec<OrderedPictureChild>, 1021 gpu_buffer: &mut GpuBufferBuilderF, 1022 spatial_tree: &SpatialTree, 1023 ) { 1024 ordered.clear(); 1025 1026 // Process the accumulated split planes and order them for rendering. 1027 // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order. 1028 let sorted = splitter.sort(vec3(0.0, 0.0, 1.0)); 1029 ordered.reserve(sorted.len()); 1030 for poly in sorted { 1031 let transform = match spatial_tree 1032 .get_world_transform(poly.anchor.spatial_node_index) 1033 .inverse() 1034 { 1035 Some(transform) => transform.into_transform(), 1036 // logging this would be a bit too verbose 1037 None => continue, 1038 }; 1039 1040 let local_points = [ 1041 transform.transform_point3d(poly.points[0].cast_unit().to_f32()), 1042 transform.transform_point3d(poly.points[1].cast_unit().to_f32()), 1043 transform.transform_point3d(poly.points[2].cast_unit().to_f32()), 1044 transform.transform_point3d(poly.points[3].cast_unit().to_f32()), 1045 ]; 1046 1047 // If any of the points are un-transformable, just drop this 1048 // plane from drawing. 1049 if local_points.iter().any(|p| p.is_none()) { 1050 continue; 1051 } 1052 1053 let p0 = local_points[0].unwrap(); 1054 let p1 = local_points[1].unwrap(); 1055 let p2 = local_points[2].unwrap(); 1056 let p3 = local_points[3].unwrap(); 1057 1058 let mut writer = gpu_buffer.write_blocks(2); 1059 writer.push_one([p0.x, p0.y, p1.x, p1.y]); 1060 writer.push_one([p2.x, p2.y, p3.x, p3.y]); 1061 let gpu_address = writer.finish(); 1062 1063 ordered.push(OrderedPictureChild { 1064 anchor: poly.anchor, 1065 gpu_address, 1066 }); 1067 } 1068 } 1069 1070 /// Called during initial picture traversal, before we know the 1071 /// bounding rect of children. It is possible to determine the 1072 /// surface / raster config now though. 1073 pub fn assign_surface( 1074 &mut self, 1075 frame_context: &FrameBuildingContext, 1076 parent_surface_index: Option<SurfaceIndex>, 1077 tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>, 1078 surfaces: &mut Vec<SurfaceInfo>, 1079 ) -> Option<SurfaceIndex> { 1080 // Reset raster config in case we early out below. 1081 self.raster_config = None; 1082 1083 match self.composite_mode { 1084 Some(ref composite_mode) => { 1085 let surface_spatial_node_index = self.spatial_node_index; 1086 1087 // Currently, we ensure that the scaling factor is >= 1.0 as a smaller scale factor can result in blurry output. 1088 let mut min_scale; 1089 let mut max_scale = 1.0e32; 1090 1091 // If a raster root is established, this surface should be scaled based on the scale factors of the surface raster to parent raster transform. 1092 // This scaling helps ensure that the content in this surface does not become blurry or pixelated when composited in the parent surface. 1093 1094 let world_scale_factors = match parent_surface_index { 1095 Some(parent_surface_index) => { 1096 let parent_surface = &surfaces[parent_surface_index.0]; 1097 1098 let local_to_surface = frame_context 1099 .spatial_tree 1100 .get_relative_transform( 1101 surface_spatial_node_index, 1102 parent_surface.surface_spatial_node_index, 1103 ); 1104 1105 // Since we can't determine reasonable scale factors for transforms 1106 // with perspective, just use a scale of (1,1) for now, which is 1107 // what Gecko does when it choosed to supplies a scale factor anyway. 1108 // In future, we might be able to improve the quality here by taking 1109 // into account the screen rect after clipping, but for now this gives 1110 // better results than just taking the matrix scale factors. 1111 let scale_factors = if local_to_surface.is_perspective() { 1112 (1.0, 1.0) 1113 } else { 1114 local_to_surface.scale_factors() 1115 }; 1116 1117 let scale_factors = ( 1118 scale_factors.0 * parent_surface.world_scale_factors.0, 1119 scale_factors.1 * parent_surface.world_scale_factors.1, 1120 ); 1121 1122 scale_factors 1123 } 1124 None => { 1125 let local_to_surface_scale_factors = frame_context 1126 .spatial_tree 1127 .get_relative_transform( 1128 surface_spatial_node_index, 1129 frame_context.spatial_tree.root_reference_frame_index(), 1130 ) 1131 .scale_factors(); 1132 1133 let scale_factors = ( 1134 local_to_surface_scale_factors.0, 1135 local_to_surface_scale_factors.1, 1136 ); 1137 1138 scale_factors 1139 } 1140 }; 1141 1142 // TODO(gw): For now, we disable snapping on any sub-graph, as that implies 1143 // that the spatial / raster node must be the same as the parent 1144 // surface. In future, we may be able to support snapping in these 1145 // cases (if it's even useful?) or perhaps add a ENABLE_SNAPPING 1146 // picture flag, if the IS_SUB_GRAPH is ever useful in a different 1147 // context. 1148 let allow_snapping = !self.flags.contains(PictureFlags::DISABLE_SNAPPING); 1149 1150 // For some primitives (e.g. text runs) we can't rely on the bounding rect being 1151 // exactly correct. For these cases, ensure we set a scissor rect when drawing 1152 // this picture to a surface. 1153 // TODO(gw) In future, we may be able to improve how the text run bounding rect is 1154 // calculated so that we don't need to do this. We could either fix Gecko up to 1155 // provide an exact bounds, or we could calculate the bounding rect internally in 1156 // WR, which would be easier to do efficiently once we have retained text runs 1157 // as part of the planned frame-tree interface changes. 1158 let force_scissor_rect = self.prim_list.needs_scissor_rect; 1159 1160 // Check if there is perspective or if an SVG filter is applied, and thus whether a new 1161 // rasterization root should be established. 1162 let (device_pixel_scale, raster_spatial_node_index, local_scale, world_scale_factors) = match composite_mode { 1163 PictureCompositeMode::TileCache { slice_id } => { 1164 let tile_cache = tile_caches.get_mut(&slice_id).unwrap(); 1165 1166 // Get the complete scale-offset from local space to device space 1167 let local_to_device = get_relative_scale_offset( 1168 tile_cache.spatial_node_index, 1169 frame_context.root_spatial_node_index, 1170 frame_context.spatial_tree, 1171 ); 1172 let local_to_cur_raster_scale = local_to_device.scale.x / tile_cache.current_raster_scale; 1173 1174 // We only update the raster scale if we're in high quality zoom mode, or there is no 1175 // pinch-zoom active, or the zoom has doubled or halved since the raster scale was 1176 // last updated. During a low-quality zoom we therefore typically retain the previous 1177 // scale factor, which avoids expensive re-rasterizations, except for when the zoom 1178 // has become too large or too small when we re-rasterize to avoid bluriness or a 1179 // proliferation of picture cache tiles. When the zoom ends we select a high quality 1180 // scale factor for the next frame to be drawn. 1181 if !frame_context.fb_config.low_quality_pinch_zoom 1182 || !frame_context 1183 .spatial_tree.get_spatial_node(tile_cache.spatial_node_index) 1184 .is_ancestor_or_self_zooming 1185 || local_to_cur_raster_scale <= 0.5 1186 || local_to_cur_raster_scale >= 2.0 1187 { 1188 tile_cache.current_raster_scale = local_to_device.scale.x; 1189 } 1190 1191 // We may need to minify when zooming out picture cache tiles 1192 min_scale = 0.0; 1193 1194 if frame_context.fb_config.low_quality_pinch_zoom { 1195 // Force the scale for this tile cache to be the currently selected 1196 // local raster scale, so we don't need to rasterize tiles during 1197 // the pinch-zoom. 1198 min_scale = tile_cache.current_raster_scale; 1199 max_scale = tile_cache.current_raster_scale; 1200 } 1201 1202 // Pick the largest scale factor of the transform for the scaling factor. 1203 let scaling_factor = world_scale_factors.0.max(world_scale_factors.1).max(min_scale).min(max_scale); 1204 1205 let device_pixel_scale = Scale::new(scaling_factor); 1206 1207 (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors) 1208 } 1209 _ => { 1210 let surface_spatial_node = frame_context.spatial_tree.get_spatial_node(surface_spatial_node_index); 1211 1212 let enable_snapping = 1213 allow_snapping && 1214 surface_spatial_node.coordinate_system_id == CoordinateSystemId::root() && 1215 surface_spatial_node.snapping_transform.is_some(); 1216 1217 if enable_snapping { 1218 let raster_spatial_node_index = frame_context.spatial_tree.root_reference_frame_index(); 1219 1220 let local_to_raster_transform = frame_context 1221 .spatial_tree 1222 .get_relative_transform( 1223 self.spatial_node_index, 1224 raster_spatial_node_index, 1225 ); 1226 1227 let local_scale = local_to_raster_transform.scale_factors(); 1228 1229 (Scale::new(1.0), raster_spatial_node_index, local_scale, (1.0, 1.0)) 1230 } else { 1231 // If client supplied a specific local scale, use that instead of 1232 // estimating from parent transform 1233 let world_scale_factors = match self.raster_space { 1234 RasterSpace::Screen => world_scale_factors, 1235 RasterSpace::Local(scale) => (scale, scale), 1236 }; 1237 1238 let device_pixel_scale = Scale::new( 1239 world_scale_factors.0.max(world_scale_factors.1).min(max_scale) 1240 ); 1241 1242 (device_pixel_scale, surface_spatial_node_index, (1.0, 1.0), world_scale_factors) 1243 } 1244 } 1245 }; 1246 1247 let surface = SurfaceInfo::new( 1248 surface_spatial_node_index, 1249 raster_spatial_node_index, 1250 frame_context.global_screen_world_rect, 1251 &frame_context.spatial_tree, 1252 device_pixel_scale, 1253 world_scale_factors, 1254 local_scale, 1255 allow_snapping, 1256 force_scissor_rect, 1257 ); 1258 1259 let surface_index = SurfaceIndex(surfaces.len()); 1260 1261 surfaces.push(surface); 1262 1263 self.raster_config = Some(RasterConfig { 1264 composite_mode: composite_mode.clone(), 1265 surface_index, 1266 }); 1267 1268 Some(surface_index) 1269 } 1270 None => { 1271 None 1272 } 1273 } 1274 } 1275 1276 /// Called after updating child pictures during the initial 1277 /// picture traversal. Bounding rects are propagated from 1278 /// child pictures up to parent picture surfaces, so that the 1279 /// parent bounding rect includes any dynamic picture bounds. 1280 pub fn propagate_bounding_rect( 1281 &mut self, 1282 surface_index: SurfaceIndex, 1283 parent_surface_index: Option<SurfaceIndex>, 1284 surfaces: &mut [SurfaceInfo], 1285 frame_context: &FrameBuildingContext, 1286 ) { 1287 let surface = &mut surfaces[surface_index.0]; 1288 1289 for cluster in &mut self.prim_list.clusters { 1290 cluster.flags.remove(ClusterFlags::IS_VISIBLE); 1291 1292 // Skip the cluster if backface culled. 1293 if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) { 1294 // For in-preserve-3d primitives and pictures, the backface visibility is 1295 // evaluated relative to the containing block. 1296 if let Picture3DContext::In { ancestor_index, .. } = self.context_3d { 1297 let mut face = VisibleFace::Front; 1298 frame_context.spatial_tree.get_relative_transform_with_face( 1299 cluster.spatial_node_index, 1300 ancestor_index, 1301 Some(&mut face), 1302 ); 1303 if face == VisibleFace::Back { 1304 continue 1305 } 1306 } 1307 } 1308 1309 // No point including this cluster if it can't be transformed 1310 let spatial_node = &frame_context 1311 .spatial_tree 1312 .get_spatial_node(cluster.spatial_node_index); 1313 if !spatial_node.invertible { 1314 continue; 1315 } 1316 1317 // Map the cluster bounding rect into the space of the surface, and 1318 // include it in the surface bounding rect. 1319 surface.map_local_to_picture.set_target_spatial_node( 1320 cluster.spatial_node_index, 1321 frame_context.spatial_tree, 1322 ); 1323 1324 // Mark the cluster visible, since it passed the invertible and 1325 // backface checks. 1326 cluster.flags.insert(ClusterFlags::IS_VISIBLE); 1327 if let Some(cluster_rect) = surface.map_local_to_picture.map(&cluster.bounding_rect) { 1328 surface.unclipped_local_rect = surface.unclipped_local_rect.union(&cluster_rect); 1329 } 1330 } 1331 1332 // If this picture establishes a surface, then map the surface bounding 1333 // rect into the parent surface coordinate space, and propagate that up 1334 // to the parent. 1335 if let Some(ref mut raster_config) = self.raster_config { 1336 // Propagate up to parent surface, now that we know this surface's static rect 1337 if let Some(parent_surface_index) = parent_surface_index { 1338 let surface_rect = raster_config.composite_mode.get_coverage( 1339 surface, 1340 Some(surface.unclipped_local_rect.cast_unit()), 1341 ); 1342 1343 let parent_surface = &mut surfaces[parent_surface_index.0]; 1344 parent_surface.map_local_to_picture.set_target_spatial_node( 1345 self.spatial_node_index, 1346 frame_context.spatial_tree, 1347 ); 1348 1349 // Drop shadows draw both a content and shadow rect, so need to expand the local 1350 // rect of any surfaces to be composited in parent surfaces correctly. 1351 1352 if let Some(parent_surface_rect) = parent_surface 1353 .map_local_to_picture 1354 .map(&surface_rect) 1355 { 1356 parent_surface.unclipped_local_rect = 1357 parent_surface.unclipped_local_rect.union(&parent_surface_rect); 1358 } 1359 } 1360 } 1361 } 1362 1363 pub fn write_gpu_blocks( 1364 &mut self, 1365 frame_state: &mut FrameBuildingState, 1366 data_stores: &mut DataStores, 1367 ) { 1368 let raster_config = match self.raster_config { 1369 Some(ref mut raster_config) => raster_config, 1370 None => { 1371 return; 1372 } 1373 }; 1374 1375 raster_config.composite_mode.write_gpu_blocks( 1376 &frame_state.surfaces[raster_config.surface_index.0], 1377 &mut frame_state.frame_gpu_data, 1378 data_stores, 1379 &mut self.extra_gpu_data 1380 ); 1381 } 1382 1383 #[cold] 1384 fn draw_debug_overlay( 1385 &self, 1386 parent_surface_index: Option<SurfaceIndex>, 1387 frame_state: &FrameBuildingState, 1388 frame_context: &FrameBuildingContext, 1389 tile_caches: &FastHashMap<SliceId, Box<TileCacheInstance>>, 1390 scratch: &mut PrimitiveScratchBuffer, 1391 ) { 1392 fn draw_debug_border( 1393 local_rect: &PictureRect, 1394 thickness: i32, 1395 pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>, 1396 global_device_pixel_scale: DevicePixelScale, 1397 color: ColorF, 1398 scratch: &mut PrimitiveScratchBuffer, 1399 ) { 1400 if let Some(world_rect) = pic_to_world_mapper.map(&local_rect) { 1401 let device_rect = world_rect * global_device_pixel_scale; 1402 scratch.push_debug_rect( 1403 device_rect, 1404 thickness, 1405 color, 1406 ColorF::TRANSPARENT, 1407 ); 1408 } 1409 } 1410 1411 let flags = frame_context.debug_flags; 1412 let draw_borders = flags.contains(DebugFlags::PICTURE_BORDERS); 1413 let draw_tile_dbg = flags.contains(DebugFlags::PICTURE_CACHING_DBG); 1414 1415 let surface_index = match &self.raster_config { 1416 Some(raster_config) => raster_config.surface_index, 1417 None => parent_surface_index.expect("bug: no parent"), 1418 }; 1419 let surface_spatial_node_index = frame_state 1420 .surfaces[surface_index.0] 1421 .surface_spatial_node_index; 1422 1423 let map_pic_to_world = SpaceMapper::new_with_target( 1424 frame_context.root_spatial_node_index, 1425 surface_spatial_node_index, 1426 frame_context.global_screen_world_rect, 1427 frame_context.spatial_tree, 1428 ); 1429 1430 let Some(raster_config) = &self.raster_config else { 1431 return; 1432 }; 1433 1434 if draw_borders { 1435 let layer_color; 1436 if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode { 1437 // Tiled picture; 1438 layer_color = ColorF::new(0.0, 1.0, 0.0, 0.8); 1439 1440 let Some(tile_cache) = tile_caches.get(&slice_id) else { 1441 return; 1442 }; 1443 1444 // Draw a rectangle for each tile. 1445 for (_, sub_slice) in tile_cache.sub_slices.iter().enumerate() { 1446 for tile in sub_slice.tiles.values() { 1447 if !tile.is_visible { 1448 continue; 1449 } 1450 let rect = tile.cached_surface.local_rect.intersection(&tile_cache.local_rect); 1451 if let Some(rect) = rect { 1452 draw_debug_border( 1453 &rect, 1454 1, 1455 &map_pic_to_world, 1456 frame_context.global_device_pixel_scale, 1457 ColorF::new(0.0, 1.0, 0.0, 0.2), 1458 scratch, 1459 ); 1460 } 1461 } 1462 } 1463 } else { 1464 // Non-tiled picture 1465 layer_color = ColorF::new(1.0, 0.0, 0.0, 0.5); 1466 } 1467 1468 // Draw a rectangle for the whole picture. 1469 let pic_rect = frame_state 1470 .surfaces[raster_config.surface_index.0] 1471 .unclipped_local_rect; 1472 1473 draw_debug_border( 1474 &pic_rect, 1475 3, 1476 &map_pic_to_world, 1477 frame_context.global_device_pixel_scale, 1478 layer_color, 1479 scratch, 1480 ); 1481 } 1482 1483 if draw_tile_dbg && self.is_visible(frame_context.spatial_tree) { 1484 if let PictureCompositeMode::TileCache { slice_id } = &raster_config.composite_mode { 1485 let Some(tile_cache) = tile_caches.get(&slice_id) else { 1486 return; 1487 }; 1488 for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter().enumerate() { 1489 for tile in sub_slice.tiles.values() { 1490 if !tile.is_visible { 1491 continue; 1492 } 1493 tile.cached_surface.root.draw_debug_rects( 1494 &map_pic_to_world, 1495 tile.is_opaque, 1496 tile.cached_surface.current_descriptor.local_valid_rect, 1497 scratch, 1498 frame_context.global_device_pixel_scale, 1499 ); 1500 1501 let label_offset = DeviceVector2D::new( 1502 20.0 + sub_slice_index as f32 * 20.0, 1503 30.0 + sub_slice_index as f32 * 20.0, 1504 ); 1505 let tile_device_rect = tile.world_tile_rect 1506 * frame_context.global_device_pixel_scale; 1507 1508 if tile_device_rect.height() >= label_offset.y { 1509 let surface = tile.surface.as_ref().expect("no tile surface set!"); 1510 1511 scratch.push_debug_string( 1512 tile_device_rect.min + label_offset, 1513 debug_colors::RED, 1514 format!("{:?}: s={} is_opaque={} surface={} sub={}", 1515 tile.id, 1516 tile_cache.slice, 1517 tile.is_opaque, 1518 surface.kind(), 1519 sub_slice_index, 1520 ), 1521 ); 1522 } 1523 } 1524 } 1525 } 1526 } 1527 } 1528 } 1529 1530 pub fn get_relative_scale_offset( 1531 child_spatial_node_index: SpatialNodeIndex, 1532 parent_spatial_node_index: SpatialNodeIndex, 1533 spatial_tree: &SpatialTree, 1534 ) -> ScaleOffset { 1535 let transform = spatial_tree.get_relative_transform( 1536 child_spatial_node_index, 1537 parent_spatial_node_index, 1538 ); 1539 let mut scale_offset = match transform { 1540 CoordinateSpaceMapping::Local => ScaleOffset::identity(), 1541 CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset, 1542 CoordinateSpaceMapping::Transform(m) => { 1543 ScaleOffset::from_transform(&m).expect("bug: pictures caches don't support complex transforms") 1544 } 1545 }; 1546 1547 // Compositors expect things to be aligned on device pixels. Logic at a higher level ensures that is 1548 // true, but floating point inaccuracy can sometimes result in small differences, so remove 1549 // them here. 1550 scale_offset.offset = scale_offset.offset.round(); 1551 1552 scale_offset 1553 } 1554 1555 /// Update dirty rects, ensure that tiles have backing surfaces and build 1556 /// the tile render tasks. 1557 fn prepare_tiled_picture_surface( 1558 surface_index: SurfaceIndex, 1559 slice_id: SliceId, 1560 surface_spatial_node_index: SpatialNodeIndex, 1561 map_pic_to_world: &SpaceMapper<PicturePixel, WorldPixel>, 1562 frame_context: &FrameBuildingContext, 1563 frame_state: &mut FrameBuildingState, 1564 tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>, 1565 ) { 1566 let tile_cache = tile_caches.get_mut(&slice_id).unwrap(); 1567 let mut debug_info = SliceDebugInfo::new(); 1568 let mut surface_render_tasks = FastHashMap::default(); 1569 let mut surface_local_dirty_rect = PictureRect::zero(); 1570 let device_pixel_scale = frame_state 1571 .surfaces[surface_index.0] 1572 .device_pixel_scale; 1573 let mut at_least_one_tile_visible = false; 1574 1575 // Get the overall world space rect of the picture cache. Used to clip 1576 // the tile rects below for occlusion testing to the relevant area. 1577 let world_clip_rect = map_pic_to_world 1578 .map(&tile_cache.local_clip_rect) 1579 .expect("bug: unable to map clip rect") 1580 .round(); 1581 let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); 1582 1583 for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter_mut().enumerate() { 1584 for tile in sub_slice.tiles.values_mut() { 1585 // Ensure that the dirty rect doesn't extend outside the local valid rect. 1586 tile.cached_surface.local_dirty_rect = tile.cached_surface.local_dirty_rect 1587 .intersection(&tile.cached_surface.current_descriptor.local_valid_rect) 1588 .unwrap_or_else(|| { tile.cached_surface.is_valid = true; PictureRect::zero() }); 1589 1590 let valid_rect = frame_state.composite_state.get_surface_rect( 1591 &tile.cached_surface.current_descriptor.local_valid_rect, 1592 &tile.cached_surface.local_rect, 1593 tile_cache.transform_index, 1594 ).to_i32(); 1595 1596 let scissor_rect = frame_state.composite_state.get_surface_rect( 1597 &tile.cached_surface.local_dirty_rect, 1598 &tile.cached_surface.local_rect, 1599 tile_cache.transform_index, 1600 ).to_i32().intersection(&valid_rect).unwrap_or_else(|| { Box2D::zero() }); 1601 1602 if tile.is_visible { 1603 // Get the world space rect that this tile will actually occupy on screen 1604 let world_draw_rect = world_clip_rect.intersection(&tile.world_valid_rect); 1605 1606 // If that draw rect is occluded by some set of tiles in front of it, 1607 // then mark it as not visible and skip drawing. When it's not occluded 1608 // it will fail this test, and get rasterized by the render task setup 1609 // code below. 1610 match world_draw_rect { 1611 Some(world_draw_rect) => { 1612 let check_occluded_tiles = match frame_state.composite_state.compositor_kind { 1613 CompositorKind::Layer { .. } => true, 1614 CompositorKind::Native { .. } | CompositorKind::Draw { .. } => { 1615 // Only check for occlusion on visible tiles that are fixed position. 1616 tile_cache.spatial_node_index == frame_context.root_spatial_node_index 1617 } 1618 }; 1619 if check_occluded_tiles && 1620 frame_state.composite_state.occluders.is_tile_occluded(tile.z_id, world_draw_rect) { 1621 // If this tile has an allocated native surface, free it, since it's completely 1622 // occluded. We will need to re-allocate this surface if it becomes visible, 1623 // but that's likely to be rare (e.g. when there is no content display list 1624 // for a frame or two during a tab switch). 1625 let surface = tile.surface.as_mut().expect("no tile surface set!"); 1626 1627 if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface { 1628 if let Some(id) = id.take() { 1629 frame_state.resource_cache.destroy_compositor_tile(id); 1630 } 1631 } 1632 1633 tile.is_visible = false; 1634 1635 if frame_context.fb_config.testing { 1636 debug_info.tiles.insert( 1637 tile.tile_offset, 1638 TileDebugInfo::Occluded, 1639 ); 1640 } 1641 1642 continue; 1643 } 1644 } 1645 None => { 1646 tile.is_visible = false; 1647 } 1648 } 1649 1650 // In extreme zoom/offset cases, we may end up with a local scissor/valid rect 1651 // that becomes empty after transformation to device space (e.g. if the local 1652 // rect height is 0.00001 and the compositor transform has large scale + offset). 1653 // DirectComposition panics if we try to BeginDraw with an empty rect, so catch 1654 // that here and mark the tile non-visible. This is a bit of a hack - we should 1655 // ideally handle these in a more accurate way so we don't end up with an empty 1656 // rect here. 1657 if !tile.cached_surface.is_valid && (scissor_rect.is_empty() || valid_rect.is_empty()) { 1658 tile.is_visible = false; 1659 } 1660 } 1661 1662 // If we get here, we want to ensure that the surface remains valid in the texture 1663 // cache, _even if_ it's not visible due to clipping or being scrolled off-screen. 1664 // This ensures that we retain valid tiles that are off-screen, but still in the 1665 // display port of this tile cache instance. 1666 if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() { 1667 if let SurfaceTextureDescriptor::TextureCache { handle: Some(handle), .. } = descriptor { 1668 frame_state.resource_cache 1669 .picture_textures.request(handle); 1670 } 1671 } 1672 1673 // If the tile has been found to be off-screen / clipped, skip any further processing. 1674 if !tile.is_visible { 1675 if frame_context.fb_config.testing { 1676 debug_info.tiles.insert( 1677 tile.tile_offset, 1678 TileDebugInfo::Culled, 1679 ); 1680 } 1681 1682 continue; 1683 } 1684 1685 at_least_one_tile_visible = true; 1686 1687 if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() { 1688 match descriptor { 1689 SurfaceTextureDescriptor::TextureCache { ref handle, .. } => { 1690 let exists = handle.as_ref().map_or(false, 1691 |handle| frame_state.resource_cache.picture_textures.entry_exists(handle) 1692 ); 1693 // Invalidate if the backing texture was evicted. 1694 if exists { 1695 // Request the backing texture so it won't get evicted this frame. 1696 // We specifically want to mark the tile texture as used, even 1697 // if it's detected not visible below and skipped. This is because 1698 // we maintain the set of tiles we care about based on visibility 1699 // during pre_update. If a tile still exists after that, we are 1700 // assuming that it's either visible or we want to retain it for 1701 // a while in case it gets scrolled back onto screen soon. 1702 // TODO(gw): Consider switching to manual eviction policy? 1703 frame_state.resource_cache 1704 .picture_textures 1705 .request(handle.as_ref().unwrap()); 1706 } else { 1707 // If the texture was evicted on a previous frame, we need to assume 1708 // that the entire tile rect is dirty. 1709 tile.invalidate(None, InvalidationReason::NoTexture); 1710 } 1711 } 1712 SurfaceTextureDescriptor::Native { id, .. } => { 1713 if id.is_none() { 1714 // There is no current surface allocation, so ensure the entire tile is invalidated 1715 tile.invalidate(None, InvalidationReason::NoSurface); 1716 } 1717 } 1718 } 1719 } 1720 1721 // Ensure - again - that the dirty rect doesn't extend outside the local valid rect, 1722 // as the tile could have been invalidated since the first computation. 1723 tile.cached_surface.local_dirty_rect = tile.cached_surface.local_dirty_rect 1724 .intersection(&tile.cached_surface.current_descriptor.local_valid_rect) 1725 .unwrap_or_else(|| { tile.cached_surface.is_valid = true; PictureRect::zero() }); 1726 1727 surface_local_dirty_rect = surface_local_dirty_rect.union(&tile.cached_surface.local_dirty_rect); 1728 1729 // Update the world/device dirty rect 1730 let world_dirty_rect = map_pic_to_world.map(&tile.cached_surface.local_dirty_rect).expect("bug"); 1731 1732 let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round(); 1733 tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale) 1734 .round_out() 1735 .intersection(&device_rect) 1736 .unwrap_or_else(DeviceRect::zero); 1737 1738 if tile.cached_surface.is_valid { 1739 if frame_context.fb_config.testing { 1740 debug_info.tiles.insert( 1741 tile.tile_offset, 1742 TileDebugInfo::Valid, 1743 ); 1744 } 1745 } else { 1746 // Add this dirty rect to the dirty region tracker. This must be done outside the if statement below, 1747 // so that we include in the dirty region tiles that are handled by a background color only (no 1748 // surface allocation). 1749 tile_cache.dirty_region.add_dirty_region( 1750 tile.cached_surface.local_dirty_rect, 1751 frame_context.spatial_tree, 1752 ); 1753 1754 // Ensure that this texture is allocated. 1755 if let TileSurface::Texture { ref mut descriptor } = tile.surface.as_mut().unwrap() { 1756 match descriptor { 1757 SurfaceTextureDescriptor::TextureCache { ref mut handle } => { 1758 1759 frame_state.resource_cache.picture_textures.update( 1760 tile_cache.current_tile_size, 1761 handle, 1762 &mut frame_state.resource_cache.texture_cache.next_id, 1763 &mut frame_state.resource_cache.texture_cache.pending_updates, 1764 ); 1765 } 1766 SurfaceTextureDescriptor::Native { id } => { 1767 if id.is_none() { 1768 // Allocate a native surface id if we're in native compositing mode, 1769 // and we don't have a surface yet (due to first frame, or destruction 1770 // due to tile size changing etc). 1771 if sub_slice.native_surface.is_none() { 1772 let opaque = frame_state 1773 .resource_cache 1774 .create_compositor_surface( 1775 tile_cache.virtual_offset, 1776 tile_cache.current_tile_size, 1777 true, 1778 ); 1779 1780 let alpha = frame_state 1781 .resource_cache 1782 .create_compositor_surface( 1783 tile_cache.virtual_offset, 1784 tile_cache.current_tile_size, 1785 false, 1786 ); 1787 1788 sub_slice.native_surface = Some(NativeSurface { 1789 opaque, 1790 alpha, 1791 }); 1792 } 1793 1794 // Create the tile identifier and allocate it. 1795 let surface_id = if tile.is_opaque { 1796 sub_slice.native_surface.as_ref().unwrap().opaque 1797 } else { 1798 sub_slice.native_surface.as_ref().unwrap().alpha 1799 }; 1800 1801 let tile_id = NativeTileId { 1802 surface_id, 1803 x: tile.tile_offset.x, 1804 y: tile.tile_offset.y, 1805 }; 1806 1807 frame_state.resource_cache.create_compositor_tile(tile_id); 1808 1809 *id = Some(tile_id); 1810 } 1811 } 1812 } 1813 1814 // The cast_unit() here is because the `content_origin` is expected to be in 1815 // device pixels, however we're establishing raster roots for picture cache 1816 // tiles meaning the `content_origin` needs to be in the local space of that root. 1817 // TODO(gw): `content_origin` should actually be in RasterPixels to be consistent 1818 // with both local / screen raster modes, but this involves a lot of 1819 // changes to render task and picture code. 1820 let content_origin_f = tile.cached_surface.local_rect.min.cast_unit() * device_pixel_scale; 1821 let content_origin = content_origin_f.round(); 1822 // TODO: these asserts used to have a threshold of 0.01 but failed intermittently the 1823 // gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html test on android. 1824 // moving the rectangles in space mapping conversion code to the Box2D representaton 1825 // made the failure happen more often. 1826 debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.15); 1827 debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.15); 1828 1829 let surface = descriptor.resolve( 1830 frame_state.resource_cache, 1831 tile_cache.current_tile_size, 1832 ); 1833 1834 // Recompute the scissor rect as the tile could have been invalidated since the first computation. 1835 let scissor_rect = frame_state.composite_state.get_surface_rect( 1836 &tile.cached_surface.local_dirty_rect, 1837 &tile.cached_surface.local_rect, 1838 tile_cache.transform_index, 1839 ).to_i32(); 1840 1841 let composite_task_size = tile_cache.current_tile_size; 1842 1843 let tile_key = TileKey { 1844 sub_slice_index: SubSliceIndex::new(sub_slice_index), 1845 tile_offset: tile.tile_offset, 1846 }; 1847 1848 let mut clear_color = ColorF::TRANSPARENT; 1849 1850 if SubSliceIndex::new(sub_slice_index).is_primary() { 1851 if let Some(background_color) = tile_cache.background_color { 1852 clear_color = background_color; 1853 } 1854 1855 // If this picture cache has a spanning_opaque_color, we will use 1856 // that as the clear color. The primitive that was detected as a 1857 // spanning primitive will have been set with IS_BACKDROP, causing 1858 // it to be skipped and removing everything added prior to it 1859 // during batching. 1860 if let Some(color) = tile_cache.backdrop.spanning_opaque_color { 1861 clear_color = color; 1862 } 1863 } 1864 1865 let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer(); 1866 1867 // TODO(gw): As a performance optimization, we could skip the resolve picture 1868 // if the dirty rect is the same as the resolve rect (probably quite 1869 // common for effects that scroll underneath a backdrop-filter, for example). 1870 let use_tile_composite = !tile.cached_surface.sub_graphs.is_empty(); 1871 1872 if use_tile_composite { 1873 let mut local_content_rect = tile.cached_surface.local_dirty_rect; 1874 1875 for (sub_graph_rect, surface_stack) in &tile.cached_surface.sub_graphs { 1876 if let Some(dirty_sub_graph_rect) = sub_graph_rect.intersection(&tile.cached_surface.local_dirty_rect) { 1877 for (composite_mode, surface_index) in surface_stack { 1878 let surface = &frame_state.surfaces[surface_index.0]; 1879 1880 let rect = composite_mode.get_coverage( 1881 surface, 1882 Some(dirty_sub_graph_rect.cast_unit()), 1883 ).cast_unit(); 1884 1885 local_content_rect = local_content_rect.union(&rect); 1886 } 1887 } 1888 } 1889 1890 // We know that we'll never need to sample > 300 device pixels outside the tile 1891 // for blurring, so clamp the content rect here so that we don't try to allocate 1892 // a really large surface in the case of a drop-shadow with large offset. 1893 let max_content_rect = (tile.cached_surface.local_dirty_rect.cast_unit() * device_pixel_scale) 1894 .inflate( 1895 MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE, 1896 MAX_BLUR_RADIUS * BLUR_SAMPLE_SCALE, 1897 ) 1898 .round_out() 1899 .to_i32(); 1900 1901 let content_device_rect = (local_content_rect.cast_unit() * device_pixel_scale) 1902 .round_out() 1903 .to_i32(); 1904 1905 let content_device_rect = content_device_rect 1906 .intersection(&max_content_rect) 1907 .expect("bug: no intersection with tile dirty rect: {content_device_rect:?} / {max_content_rect:?}"); 1908 1909 let content_task_size = content_device_rect.size(); 1910 let normalized_content_rect = content_task_size.into(); 1911 1912 let inner_offset = content_origin + scissor_rect.min.to_vector().to_f32(); 1913 let outer_offset = content_device_rect.min.to_f32(); 1914 let sub_rect_offset = (inner_offset - outer_offset).round().to_i32(); 1915 1916 let render_task_id = frame_state.rg_builder.add().init( 1917 RenderTask::new_dynamic( 1918 content_task_size, 1919 RenderTaskKind::new_picture( 1920 content_task_size, 1921 true, 1922 content_device_rect.min.to_f32(), 1923 surface_spatial_node_index, 1924 // raster == surface implicitly for picture cache tiles 1925 surface_spatial_node_index, 1926 device_pixel_scale, 1927 Some(normalized_content_rect), 1928 None, 1929 Some(clear_color), 1930 cmd_buffer_index, 1931 false, 1932 None, 1933 ) 1934 ), 1935 ); 1936 1937 let composite_task_id = frame_state.rg_builder.add().init( 1938 RenderTask::new( 1939 RenderTaskLocation::Static { 1940 surface: StaticRenderTaskSurface::PictureCache { 1941 surface, 1942 }, 1943 rect: composite_task_size.into(), 1944 }, 1945 RenderTaskKind::new_tile_composite( 1946 sub_rect_offset, 1947 scissor_rect, 1948 valid_rect, 1949 clear_color, 1950 ), 1951 ), 1952 ); 1953 1954 surface_render_tasks.insert( 1955 tile_key, 1956 SurfaceTileDescriptor { 1957 current_task_id: render_task_id, 1958 composite_task_id: Some(composite_task_id), 1959 dirty_rect: tile.cached_surface.local_dirty_rect, 1960 }, 1961 ); 1962 } else { 1963 let render_task_id = frame_state.rg_builder.add().init( 1964 RenderTask::new( 1965 RenderTaskLocation::Static { 1966 surface: StaticRenderTaskSurface::PictureCache { 1967 surface, 1968 }, 1969 rect: composite_task_size.into(), 1970 }, 1971 RenderTaskKind::new_picture( 1972 composite_task_size, 1973 true, 1974 content_origin, 1975 surface_spatial_node_index, 1976 // raster == surface implicitly for picture cache tiles 1977 surface_spatial_node_index, 1978 device_pixel_scale, 1979 Some(scissor_rect), 1980 Some(valid_rect), 1981 Some(clear_color), 1982 cmd_buffer_index, 1983 false, 1984 None, 1985 ) 1986 ), 1987 ); 1988 1989 surface_render_tasks.insert( 1990 tile_key, 1991 SurfaceTileDescriptor { 1992 current_task_id: render_task_id, 1993 composite_task_id: None, 1994 dirty_rect: tile.cached_surface.local_dirty_rect, 1995 }, 1996 ); 1997 } 1998 } 1999 2000 if frame_context.fb_config.testing { 2001 debug_info.tiles.insert( 2002 tile.tile_offset, 2003 TileDebugInfo::Dirty(DirtyTileDebugInfo { 2004 local_valid_rect: tile.cached_surface.current_descriptor.local_valid_rect, 2005 local_dirty_rect: tile.cached_surface.local_dirty_rect, 2006 }), 2007 ); 2008 } 2009 } 2010 2011 let surface = tile.surface.as_ref().expect("no tile surface set!"); 2012 2013 let descriptor = CompositeTileDescriptor { 2014 surface_kind: surface.into(), 2015 tile_id: tile.id, 2016 }; 2017 2018 let (surface, is_opaque) = match surface { 2019 TileSurface::Color { color } => { 2020 (CompositeTileSurface::Color { color: *color }, true) 2021 } 2022 TileSurface::Texture { descriptor, .. } => { 2023 let surface = descriptor.resolve(frame_state.resource_cache, tile_cache.current_tile_size); 2024 ( 2025 CompositeTileSurface::Texture { surface }, 2026 tile.is_opaque 2027 ) 2028 } 2029 }; 2030 2031 if is_opaque { 2032 sub_slice.opaque_tile_descriptors.push(descriptor); 2033 } else { 2034 sub_slice.alpha_tile_descriptors.push(descriptor); 2035 } 2036 2037 let composite_tile = CompositeTile { 2038 kind: tile_kind(&surface, is_opaque), 2039 surface, 2040 local_rect: tile.cached_surface.local_rect, 2041 local_valid_rect: tile.cached_surface.current_descriptor.local_valid_rect, 2042 local_dirty_rect: tile.cached_surface.local_dirty_rect, 2043 device_clip_rect, 2044 z_id: tile.z_id, 2045 transform_index: tile_cache.transform_index, 2046 clip_index: tile_cache.compositor_clip, 2047 tile_id: Some(tile.id), 2048 }; 2049 2050 sub_slice.composite_tiles.push(composite_tile); 2051 2052 // Now that the tile is valid, reset the dirty rect. 2053 tile.cached_surface.local_dirty_rect = PictureRect::zero(); 2054 tile.cached_surface.is_valid = true; 2055 } 2056 2057 // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles 2058 // hashmap doesn't provide any ordering guarantees, but we want to detect the 2059 // composite descriptor as equal if the tiles list is the same, regardless of 2060 // ordering. 2061 sub_slice.opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id); 2062 sub_slice.alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id); 2063 } 2064 2065 // Check to see if we should add backdrops as native surfaces. 2066 let backdrop_rect = tile_cache.backdrop.backdrop_rect 2067 .intersection(&tile_cache.local_rect) 2068 .and_then(|r| { 2069 r.intersection(&tile_cache.local_clip_rect) 2070 }); 2071 2072 let mut backdrop_in_use_and_visible = false; 2073 if let Some(backdrop_rect) = backdrop_rect { 2074 let supports_surface_for_backdrop = match frame_state.composite_state.compositor_kind { 2075 CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { 2076 false 2077 } 2078 CompositorKind::Native { capabilities, .. } => { 2079 capabilities.supports_surface_for_backdrop 2080 } 2081 }; 2082 if supports_surface_for_backdrop && !tile_cache.found_prims_after_backdrop && at_least_one_tile_visible { 2083 if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind { 2084 backdrop_in_use_and_visible = true; 2085 2086 // We're going to let the compositor handle the backdrop as a native surface. 2087 // Hide all of our sub_slice tiles so they aren't also trying to draw it. 2088 for sub_slice in &mut tile_cache.sub_slices { 2089 for tile in sub_slice.tiles.values_mut() { 2090 tile.is_visible = false; 2091 } 2092 } 2093 2094 // Destroy our backdrop surface if it doesn't match the new color. 2095 // TODO: This is a performance hit for animated color backdrops. 2096 if let Some(backdrop_surface) = &tile_cache.backdrop_surface { 2097 if backdrop_surface.color != color { 2098 frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id); 2099 tile_cache.backdrop_surface = None; 2100 } 2101 } 2102 2103 // Calculate the device_rect for the backdrop, which is just the backdrop_rect 2104 // converted into world space and scaled to device pixels. 2105 let world_backdrop_rect = map_pic_to_world.map(&backdrop_rect).expect("bug: unable to map backdrop rect"); 2106 let device_rect = (world_backdrop_rect * frame_context.global_device_pixel_scale).round(); 2107 2108 // If we already have a backdrop surface, update the device rect. Otherwise, create 2109 // a backdrop surface. 2110 if let Some(backdrop_surface) = &mut tile_cache.backdrop_surface { 2111 backdrop_surface.device_rect = device_rect; 2112 } else { 2113 // Create native compositor surface with color for the backdrop and store the id. 2114 tile_cache.backdrop_surface = Some(BackdropSurface { 2115 id: frame_state.resource_cache.create_compositor_backdrop_surface(color), 2116 color, 2117 device_rect, 2118 }); 2119 } 2120 } 2121 } 2122 } 2123 2124 if !backdrop_in_use_and_visible { 2125 if let Some(backdrop_surface) = &tile_cache.backdrop_surface { 2126 // We've already allocated a backdrop surface, but we're not using it. 2127 // Tell the compositor to get rid of it. 2128 frame_state.resource_cache.destroy_compositor_surface(backdrop_surface.id); 2129 tile_cache.backdrop_surface = None; 2130 } 2131 } 2132 2133 // If invalidation debugging is enabled, dump the picture cache state to a tree printer. 2134 if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) { 2135 tile_cache.print(); 2136 } 2137 2138 // If testing mode is enabled, write some information about the current state 2139 // of this picture cache (made available in RenderResults). 2140 if frame_context.fb_config.testing { 2141 frame_state.composite_state 2142 .picture_cache_debug 2143 .slices 2144 .insert( 2145 tile_cache.slice, 2146 debug_info, 2147 ); 2148 } 2149 2150 let descriptor = SurfaceDescriptor::new_tiled(surface_render_tasks); 2151 2152 frame_state.surface_builder.push_surface( 2153 surface_index, 2154 false, 2155 surface_local_dirty_rect, 2156 Some(descriptor), 2157 frame_state.surfaces, 2158 frame_state.rg_builder, 2159 ); 2160 } 2161 2162 fn compute_subpixel_mode( 2163 raster_config: &Option<RasterConfig>, 2164 tile_caches: &FastHashMap<SliceId, Box<TileCacheInstance>>, 2165 parent_subpixel_mode: SubpixelMode, 2166 ) -> SubpixelMode { 2167 2168 // Disallow subpixel AA if an intermediate surface is needed. 2169 // TODO(lsalzman): allow overriding parent if intermediate surface is opaque 2170 let subpixel_mode = match raster_config { 2171 Some(RasterConfig { ref composite_mode, .. }) => { 2172 let subpixel_mode = match composite_mode { 2173 PictureCompositeMode::TileCache { slice_id } => { 2174 tile_caches[&slice_id].subpixel_mode 2175 } 2176 PictureCompositeMode::Blit(..) | 2177 PictureCompositeMode::ComponentTransferFilter(..) | 2178 PictureCompositeMode::Filter(..) | 2179 PictureCompositeMode::MixBlend(..) | 2180 PictureCompositeMode::IntermediateSurface | 2181 PictureCompositeMode::SVGFEGraph(..) => { 2182 // TODO(gw): We can take advantage of the same logic that 2183 // exists in the opaque rect detection for tile 2184 // caches, to allow subpixel text on other surfaces 2185 // that can be detected as opaque. 2186 SubpixelMode::Deny 2187 } 2188 }; 2189 2190 subpixel_mode 2191 } 2192 None => { 2193 SubpixelMode::Allow 2194 } 2195 }; 2196 2197 // Still disable subpixel AA if parent forbids it 2198 let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) { 2199 (SubpixelMode::Allow, SubpixelMode::Allow) => { 2200 // Both parent and this surface unconditionally allow subpixel AA 2201 SubpixelMode::Allow 2202 } 2203 (SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect, prohibited_rect }) => { 2204 // Parent allows, but we are conditional subpixel AA 2205 SubpixelMode::Conditional { 2206 allowed_rect, 2207 prohibited_rect, 2208 } 2209 } 2210 (SubpixelMode::Conditional { allowed_rect, prohibited_rect }, SubpixelMode::Allow) => { 2211 // Propagate conditional subpixel mode to child pictures that allow subpixel AA 2212 SubpixelMode::Conditional { 2213 allowed_rect, 2214 prohibited_rect, 2215 } 2216 } 2217 (SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => { 2218 unreachable!("bug: only top level picture caches have conditional subpixel"); 2219 } 2220 (SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => { 2221 // Either parent or this surface explicitly deny subpixel, these take precedence 2222 SubpixelMode::Deny 2223 } 2224 }; 2225 2226 subpixel_mode 2227 } 2228 2229 #[test] 2230 fn test_large_surface_scale_1() { 2231 use crate::spatial_tree::{SceneSpatialTree, SpatialTree}; 2232 2233 let mut cst = SceneSpatialTree::new(); 2234 let root_reference_frame_index = cst.root_reference_frame_index(); 2235 2236 let mut spatial_tree = SpatialTree::new(); 2237 spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates()); 2238 spatial_tree.update_tree(&SceneProperties::new()); 2239 2240 let map_local_to_picture = SpaceMapper::new_with_target( 2241 root_reference_frame_index, 2242 root_reference_frame_index, 2243 PictureRect::max_rect(), 2244 &spatial_tree, 2245 ); 2246 2247 let mut surfaces = vec![ 2248 SurfaceInfo { 2249 unclipped_local_rect: PictureRect::max_rect(), 2250 clipped_local_rect: PictureRect::max_rect(), 2251 is_opaque: true, 2252 clipping_rect: PictureRect::max_rect(), 2253 culling_rect: VisRect::max_rect(), 2254 map_local_to_picture: map_local_to_picture.clone(), 2255 raster_spatial_node_index: root_reference_frame_index, 2256 surface_spatial_node_index: root_reference_frame_index, 2257 visibility_spatial_node_index: root_reference_frame_index, 2258 device_pixel_scale: DevicePixelScale::new(1.0), 2259 world_scale_factors: (1.0, 1.0), 2260 local_scale: (1.0, 1.0), 2261 allow_snapping: true, 2262 force_scissor_rect: false, 2263 }, 2264 SurfaceInfo { 2265 unclipped_local_rect: PictureRect::new( 2266 PicturePoint::new(52.76350021362305, 0.0), 2267 PicturePoint::new(159.6738739013672, 35.0), 2268 ), 2269 clipped_local_rect: PictureRect::max_rect(), 2270 is_opaque: true, 2271 clipping_rect: PictureRect::max_rect(), 2272 culling_rect: VisRect::max_rect(), 2273 map_local_to_picture, 2274 raster_spatial_node_index: root_reference_frame_index, 2275 surface_spatial_node_index: root_reference_frame_index, 2276 visibility_spatial_node_index: root_reference_frame_index, 2277 device_pixel_scale: DevicePixelScale::new(43.82798767089844), 2278 world_scale_factors: (1.0, 1.0), 2279 local_scale: (1.0, 1.0), 2280 allow_snapping: true, 2281 force_scissor_rect: false, 2282 }, 2283 ]; 2284 2285 get_surface_rects( 2286 SurfaceIndex(1), 2287 &PictureCompositeMode::Blit(BlitReason::BLEND_MODE), 2288 SurfaceIndex(0), 2289 &mut surfaces, 2290 &spatial_tree, 2291 MAX_SURFACE_SIZE as f32, 2292 false, 2293 ); 2294 } 2295 2296 #[test] 2297 fn test_drop_filter_dirty_region_outside_prim() { 2298 // Ensure that if we have a drop-filter where the content of the 2299 // shadow is outside the dirty rect, but blurred pixels from that 2300 // content will affect the dirty rect, that we correctly calculate 2301 // the required region of the drop-filter input 2302 2303 use api::Shadow; 2304 use crate::spatial_tree::{SceneSpatialTree, SpatialTree}; 2305 2306 let mut cst = SceneSpatialTree::new(); 2307 let root_reference_frame_index = cst.root_reference_frame_index(); 2308 2309 let mut spatial_tree = SpatialTree::new(); 2310 spatial_tree.apply_updates(cst.end_frame_and_get_pending_updates()); 2311 spatial_tree.update_tree(&SceneProperties::new()); 2312 2313 let map_local_to_picture = SpaceMapper::new_with_target( 2314 root_reference_frame_index, 2315 root_reference_frame_index, 2316 PictureRect::max_rect(), 2317 &spatial_tree, 2318 ); 2319 2320 let mut surfaces = vec![ 2321 SurfaceInfo { 2322 unclipped_local_rect: PictureRect::max_rect(), 2323 clipped_local_rect: PictureRect::max_rect(), 2324 is_opaque: true, 2325 clipping_rect: PictureRect::max_rect(), 2326 map_local_to_picture: map_local_to_picture.clone(), 2327 raster_spatial_node_index: root_reference_frame_index, 2328 surface_spatial_node_index: root_reference_frame_index, 2329 visibility_spatial_node_index: root_reference_frame_index, 2330 device_pixel_scale: DevicePixelScale::new(1.0), 2331 world_scale_factors: (1.0, 1.0), 2332 local_scale: (1.0, 1.0), 2333 allow_snapping: true, 2334 force_scissor_rect: false, 2335 culling_rect: VisRect::max_rect(), 2336 }, 2337 SurfaceInfo { 2338 unclipped_local_rect: PictureRect::new( 2339 PicturePoint::new(0.0, 0.0), 2340 PicturePoint::new(750.0, 450.0), 2341 ), 2342 clipped_local_rect: PictureRect::new( 2343 PicturePoint::new(0.0, 0.0), 2344 PicturePoint::new(750.0, 450.0), 2345 ), 2346 is_opaque: true, 2347 clipping_rect: PictureRect::max_rect(), 2348 map_local_to_picture, 2349 raster_spatial_node_index: root_reference_frame_index, 2350 surface_spatial_node_index: root_reference_frame_index, 2351 visibility_spatial_node_index: root_reference_frame_index, 2352 device_pixel_scale: DevicePixelScale::new(1.0), 2353 world_scale_factors: (1.0, 1.0), 2354 local_scale: (1.0, 1.0), 2355 allow_snapping: true, 2356 force_scissor_rect: false, 2357 culling_rect: VisRect::max_rect(), 2358 }, 2359 ]; 2360 2361 let shadows = smallvec![ 2362 Shadow { 2363 offset: LayoutVector2D::zero(), 2364 color: ColorF::BLACK, 2365 blur_radius: 75.0, 2366 }, 2367 ]; 2368 2369 let composite_mode = PictureCompositeMode::Filter(Filter::DropShadows(shadows)); 2370 2371 // Ensure we get a valid and correct render task size when dirty region covers entire screen 2372 let info = get_surface_rects( 2373 SurfaceIndex(1), 2374 &composite_mode, 2375 SurfaceIndex(0), 2376 &mut surfaces, 2377 &spatial_tree, 2378 MAX_SURFACE_SIZE as f32, 2379 false, 2380 ).expect("No surface rect"); 2381 assert_eq!(info.task_size, DeviceIntSize::new(1200, 900)); 2382 2383 // Ensure we get a valid and correct render task size when dirty region is outside filter content 2384 surfaces[0].clipping_rect = PictureRect::new( 2385 PicturePoint::new(768.0, 128.0), 2386 PicturePoint::new(1024.0, 256.0), 2387 ); 2388 let info = get_surface_rects( 2389 SurfaceIndex(1), 2390 &composite_mode, 2391 SurfaceIndex(0), 2392 &mut surfaces, 2393 &spatial_tree, 2394 MAX_SURFACE_SIZE as f32, 2395 false, 2396 ).expect("No surface rect"); 2397 assert_eq!(info.task_size, DeviceIntSize::new(432, 578)); 2398 }