1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 //! A picture represents a dynamically rendered image.
9 //! Pictures consists of:
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).
17 //! The tree of pictures are generated during scene building.
19 //! Depending on their composite operations pictures can be rendered into
20 //! intermediate targets or folded into their parent picture.
22 //! ## Picture caching
24 //! Pictures can be cached to reduce the amount of rasterization happening per
27 //! When picture caching is enabled, the scene is cut into a small number of slices,
32 //! - background UI slice which is hidden by the other two slices most of the time.
34 //! Each of these slice is made up of fixed-size large tiles of 2048x512 pixels
35 //! (or 128x128 for the UI slice).
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
43 //! Each tile keeps track of the elements that affect it, which can be:
48 //! - opacity bindings
51 //! These dependency lists are built each frame and compared to the previous frame to
52 //! see if the tile changed.
54 //! The tile's primitive dependency information is organized in a quadtree, each node
55 //! storing an index buffer of tile primitive dependencies.
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.
61 //! ## Display List shape
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.
72 //! ## Compositor Surfaces
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.
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.
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.
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).
97 use api::{MixBlendMode, PremultipliedColorF, FilterPrimitiveKind};
98 use api::{PropertyBinding, PropertyBindingId, FilterPrimitive};
99 use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags};
100 use api::{ImageRendering, ColorDepth, YuvColorSpace, YuvFormat, AlphaType};
102 use crate::batch::BatchFilter;
103 use crate::box_shadow::BLUR_SAMPLE_SCALE;
104 use crate::clip::{ClipStore, ClipChainInstance, ClipChainId, ClipInstance};
105 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX,
106 SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
108 use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
109 use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency};
110 use crate::debug_colors;
111 use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Vector3D, Rect, Transform3D, SideOffsets2D};
112 use euclid::approxeq::ApproxEq;
113 use crate::filterdata::SFilterData;
114 use crate::intern::ItemUid;
115 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
116 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
117 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
118 use crate::gpu_types::{UvRectKind, ZBufferId};
119 use plane_split::{Clipper, Polygon, Splitter};
120 use crate::prim_store::{PrimitiveTemplateKind, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
121 use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveScratchBuffer};
122 use crate::print_tree::{PrintTree, PrintTreePrinter};
123 use crate::render_backend::{DataStores, FrameId};
124 use crate::render_task_graph::RenderTaskId;
125 use crate::render_target::RenderTargetKind;
126 use crate::render_task::{BlurTask, RenderTask, RenderTaskLocation, BlurTaskCache};
127 use crate::render_task::{StaticRenderTaskSurface, RenderTaskKind};
128 use crate::renderer::BlendMode;
129 use crate::resource_cache::{ResourceCache, ImageGeneration, ImageRequest};
130 use crate::space::SpaceMapper;
131 use crate::scene::SceneProperties;
132 use smallvec::SmallVec;
133 use std::{mem, u8, marker, u32};
134 use std::sync::atomic::{AtomicUsize, Ordering};
135 use std::collections::hash_map::Entry;
137 use crate::texture_cache::TextureCacheHandle;
138 use crate::util::{MaxRect, VecHelper, MatrixHelpers, Recycler, raster_rect_to_device_pixels, ScaleOffset};
139 use crate::filterdata::{FilterDataHandle};
140 use crate::tile_cache::{SliceDebugInfo, TileDebugInfo, DirtyTileDebugInfo};
141 use crate::visibility::{PrimitiveVisibilityFlags, FrameVisibilityContext};
142 use crate::visibility::{VisibilityState, FrameVisibilityState};
143 #[cfg(any(feature = "capture", feature = "replay"))]
145 #[cfg(feature = "capture")]
146 use crate::scene_builder_thread::InternerUpdates;
147 #[cfg(any(feature = "capture", feature = "replay"))]
148 use crate::intern::{Internable, UpdateList};
149 #[cfg(any(feature = "capture", feature = "replay"))]
150 use crate::clip::{ClipIntern, PolygonIntern};
151 #[cfg(any(feature = "capture", feature = "replay"))]
152 use crate::filterdata::FilterDataIntern;
153 #[cfg(any(feature = "capture", feature = "replay"))]
154 use api::PrimitiveKeyKind;
155 #[cfg(any(feature = "capture", feature = "replay"))]
156 use crate::prim_store::backdrop::Backdrop;
157 #[cfg(any(feature = "capture", feature = "replay"))]
158 use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
159 #[cfg(any(feature = "capture", feature = "replay"))]
160 use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
161 #[cfg(any(feature = "capture", feature = "replay"))]
162 use crate::prim_store::image::{Image, YuvImage};
163 #[cfg(any(feature = "capture", feature = "replay"))]
164 use crate::prim_store::line_dec::LineDecoration;
165 #[cfg(any(feature = "capture", feature = "replay"))]
166 use crate::prim_store::picture::Picture;
167 #[cfg(any(feature = "capture", feature = "replay"))]
168 use crate::prim_store::text_run::TextRun;
170 #[cfg(feature = "capture")]
172 #[cfg(feature = "capture")]
173 use std::io::prelude::*;
174 #[cfg(feature = "capture")]
175 use std::path::PathBuf;
176 use crate::scene_building::{SliceFlags};
178 #[cfg(feature = "replay")]
179 // used by tileview so don't use an internal_types FastHashMap
180 use std::collections::HashMap;
182 // Maximum blur radius for blur filter (different than box-shadow blur).
183 // Taken from FilterNodeSoftware.cpp in Gecko.
184 pub const MAX_BLUR_RADIUS: f32 = 100.;
186 /// Specify whether a surface allows subpixel AA text rendering.
187 #[derive(Debug, Copy, Clone)]
188 pub enum SubpixelMode {
189 /// This surface allows subpixel AA text
191 /// Subpixel AA text cannot be drawn on this surface
193 /// Subpixel AA can be drawn on this surface, if not intersecting
194 /// with the excluded regions, and inside the allowed rect.
196 allowed_rect: PictureRect,
200 /// A comparable transform matrix, that compares with epsilon checks.
201 #[derive(Debug, Clone)]
206 impl PartialEq for MatrixKey {
207 fn eq(&self, other: &Self) -> bool {
208 const EPSILON: f32 = 0.001;
210 // TODO(gw): It's possible that we may need to adjust the epsilon
211 // to be tighter on most of the matrix, except the
212 // translation parts?
213 for (i, j) in self.m.iter().zip(other.m.iter()) {
214 if !i.approx_eq_eps(j, &EPSILON) {
223 /// A comparable / hashable version of a coordinate space mapping. Used to determine
224 /// if a transform dependency for a tile has changed.
225 #[derive(Debug, PartialEq, Clone)]
239 impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
240 fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
242 CoordinateSpaceMapping::Local => {
245 CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
246 TransformKey::ScaleOffset {
247 scale_x: scale_offset.scale.x,
248 scale_y: scale_offset.scale.y,
249 offset_x: scale_offset.offset.x,
250 offset_y: scale_offset.offset.y,
253 CoordinateSpaceMapping::Transform(ref m) => {
254 TransformKey::Transform {
264 /// Unit for tile coordinates.
265 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
266 pub struct TileCoordinate;
268 // Geometry types for tile coordinates.
269 pub type TileOffset = Point2D<i32, TileCoordinate>;
270 // TileSize type is also used in used in lib.rs and cbindgen picks the wrong one when
271 // generating headers.
273 pub type TileSize = Size2D<i32, TileCoordinate>;
274 pub type TileRect = Rect<i32, TileCoordinate>;
276 /// The maximum number of compositor surfaces that are allowed per picture cache. This
277 /// is an arbitrary number that should be enough for common cases, but low enough to
278 /// prevent performance and memory usage drastically degrading in pathological cases.
279 const MAX_COMPOSITOR_SURFACES: usize = 4;
281 /// The size in device pixels of a normal cached tile.
282 pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
285 _unit: marker::PhantomData,
288 /// The size in device pixels of a tile for horizontal scroll bars
289 pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
292 _unit: marker::PhantomData,
295 /// The size in device pixels of a tile for vertical scroll bars
296 pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
299 _unit: marker::PhantomData,
302 /// The maximum size per axis of a surface,
303 /// in WorldPixel coordinates.
304 const MAX_SURFACE_SIZE: f32 = 4096.0;
305 /// Maximum size of a compositor surface.
306 const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0;
308 /// The maximum number of sub-dependencies (e.g. clips, transforms) we can handle
309 /// per-primitive. If a primitive has more than this, it will invalidate every frame.
310 const MAX_PRIM_SUB_DEPS: usize = u8::MAX as usize;
312 /// Used to get unique tile IDs, even when the tile cache is
313 /// destroyed between display lists / scenes.
314 static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
316 fn clamp(value: i32, low: i32, high: i32) -> i32 {
317 value.max(low).min(high)
320 fn clampf(value: f32, low: f32, high: f32) -> f32 {
321 value.max(low).min(high)
324 /// Clamps the blur radius depending on scale factors.
325 fn clamp_blur_radius(blur_radius: f32, scale_factors: (f32, f32)) -> f32 {
326 // Clamping must occur after scale factors are applied, but scale factors are not applied
327 // until later on. To clamp the blur radius, we first apply the scale factors and then clamp
328 // and finally revert the scale factors.
330 // TODO: the clamping should be done on a per-axis basis, but WR currently only supports
331 // having a single value for both x and y blur.
332 let largest_scale_factor = f32::max(scale_factors.0, scale_factors.1);
333 let scaled_blur_radius = blur_radius * largest_scale_factor;
335 if scaled_blur_radius > MAX_BLUR_RADIUS {
336 MAX_BLUR_RADIUS / largest_scale_factor
338 // Return the original blur radius to avoid any rounding errors
343 /// An index into the prims array in a TileDescriptor.
344 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
345 #[cfg_attr(feature = "capture", derive(Serialize))]
346 #[cfg_attr(feature = "replay", derive(Deserialize))]
347 pub struct PrimitiveDependencyIndex(pub u32);
349 /// Information about the state of a binding.
351 pub struct BindingInfo<T> {
352 /// The current value retrieved from dynamic scene properties.
354 /// True if it was changed (or is new) since the last frame build.
358 /// Information stored in a tile descriptor for a binding.
359 #[derive(Debug, PartialEq, Clone, Copy)]
360 #[cfg_attr(feature = "capture", derive(Serialize))]
361 #[cfg_attr(feature = "replay", derive(Deserialize))]
362 pub enum Binding<T> {
364 Binding(PropertyBindingId),
367 impl<T> From<PropertyBinding<T>> for Binding<T> {
368 fn from(binding: PropertyBinding<T>) -> Binding<T> {
370 PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
371 PropertyBinding::Value(value) => Binding::Value(value),
376 pub type OpacityBinding = Binding<f32>;
377 pub type OpacityBindingInfo = BindingInfo<f32>;
379 pub type ColorBinding = Binding<ColorU>;
380 pub type ColorBindingInfo = BindingInfo<ColorU>;
382 /// A dependency for a transform is defined by the spatial node index + frame it was used
383 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
384 #[cfg_attr(feature = "capture", derive(Serialize))]
385 #[cfg_attr(feature = "replay", derive(Deserialize))]
386 pub struct SpatialNodeKey {
387 spatial_node_index: SpatialNodeIndex,
391 /// A helper for comparing spatial nodes between frames. The comparisons
392 /// are done by-value, so that if the shape of the spatial node tree
393 /// changes, invalidations aren't done simply due to the spatial node
394 /// index changing between display lists.
395 struct SpatialNodeComparer {
396 /// The root spatial node index of the tile cache
397 ref_spatial_node_index: SpatialNodeIndex,
398 /// Maintains a map of currently active transform keys
399 spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>,
400 /// A cache of recent comparisons between prev and current spatial nodes
401 compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>,
402 /// A set of frames that we need to retain spatial node entries for
403 referenced_frames: FastHashSet<FrameId>,
406 impl SpatialNodeComparer {
407 /// Construct a new comparer
409 SpatialNodeComparer {
410 ref_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
411 spatial_nodes: FastHashMap::default(),
412 compare_cache: FastHashMap::default(),
413 referenced_frames: FastHashSet::default(),
417 /// Advance to the next frame
420 ref_spatial_node_index: SpatialNodeIndex,
422 // Drop any node information for unreferenced frames, to ensure that the
423 // hashmap doesn't grow indefinitely!
424 let referenced_frames = &self.referenced_frames;
425 self.spatial_nodes.retain(|key, _| {
426 referenced_frames.contains(&key.frame_id)
429 // Update the root spatial node for this comparer
430 self.ref_spatial_node_index = ref_spatial_node_index;
431 self.compare_cache.clear();
432 self.referenced_frames.clear();
435 /// Register a transform that is used, and build the transform key for it if new.
436 fn register_used_transform(
438 spatial_node_index: SpatialNodeIndex,
440 spatial_tree: &SpatialTree,
442 let key = SpatialNodeKey {
447 if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) {
451 self.ref_spatial_node_index,
458 /// Return true if the transforms for two given spatial nodes are considered equivalent
459 fn are_transforms_equivalent(
461 prev_spatial_node_key: &SpatialNodeKey,
462 curr_spatial_node_key: &SpatialNodeKey,
464 let key = (*prev_spatial_node_key, *curr_spatial_node_key);
465 let spatial_nodes = &self.spatial_nodes;
470 let prev = &spatial_nodes[&prev_spatial_node_key];
471 let curr = &spatial_nodes[&curr_spatial_node_key];
476 /// Ensure that the comparer won't GC any nodes for a given frame id
477 fn retain_for_frame(&mut self, frame_id: FrameId) {
478 self.referenced_frames.insert(frame_id);
482 // Immutable context passed to picture cache tiles during pre_update
483 struct TilePreUpdateContext {
484 /// Maps from picture cache coords -> world space coords.
485 pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
487 /// The fractional position of the picture cache, which may
488 /// require invalidation of all tiles.
489 fract_offset: PictureVector2D,
490 device_fract_offset: DeviceVector2D,
492 /// The optional background color of the picture cache instance
493 background_color: Option<ColorF>,
495 /// The visible part of the screen in world coords.
496 global_screen_world_rect: WorldRect,
498 /// Current size of tiles in picture units.
499 tile_size: PictureSize,
501 /// The current frame id for this picture cache
505 // Immutable context passed to picture cache tiles during post_update
506 struct TilePostUpdateContext<'a> {
507 /// Maps from picture cache coords -> world space coords.
508 pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
510 /// Global scale factor from world -> device pixels.
511 global_device_pixel_scale: DevicePixelScale,
513 /// The local clip rect (in picture space) of the entire picture cache
514 local_clip_rect: PictureRect,
516 /// The calculated backdrop information for this cache instance.
517 backdrop: Option<BackdropInfo>,
519 /// Information about opacity bindings from the picture cache.
520 opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
522 /// Information about color bindings from the picture cache.
523 color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
525 /// Current size in device pixels of tiles for this cache
526 current_tile_size: DeviceIntSize,
528 /// The local rect of the overall picture cache
529 local_rect: PictureRect,
531 /// Pre-allocated z-id to assign to tiles during post_update.
534 /// If true, the scale factor of the root transform for this picture
535 /// cache changed, so we need to invalidate the tile and re-render.
536 invalidate_all: bool,
539 // Mutable state passed to picture cache tiles during post_update
540 struct TilePostUpdateState<'a> {
541 /// Allow access to the texture cache for requesting tiles
542 resource_cache: &'a mut ResourceCache,
544 /// Current configuration and setup for compositing all the picture cache tiles in renderer.
545 composite_state: &'a mut CompositeState,
547 /// A cache of comparison results to avoid re-computation during invalidation.
548 compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
550 /// Information about transform node differences from last frame.
551 spatial_node_comparer: &'a mut SpatialNodeComparer,
554 /// Information about the dependencies of a single primitive instance.
555 struct PrimitiveDependencyInfo {
556 /// Unique content identifier of the primitive.
559 /// The (conservative) clipped area in picture space this primitive occupies.
560 prim_clip_box: PictureBox2D,
562 /// Image keys this primitive depends on.
563 images: SmallVec<[ImageDependency; 8]>,
565 /// Opacity bindings this primitive depends on.
566 opacity_bindings: SmallVec<[OpacityBinding; 4]>,
568 /// Color binding this primitive depends on.
569 color_binding: Option<ColorBinding>,
571 /// Clips that this primitive depends on.
572 clips: SmallVec<[ItemUid; 8]>,
574 /// Spatial nodes references by the clip dependencies of this primitive.
575 spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
578 impl PrimitiveDependencyInfo {
579 /// Construct dependency info for a new primitive.
582 prim_clip_box: PictureBox2D,
584 PrimitiveDependencyInfo {
586 images: SmallVec::new(),
587 opacity_bindings: SmallVec::new(),
590 clips: SmallVec::new(),
591 spatial_nodes: SmallVec::new(),
596 /// A stable ID for a given tile, to help debugging. These are also used
597 /// as unique identifiers for tile surfaces when using a native compositor.
598 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
599 #[cfg_attr(feature = "capture", derive(Serialize))]
600 #[cfg_attr(feature = "replay", derive(Deserialize))]
601 pub struct TileId(pub usize);
603 /// A descriptor for the kind of texture that a picture cache tile will
606 pub enum SurfaceTextureDescriptor {
607 /// When using the WR compositor, the tile is drawn into an entry
608 /// in the WR texture cache.
610 handle: TextureCacheHandle
612 /// When using an OS compositor, the tile is drawn into a native
613 /// surface identified by arbitrary id.
615 /// The arbitrary id of this tile.
616 id: Option<NativeTileId>,
620 /// This is the same as a `SurfaceTextureDescriptor` but has been resolved
621 /// into a texture cache handle (if appropriate) that can be used by the
622 /// batching and compositing code in the renderer.
623 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
624 #[cfg_attr(feature = "capture", derive(Serialize))]
625 #[cfg_attr(feature = "replay", derive(Deserialize))]
626 pub enum ResolvedSurfaceTexture {
628 /// The texture ID to draw to.
629 texture: TextureSource,
632 /// The arbitrary id of this tile.
634 /// The size of the tile in device pixels.
639 impl SurfaceTextureDescriptor {
640 /// Create a resolved surface texture for this descriptor
643 resource_cache: &ResourceCache,
645 ) -> ResolvedSurfaceTexture {
647 SurfaceTextureDescriptor::TextureCache { handle } => {
648 let cache_item = resource_cache.texture_cache.get(handle);
650 ResolvedSurfaceTexture::TextureCache {
651 texture: cache_item.texture_id,
654 SurfaceTextureDescriptor::Native { id } => {
655 ResolvedSurfaceTexture::Native {
656 id: id.expect("bug: native surface not allocated"),
664 /// The backing surface for this tile.
666 pub enum TileSurface {
668 /// Descriptor for the surface that this tile draws into.
669 descriptor: SurfaceTextureDescriptor,
678 fn kind(&self) -> &'static str {
680 TileSurface::Color { .. } => "Color",
681 TileSurface::Texture { .. } => "Texture",
682 TileSurface::Clear => "Clear",
687 /// Optional extra information returned by is_same when
688 /// logging is enabled.
689 #[derive(Debug, Copy, Clone, PartialEq)]
690 #[cfg_attr(feature = "capture", derive(Serialize))]
691 #[cfg_attr(feature = "replay", derive(Deserialize))]
692 pub enum CompareHelperResult<T> {
702 /// Two items are not equal
707 /// User callback returned true on item
713 /// The result of a primitive dependency comparison. Size is a u8
714 /// since this is a hot path in the code, and keeping the data small
715 /// is a performance win.
716 #[derive(Debug, Copy, Clone, PartialEq)]
717 #[cfg_attr(feature = "capture", derive(Serialize))]
718 #[cfg_attr(feature = "replay", derive(Deserialize))]
720 pub enum PrimitiveCompareResult {
723 /// Something in the PrimitiveDescriptor was different
725 /// The clip node content or spatial node changed
727 /// The value of the transform changed
729 /// An image dependency was dirty
731 /// The value of an opacity binding changed
733 /// The value of a color binding changed
737 /// A more detailed version of PrimitiveCompareResult used when
738 /// debug logging is enabled.
739 #[derive(Debug, Copy, Clone, PartialEq)]
740 #[cfg_attr(feature = "capture", derive(Serialize))]
741 #[cfg_attr(feature = "replay", derive(Deserialize))]
742 pub enum PrimitiveCompareResultDetail {
745 /// Something in the PrimitiveDescriptor was different
747 old: PrimitiveDescriptor,
748 new: PrimitiveDescriptor,
750 /// The clip node content or spatial node changed
752 detail: CompareHelperResult<ItemUid>,
754 /// The value of the transform changed
756 detail: CompareHelperResult<SpatialNodeKey>,
758 /// An image dependency was dirty
760 detail: CompareHelperResult<ImageDependency>,
762 /// The value of an opacity binding changed
764 detail: CompareHelperResult<OpacityBinding>,
766 /// The value of a color binding changed
768 detail: CompareHelperResult<ColorBinding>,
772 /// Debugging information about why a tile was invalidated
773 #[derive(Debug,Clone)]
774 #[cfg_attr(feature = "capture", derive(Serialize))]
775 #[cfg_attr(feature = "replay", derive(Deserialize))]
776 pub enum InvalidationReason {
777 /// The fractional offset changed
782 /// The background color changed
787 /// The opaque state of the backing native surface changed
788 SurfaceOpacityChanged{
791 /// There was no backing texture (evicted or never rendered)
793 /// There was no backing native surface (never rendered, or recreated)
795 /// The primitive count in the dependency list was different
797 old: Option<Vec<ItemUid>>,
798 new: Option<Vec<ItemUid>>,
800 /// The content of one of the primitives was different
802 /// What changed in the primitive that was different
803 prim_compare_result: PrimitiveCompareResult,
804 prim_compare_result_detail: Option<PrimitiveCompareResultDetail>,
806 // The compositor type changed
807 CompositorKindChanged,
808 // The valid region of the tile changed
810 // The overall scale of the picture cache changed
814 /// A minimal subset of Tile for debug capturing
815 #[cfg_attr(feature = "capture", derive(Serialize))]
816 #[cfg_attr(feature = "replay", derive(Deserialize))]
817 pub struct TileSerializer {
818 pub rect: PictureRect,
819 pub current_descriptor: TileDescriptor,
820 pub device_fract_offset: DeviceVector2D,
823 pub background_color: Option<ColorF>,
824 pub invalidation_reason: Option<InvalidationReason>
827 /// A minimal subset of TileCacheInstance for debug capturing
828 #[cfg_attr(feature = "capture", derive(Serialize))]
829 #[cfg_attr(feature = "replay", derive(Deserialize))]
830 pub struct TileCacheInstanceSerializer {
832 pub tiles: FastHashMap<TileOffset, TileSerializer>,
833 pub background_color: Option<ColorF>,
834 pub fract_offset: PictureVector2D,
837 /// Information about a cached tile.
839 /// The grid position of this tile within the picture cache
840 pub tile_offset: TileOffset,
841 /// The current world rect of this tile.
842 pub world_tile_rect: WorldRect,
843 /// The current local rect of this tile.
844 pub local_tile_rect: PictureRect,
845 /// Same as local_tile_rect, but in min/max form as an optimization
846 pub local_tile_box: PictureBox2D,
847 /// The picture space dirty rect for this tile.
848 local_dirty_rect: PictureRect,
849 /// The device space dirty rect for this tile.
850 /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
851 /// expose these as multiple dirty rects, which will help in some cases.
852 pub device_dirty_rect: DeviceRect,
853 /// Device space rect that contains valid pixels region of this tile.
854 pub device_valid_rect: DeviceRect,
855 /// Uniquely describes the content of this tile, in a way that can be
856 /// (reasonably) efficiently hashed and compared.
857 pub current_descriptor: TileDescriptor,
858 /// The content descriptor for this tile from the previous frame.
859 pub prev_descriptor: TileDescriptor,
860 /// Handle to the backing surface for this tile.
861 pub surface: Option<TileSurface>,
862 /// If true, this tile is marked valid, and the existing texture
863 /// cache handle can be used. Tiles are invalidated during the
864 /// build_dirty_regions method.
866 /// If true, this tile intersects with the currently visible screen
867 /// rect, and will be drawn.
868 pub is_visible: bool,
869 /// The current fractional offset of the cache transform root. If this changes,
870 /// all tiles need to be invalidated and redrawn, since snapping differences are
872 device_fract_offset: DeviceVector2D,
873 /// The tile id is stable between display lists and / or frames,
874 /// if the tile is retained. Useful for debugging tile evictions.
876 /// If true, the tile was determined to be opaque, which means blending
877 /// can be disabled when drawing it.
879 /// Root node of the quadtree dirty rect tracker.
881 /// The last rendered background color on this tile.
882 background_color: Option<ColorF>,
883 /// The first reason the tile was invalidated this frame.
884 invalidation_reason: Option<InvalidationReason>,
885 /// The local space valid rect for all primitives that affect this tile.
886 local_valid_rect: PictureBox2D,
887 /// z-buffer id for this tile
889 /// The last frame this tile had its dependencies updated (dependency updating is
890 /// skipped if a tile is off-screen).
891 pub last_updated_frame_id: FrameId,
895 /// Construct a new, invalid tile.
896 fn new(tile_offset: TileOffset) -> Self {
897 let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
901 local_tile_rect: PictureRect::zero(),
902 local_tile_box: PictureBox2D::zero(),
903 world_tile_rect: WorldRect::zero(),
904 device_valid_rect: DeviceRect::zero(),
905 local_dirty_rect: PictureRect::zero(),
906 device_dirty_rect: DeviceRect::zero(),
908 current_descriptor: TileDescriptor::new(),
909 prev_descriptor: TileDescriptor::new(),
912 device_fract_offset: DeviceVector2D::zero(),
915 root: TileNode::new_leaf(Vec::new()),
916 background_color: None,
917 invalidation_reason: None,
918 local_valid_rect: PictureBox2D::zero(),
919 z_id: ZBufferId::invalid(),
920 last_updated_frame_id: FrameId::INVALID,
924 /// Print debug information about this tile to a tree printer.
925 fn print(&self, pt: &mut dyn PrintTreePrinter) {
926 pt.new_level(format!("Tile {:?}", self.id));
927 pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect));
928 pt.add_item(format!("device_fract_offset: {:?}", self.device_fract_offset));
929 pt.add_item(format!("background_color: {:?}", self.background_color));
930 pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
931 self.current_descriptor.print(pt);
935 /// Check if the content of the previous and current tile descriptors match
936 fn update_dirty_rects(
938 ctx: &TilePostUpdateContext,
939 state: &mut TilePostUpdateState,
940 invalidation_reason: &mut Option<InvalidationReason>,
941 frame_context: &FrameVisibilityContext,
943 let mut prim_comparer = PrimitiveComparer::new(
944 &self.prev_descriptor,
945 &self.current_descriptor,
946 state.resource_cache,
947 state.spatial_node_comparer,
948 ctx.opacity_bindings,
952 let mut dirty_rect = PictureBox2D::zero();
953 self.root.update_dirty_rects(
954 &self.prev_descriptor.prims,
955 &self.current_descriptor.prims,
966 /// Invalidate a tile based on change in content. This
967 /// must be called even if the tile is not currently
968 /// visible on screen. We might be able to improve this
969 /// later by changing how ComparableVec is used.
970 fn update_content_validity(
972 ctx: &TilePostUpdateContext,
973 state: &mut TilePostUpdateState,
974 frame_context: &FrameVisibilityContext,
976 // Check if the contents of the primitives, clips, and
977 // other dependencies are the same.
978 state.compare_cache.clear();
979 let mut invalidation_reason = None;
980 let dirty_rect = self.update_dirty_rects(
983 &mut invalidation_reason,
986 if !dirty_rect.is_empty() {
989 invalidation_reason.expect("bug: no invalidation_reason"),
992 if ctx.invalidate_all {
993 self.invalidate(None, InvalidationReason::ScaleChanged);
995 // TODO(gw): We can avoid invalidating the whole tile in some cases here,
996 // but it should be a fairly rare invalidation case.
997 if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
998 self.invalidate(None, InvalidationReason::ValidRectChanged);
999 state.composite_state.dirty_rects_are_valid = false;
1003 /// Invalidate this tile. If `invalidation_rect` is None, the entire
1004 /// tile is invalidated.
1007 invalidation_rect: Option<PictureRect>,
1008 reason: InvalidationReason,
1010 self.is_valid = false;
1012 match invalidation_rect {
1014 self.local_dirty_rect = self.local_dirty_rect.union(&rect);
1017 self.local_dirty_rect = self.local_tile_rect;
1021 if self.invalidation_reason.is_none() {
1022 self.invalidation_reason = Some(reason);
1026 /// Called during pre_update of a tile cache instance. Allows the
1027 /// tile to setup state before primitive dependency calculations.
1030 ctx: &TilePreUpdateContext,
1032 // Ensure each tile is offset by the appropriate amount from the
1033 // origin, such that the content origin will be a whole number and
1034 // the snapping will be consistent.
1035 self.local_tile_rect = PictureRect::new(
1037 self.tile_offset.x as f32 * ctx.tile_size.width + ctx.fract_offset.x,
1038 self.tile_offset.y as f32 * ctx.tile_size.height + ctx.fract_offset.y,
1042 self.local_tile_box = PictureBox2D::new(
1043 self.local_tile_rect.origin,
1044 self.local_tile_rect.bottom_right(),
1046 self.local_valid_rect = PictureBox2D::zero();
1047 self.invalidation_reason = None;
1049 self.world_tile_rect = ctx.pic_to_world_mapper
1050 .map(&self.local_tile_rect)
1051 .expect("bug: map local tile rect");
1053 // Check if this tile is currently on screen.
1054 self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
1056 // If the tile isn't visible, early exit, skipping the normal set up to
1057 // validate dependencies. Instead, we will only compare the current tile
1058 // dependencies the next time it comes into view.
1059 if !self.is_visible {
1063 // We may need to rerender if glyph subpixel positions have changed. Note
1064 // that we update the tile fract offset itself after we have completed
1065 // invalidation. This allows for other whole tile invalidation cases to
1066 // update the fract offset appropriately.
1067 let fract_delta = self.device_fract_offset - ctx.device_fract_offset;
1068 let fract_changed = fract_delta.x.abs() > 0.01 || fract_delta.y.abs() > 0.01;
1070 self.invalidate(None, InvalidationReason::FractionalOffset {
1071 old: self.device_fract_offset,
1072 new: ctx.device_fract_offset });
1075 if ctx.background_color != self.background_color {
1076 self.invalidate(None, InvalidationReason::BackgroundColor {
1077 old: self.background_color,
1078 new: ctx.background_color });
1079 self.background_color = ctx.background_color;
1082 // Clear any dependencies so that when we rebuild them we
1083 // can compare if the tile has the same content.
1085 &mut self.current_descriptor,
1086 &mut self.prev_descriptor,
1088 self.current_descriptor.clear();
1089 self.root.clear(self.local_tile_rect.to_box2d());
1091 // Since this tile is determined to be visible, it will get updated
1092 // dependencies, so update the frame id we are storing dependencies for.
1093 self.last_updated_frame_id = ctx.frame_id;
1096 /// Add dependencies for a given primitive to this tile.
1097 fn add_prim_dependency(
1099 info: &PrimitiveDependencyInfo,
1101 // If this tile isn't currently visible, we don't want to update the dependencies
1102 // for this tile, as an optimization, since it won't be drawn anyway.
1103 if !self.is_visible {
1107 // Incorporate the bounding rect of the primitive in the local valid rect
1108 // for this tile. This is used to minimize the size of the scissor rect
1109 // during rasterization and the draw rect during composition of partial tiles.
1110 self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
1112 // Include any image keys this tile depends on.
1113 self.current_descriptor.images.extend_from_slice(&info.images);
1115 // Include any opacity bindings this primitive depends on.
1116 self.current_descriptor.opacity_bindings.extend_from_slice(&info.opacity_bindings);
1118 // Include any clip nodes that this primitive depends on.
1119 self.current_descriptor.clips.extend_from_slice(&info.clips);
1121 // Include any transforms that this primitive depends on.
1122 for spatial_node_index in &info.spatial_nodes {
1123 self.current_descriptor.transforms.push(
1125 spatial_node_index: *spatial_node_index,
1126 frame_id: self.last_updated_frame_id,
1131 // Include any color bindings this primitive depends on.
1132 if info.color_binding.is_some() {
1133 self.current_descriptor.color_bindings.insert(
1134 self.current_descriptor.color_bindings.len(), info.color_binding.unwrap());
1137 // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port,
1138 // which can cause invalidations when a new display list with changed
1139 // display port is received. To work around this, clamp the prim clip rect
1140 // to the tile boundaries - if the clip hasn't affected the tile, then the
1141 // changed clip can't affect the content of the primitive on this tile.
1142 // In future, we could consider supplying the display port clip from Gecko
1143 // in a different way (e.g. as a scroll frame clip) which still provides
1144 // the desired clip for checkerboarding, but doesn't require this extra
1147 // TODO(gw): This is a hot part of the code - we could probably optimize further by:
1148 // - Using min/max instead of clamps below (if we guarantee the rects are well formed)
1150 let tile_p0 = self.local_tile_box.min;
1151 let tile_p1 = self.local_tile_box.max;
1153 let prim_clip_box = PictureBox2D::new(
1155 clampf(info.prim_clip_box.min.x, tile_p0.x, tile_p1.x),
1156 clampf(info.prim_clip_box.min.y, tile_p0.y, tile_p1.y),
1159 clampf(info.prim_clip_box.max.x, tile_p0.x, tile_p1.x),
1160 clampf(info.prim_clip_box.max.y, tile_p0.y, tile_p1.y),
1164 // Update the tile descriptor, used for tile comparison during scene swaps.
1165 let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
1167 // We know that the casts below will never overflow because the array lengths are
1168 // truncated to MAX_PRIM_SUB_DEPS during update_prim_dependencies.
1169 debug_assert!(info.spatial_nodes.len() <= MAX_PRIM_SUB_DEPS);
1170 debug_assert!(info.clips.len() <= MAX_PRIM_SUB_DEPS);
1171 debug_assert!(info.images.len() <= MAX_PRIM_SUB_DEPS);
1172 debug_assert!(info.opacity_bindings.len() <= MAX_PRIM_SUB_DEPS);
1174 self.current_descriptor.prims.push(PrimitiveDescriptor {
1175 prim_uid: info.prim_uid,
1177 transform_dep_count: info.spatial_nodes.len() as u8,
1178 clip_dep_count: info.clips.len() as u8,
1179 image_dep_count: info.images.len() as u8,
1180 opacity_binding_dep_count: info.opacity_bindings.len() as u8,
1181 color_binding_dep_count: if info.color_binding.is_some() { 1 } else { 0 } as u8,
1184 // Add this primitive to the dirty rect quadtree.
1185 self.root.add_prim(prim_index, &info.prim_clip_box);
1188 /// Called during tile cache instance post_update. Allows invalidation and dirty
1189 /// rect calculation after primitive dependencies have been updated.
1192 ctx: &TilePostUpdateContext,
1193 state: &mut TilePostUpdateState,
1194 frame_context: &FrameVisibilityContext,
1196 // Register the frame id of this tile with the spatial node comparer, to ensure
1197 // that it doesn't GC any spatial nodes from the comparer that are referenced
1198 // by this tile. Must be done before we early exit below, so that we retain
1199 // spatial node info even for tiles that are currently not visible.
1200 state.spatial_node_comparer.retain_for_frame(self.last_updated_frame_id);
1202 // If tile is not visible, just early out from here - we don't update dependencies
1203 // so don't want to invalidate, merge, split etc. The tile won't need to be drawn
1204 // (and thus updated / invalidated) until it is on screen again.
1205 if !self.is_visible {
1209 // Calculate the overall valid rect for this tile.
1210 self.current_descriptor.local_valid_rect = self.local_valid_rect.to_rect();
1212 // TODO(gw): In theory, the local tile rect should always have an
1213 // intersection with the overall picture rect. In practice,
1214 // due to some accuracy issues with how fract_offset (and
1215 // fp accuracy) are used in the calling method, this isn't
1216 // always true. In this case, it's safe to set the local
1217 // valid rect to zero, which means it will be clipped out
1218 // and not affect the scene. In future, we should fix the
1219 // accuracy issue above, so that this assumption holds, but
1220 // it shouldn't have any noticeable effect on performance
1221 // or memory usage (textures should never get allocated).
1222 self.current_descriptor.local_valid_rect = self.local_tile_rect
1223 .intersection(&ctx.local_rect)
1224 .and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect))
1225 .unwrap_or_else(PictureRect::zero);
1227 // The device_valid_rect is referenced during `update_content_validity` so it
1228 // must be updated here first.
1229 let world_valid_rect = ctx.pic_to_world_mapper
1230 .map(&self.current_descriptor.local_valid_rect)
1231 .expect("bug: map local valid rect");
1233 // The device rect is guaranteed to be aligned on a device pixel - the round
1234 // is just to deal with float accuracy. However, the valid rect is not
1235 // always aligned to a device pixel. To handle this, round out to get all
1236 // required pixels, and intersect with the tile device rect.
1237 let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round();
1238 self.device_valid_rect = (world_valid_rect * ctx.global_device_pixel_scale)
1240 .intersection(&device_rect)
1241 .unwrap_or_else(DeviceRect::zero);
1243 // Invalidate the tile based on the content changing.
1244 self.update_content_validity(ctx, state, frame_context);
1246 // If there are no primitives there is no need to draw or cache it.
1247 if self.current_descriptor.prims.is_empty() {
1248 // If there is a native compositor surface allocated for this (now empty) tile
1249 // it must be freed here, otherwise the stale tile with previous contents will
1250 // be composited. If the tile subsequently gets new primitives added to it, the
1251 // surface will be re-allocated when it's added to the composite draw list.
1252 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
1253 if let Some(id) = id.take() {
1254 state.resource_cache.destroy_compositor_tile(id);
1258 self.is_visible = false;
1262 // Check if this tile can be considered opaque. Opacity state must be updated only
1263 // after all early out checks have been performed. Otherwise, we might miss updating
1264 // the native surface next time this tile becomes visible.
1265 let clipped_rect = self.current_descriptor.local_valid_rect
1266 .intersection(&ctx.local_clip_rect)
1267 .unwrap_or_else(PictureRect::zero);
1269 let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
1270 let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_rect(&clipped_rect));
1271 let is_opaque = has_opaque_bg_color || has_opaque_backdrop;
1273 // Set the correct z_id for this tile
1274 self.z_id = ctx.z_id;
1276 if is_opaque != self.is_opaque {
1277 // If opacity changed, the native compositor surface and all tiles get invalidated.
1278 // (this does nothing if not using native compositor mode).
1279 // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
1280 // everything in this case. If it turns out that this isn't true, we could
1281 // consider other options, such as per-tile opacity (natively supported
1282 // on CoreAnimation, and supported if backed by non-virtual surfaces in
1283 // DirectComposition).
1284 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
1285 if let Some(id) = id.take() {
1286 state.resource_cache.destroy_compositor_tile(id);
1290 // Invalidate the entire tile to force a redraw.
1291 self.invalidate(None, InvalidationReason::SurfaceOpacityChanged { became_opaque: is_opaque });
1292 self.is_opaque = is_opaque;
1295 // Check if the selected composite mode supports dirty rect updates. For Draw composite
1296 // mode, we can always update the content with smaller dirty rects, unless there is a
1297 // driver bug to workaround. For native composite mode, we can only use dirty rects if
1298 // the compositor supports partial surface updates.
1299 let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
1300 CompositorKind::Draw { .. } => {
1301 (frame_context.config.gpu_supports_render_target_partial_update, true)
1303 CompositorKind::Native { max_update_rects, .. } => {
1304 (max_update_rects > 0, false)
1308 // TODO(gw): Consider using smaller tiles and/or tile splits for
1309 // native compositors that don't support dirty rects.
1310 if supports_dirty_rects {
1311 // Only allow splitting for normal content sized tiles
1312 if ctx.current_tile_size == state.resource_cache.texture_cache.default_picture_tile_size() {
1313 let max_split_level = 3;
1315 // Consider splitting / merging dirty regions
1316 self.root.maybe_merge_or_split(
1318 &self.current_descriptor.prims,
1324 // The dirty rect will be set correctly by now. If the underlying platform
1325 // doesn't support partial updates, and this tile isn't valid, force the dirty
1326 // rect to be the size of the entire tile.
1327 if !self.is_valid && !supports_dirty_rects {
1328 self.local_dirty_rect = self.local_tile_rect;
1331 // See if this tile is a simple color, in which case we can just draw
1332 // it as a rect, and avoid allocating a texture surface and drawing it.
1333 // TODO(gw): Initial native compositor interface doesn't support simple
1334 // color tiles. We can definitely support this in DC, so this
1335 // should be added as a follow up.
1336 let is_simple_prim =
1337 ctx.backdrop.map_or(false, |b| b.kind.is_some()) &&
1338 self.current_descriptor.prims.len() == 1 &&
1340 supports_simple_prims;
1342 // Set up the backing surface for this tile.
1343 let surface = if is_simple_prim {
1344 // If we determine the tile can be represented by a color, set the
1345 // surface unconditionally (this will drop any previously used
1346 // texture cache backing surface).
1347 match ctx.backdrop.unwrap().kind {
1348 Some(BackdropKind::Color { color }) => {
1349 TileSurface::Color {
1353 Some(BackdropKind::Clear) => {
1357 // This should be prevented by the is_simple_prim check above.
1362 // If this tile will be backed by a surface, we want to retain
1363 // the texture handle from the previous frame, if possible. If
1364 // the tile was previously a color, or not set, then just set
1365 // up a new texture cache handle.
1366 match self.surface.take() {
1367 Some(TileSurface::Texture { descriptor }) => {
1368 // Reuse the existing descriptor and vis mask
1369 TileSurface::Texture {
1373 Some(TileSurface::Color { .. }) | Some(TileSurface::Clear) | None => {
1374 // This is the case where we are constructing a tile surface that
1375 // involves drawing to a texture. Create the correct surface
1376 // descriptor depending on the compositing mode that will read
1378 let descriptor = match state.composite_state.compositor_kind {
1379 CompositorKind::Draw { .. } => {
1380 // For a texture cache entry, create an invalid handle that
1381 // will be allocated when update_picture_cache is called.
1382 SurfaceTextureDescriptor::TextureCache {
1383 handle: TextureCacheHandle::invalid(),
1386 CompositorKind::Native { .. } => {
1387 // Create a native surface surface descriptor, but don't allocate
1388 // a surface yet. The surface is allocated *after* occlusion
1389 // culling occurs, so that only visible tiles allocate GPU memory.
1390 SurfaceTextureDescriptor::Native {
1396 TileSurface::Texture {
1403 // Store the current surface backing info for use during batching.
1404 self.surface = Some(surface);
1410 /// Defines a key that uniquely identifies a primitive instance.
1411 #[derive(Debug, Copy, Clone)]
1412 #[cfg_attr(feature = "capture", derive(Serialize))]
1413 #[cfg_attr(feature = "replay", derive(Deserialize))]
1414 pub struct PrimitiveDescriptor {
1415 /// Uniquely identifies the content of the primitive template.
1416 pub prim_uid: ItemUid,
1417 /// The clip rect for this primitive. Included here in
1418 /// dependencies since there is no entry in the clip chain
1419 /// dependencies for the local clip rect.
1420 pub prim_clip_box: PictureBox2D,
1421 /// The number of extra dependencies that this primitive has.
1422 transform_dep_count: u8,
1423 image_dep_count: u8,
1424 opacity_binding_dep_count: u8,
1426 color_binding_dep_count: u8,
1429 impl PartialEq for PrimitiveDescriptor {
1430 fn eq(&self, other: &Self) -> bool {
1431 const EPSILON: f32 = 0.001;
1433 if self.prim_uid != other.prim_uid {
1437 if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) {
1440 if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) {
1443 if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) {
1446 if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) {
1454 /// A small helper to compare two arrays of primitive dependencies.
1455 struct CompareHelper<'a, T> where T: Copy {
1458 curr_items: &'a [T],
1459 prev_items: &'a [T],
1462 impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq {
1463 /// Construct a new compare helper for a current / previous set of dependency information.
1465 prev_items: &'a [T],
1466 curr_items: &'a [T],
1476 /// Reset the current position in the dependency array to the start
1477 fn reset(&mut self) {
1478 self.offset_prev = 0;
1479 self.offset_curr = 0;
1482 /// Test if two sections of the dependency arrays are the same, by checking both
1483 /// item equality, and a user closure to see if the content of the item changed.
1489 opt_detail: Option<&mut CompareHelperResult<T>>,
1490 ) -> bool where F: FnMut(&T, &T) -> bool {
1491 // If the number of items is different, trivial reject.
1492 if prev_count != curr_count {
1493 if let Some(detail) = opt_detail { *detail = CompareHelperResult::Count{ prev_count, curr_count }; }
1496 // If both counts are 0, then no need to check these dependencies.
1497 if curr_count == 0 {
1498 if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; }
1501 // If both counts are u8::MAX, this is a sentinel that we can't compare these
1502 // deps, so just trivial reject.
1503 if curr_count as usize == MAX_PRIM_SUB_DEPS {
1504 if let Some(detail) = opt_detail { *detail = CompareHelperResult::Sentinel; }
1508 let end_prev = self.offset_prev + prev_count as usize;
1509 let end_curr = self.offset_curr + curr_count as usize;
1511 let curr_items = &self.curr_items[self.offset_curr .. end_curr];
1512 let prev_items = &self.prev_items[self.offset_prev .. end_prev];
1514 for (curr, prev) in curr_items.iter().zip(prev_items.iter()) {
1516 if let Some(detail) = opt_detail { *detail = CompareHelperResult::PredicateTrue{ curr: *curr }; }
1521 if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; }
1525 // Advance the prev dependency array by a given amount
1526 fn advance_prev(&mut self, count: u8) {
1527 self.offset_prev += count as usize;
1530 // Advance the current dependency array by a given amount
1531 fn advance_curr(&mut self, count: u8) {
1532 self.offset_curr += count as usize;
1536 /// Uniquely describes the content of this tile, in a way that can be
1537 /// (reasonably) efficiently hashed and compared.
1538 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
1539 #[cfg_attr(feature = "capture", derive(Serialize))]
1540 #[cfg_attr(feature = "replay", derive(Deserialize))]
1541 pub struct TileDescriptor {
1542 /// List of primitive instance unique identifiers. The uid is guaranteed
1543 /// to uniquely describe the content of the primitive template, while
1544 /// the other parameters describe the clip chain and instance params.
1545 pub prims: Vec<PrimitiveDescriptor>,
1547 /// List of clip node descriptors.
1548 clips: Vec<ItemUid>,
1550 /// List of image keys that this tile depends on.
1551 images: Vec<ImageDependency>,
1553 /// The set of opacity bindings that this tile depends on.
1554 // TODO(gw): Ugh, get rid of all opacity binding support!
1555 opacity_bindings: Vec<OpacityBinding>,
1557 /// List of the effects of transforms that we care about
1558 /// tracking for this tile.
1559 transforms: Vec<SpatialNodeKey>,
1561 /// Picture space rect that contains valid pixels region of this tile.
1562 local_valid_rect: PictureRect,
1564 /// List of the effects of color that we care about
1565 /// tracking for this tile.
1566 color_bindings: Vec<ColorBinding>,
1569 impl TileDescriptor {
1574 opacity_bindings: Vec::new(),
1576 transforms: Vec::new(),
1577 local_valid_rect: PictureRect::zero(),
1578 color_bindings: Vec::new(),
1582 /// Print debug information about this tile descriptor to a tree printer.
1583 fn print(&self, pt: &mut dyn PrintTreePrinter) {
1584 pt.new_level("current_descriptor".to_string());
1586 pt.new_level("prims".to_string());
1587 for prim in &self.prims {
1588 pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
1589 pt.add_item(format!("clip: p0={},{} p1={},{}",
1590 prim.prim_clip_box.min.x,
1591 prim.prim_clip_box.min.y,
1592 prim.prim_clip_box.max.x,
1593 prim.prim_clip_box.max.y,
1595 pt.add_item(format!("deps: t={} i={} o={} c={} color={}",
1596 prim.transform_dep_count,
1597 prim.image_dep_count,
1598 prim.opacity_binding_dep_count,
1599 prim.clip_dep_count,
1600 prim.color_binding_dep_count,
1606 if !self.clips.is_empty() {
1607 pt.new_level("clips".to_string());
1608 for clip in &self.clips {
1609 pt.new_level(format!("clip uid={}", clip.get_uid()));
1615 if !self.images.is_empty() {
1616 pt.new_level("images".to_string());
1617 for info in &self.images {
1618 pt.new_level(format!("key={:?}", info.key));
1619 pt.add_item(format!("generation={:?}", info.generation));
1625 if !self.opacity_bindings.is_empty() {
1626 pt.new_level("opacity_bindings".to_string());
1627 for opacity_binding in &self.opacity_bindings {
1628 pt.new_level(format!("binding={:?}", opacity_binding));
1634 if !self.transforms.is_empty() {
1635 pt.new_level("transforms".to_string());
1636 for transform in &self.transforms {
1637 pt.new_level(format!("spatial_node={:?}", transform));
1643 if !self.color_bindings.is_empty() {
1644 pt.new_level("color_bindings".to_string());
1645 for color_binding in &self.color_bindings {
1646 pt.new_level(format!("binding={:?}", color_binding));
1655 /// Clear the dependency information for a tile, when the dependencies
1656 /// are being rebuilt.
1657 fn clear(&mut self) {
1660 self.opacity_bindings.clear();
1661 self.images.clear();
1662 self.transforms.clear();
1663 self.local_valid_rect = PictureRect::zero();
1664 self.color_bindings.clear();
1668 /// Represents the dirty region of a tile cache picture.
1670 pub struct DirtyRegion {
1671 /// The individual filters that make up this region.
1672 pub filters: Vec<BatchFilter>,
1674 /// The overall dirty rect, a combination of dirty_rects
1675 pub combined: WorldRect,
1677 /// Spatial node of the picture cache this region represents
1678 spatial_node_index: SpatialNodeIndex,
1682 /// Construct a new dirty region tracker.
1684 spatial_node_index: SpatialNodeIndex,
1687 filters: Vec::with_capacity(16),
1688 combined: WorldRect::zero(),
1693 /// Reset the dirty regions back to empty
1696 spatial_node_index: SpatialNodeIndex,
1698 self.filters.clear();
1699 self.combined = WorldRect::zero();
1700 self.spatial_node_index = spatial_node_index;
1703 /// Add a dirty region to the tracker. Returns the visibility mask that corresponds to
1704 /// this region in the tracker.
1705 pub fn add_dirty_region(
1707 rect_in_pic_space: PictureRect,
1708 sub_slice_index: SubSliceIndex,
1709 spatial_tree: &SpatialTree,
1711 let map_pic_to_world = SpaceMapper::new_with_target(
1712 ROOT_SPATIAL_NODE_INDEX,
1713 self.spatial_node_index,
1714 WorldRect::max_rect(),
1718 let world_rect = map_pic_to_world
1719 .map(&rect_in_pic_space)
1722 // Include this in the overall dirty rect
1723 self.combined = self.combined.union(&world_rect);
1725 self.filters.push(BatchFilter {
1731 // TODO(gw): This returns a heap allocated object. Perhaps we can simplify this
1732 // logic? Although - it's only used very rarely so it may not be an issue.
1735 inflate_amount: f32,
1736 spatial_tree: &SpatialTree,
1738 let map_pic_to_world = SpaceMapper::new_with_target(
1739 ROOT_SPATIAL_NODE_INDEX,
1740 self.spatial_node_index,
1741 WorldRect::max_rect(),
1745 let mut filters = Vec::with_capacity(self.filters.len());
1746 let mut combined = WorldRect::zero();
1748 for filter in &self.filters {
1749 let rect_in_pic_space = filter.rect_in_pic_space.inflate(inflate_amount, inflate_amount);
1751 let world_rect = map_pic_to_world
1752 .map(&rect_in_pic_space)
1755 combined = combined.union(&world_rect);
1756 filters.push(BatchFilter {
1758 sub_slice_index: filter.sub_slice_index,
1765 spatial_node_index: self.spatial_node_index,
1770 #[derive(Debug, Copy, Clone)]
1771 pub enum BackdropKind {
1778 /// Stores information about the calculated opaque backdrop of this slice.
1779 #[derive(Debug, Copy, Clone)]
1780 pub struct BackdropInfo {
1781 /// The picture space rectangle that is known to be opaque. This is used
1782 /// to determine where subpixel AA can be used, and where alpha blending
1783 /// can be disabled.
1784 pub opaque_rect: PictureRect,
1785 /// Kind of the backdrop
1786 pub kind: Option<BackdropKind>,
1790 fn empty() -> Self {
1792 opaque_rect: PictureRect::zero(),
1799 pub struct TileCacheLoggerSlice {
1800 pub serialized_slice: String,
1801 pub local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>,
1804 #[cfg(any(feature = "capture", feature = "replay"))]
1805 macro_rules! declare_tile_cache_logger_updatelists {
1806 ( $( $name:ident : $ty:ty, )+ ) => {
1807 #[cfg_attr(feature = "capture", derive(Serialize))]
1808 #[cfg_attr(feature = "replay", derive(Deserialize))]
1809 struct TileCacheLoggerUpdateListsSerializer {
1810 pub ron_string: Vec<String>,
1813 pub struct TileCacheLoggerUpdateLists {
1815 /// Generate storage, one per interner.
1816 /// the tuple is a workaround to avoid the need for multiple
1817 /// fields that start with $name (macro concatenation).
1818 /// the string is .ron serialized updatelist at capture time;
1819 /// the updates is the list of DataStore updates (avoid UpdateList
1820 /// due to Default() requirements on the Keys) reconstructed at
1822 pub $name: (Vec<String>, Vec<UpdateList<<$ty as Internable>::Key>>),
1826 impl TileCacheLoggerUpdateLists {
1827 pub fn new() -> Self {
1828 TileCacheLoggerUpdateLists {
1830 $name : ( Vec::new(), Vec::new() ),
1835 /// serialize all interners in updates to .ron
1836 #[cfg(feature = "capture")]
1837 fn serialize_updates(
1839 updates: &InternerUpdates
1842 self.$name.0.push(ron::ser::to_string_pretty(&updates.$name, Default::default()).unwrap());
1846 fn is_empty(&self) -> bool {
1848 if !self.$name.0.is_empty() { return false; }
1853 #[cfg(feature = "capture")]
1854 fn to_ron(&self) -> String {
1855 let mut serializer =
1856 TileCacheLoggerUpdateListsSerializer { ron_string: Vec::new() };
1858 serializer.ron_string.push(
1859 ron::ser::to_string_pretty(&self.$name.0, Default::default()).unwrap());
1861 ron::ser::to_string_pretty(&serializer, Default::default()).unwrap()
1864 #[cfg(feature = "replay")]
1865 pub fn from_ron(&mut self, text: &str) {
1866 let serializer : TileCacheLoggerUpdateListsSerializer =
1867 match ron::de::from_str(&text) {
1868 Ok(data) => { data }
1870 println!("ERROR: failed to deserialize updatelist: {:?}\n{:?}", &text, e);
1876 let ron_lists : Vec<String> = ron::de::from_str(&serializer.ron_string[index]).unwrap();
1877 self.$name.1 = ron_lists.iter()
1878 .map( |list| ron::de::from_str(&list).unwrap() )
1882 // error: value assigned to `index` is never read
1886 /// helper method to add a stringified version of all interned keys into
1887 /// a lookup table based on ItemUid. Use strings as a form of type erasure
1888 /// so all UpdateLists can go into a single map.
1889 /// Then during analysis, when we see an invalidation reason due to
1890 /// "ItemUid such and such was added to the tile primitive list", the lookup
1891 /// allows mapping that back into something readable.
1892 #[cfg(feature = "replay")]
1893 pub fn insert_in_lookup(
1895 itemuid_to_string: &mut HashMap<ItemUid, String>)
1899 for list in &self.$name.1 {
1900 for insertion in &list.insertions {
1901 itemuid_to_string.insert(
1903 format!("{:?}", insertion.value));
1913 #[cfg(any(feature = "capture", feature = "replay"))]
1914 crate::enumerate_interners!(declare_tile_cache_logger_updatelists);
1916 #[cfg(not(any(feature = "capture", feature = "replay")))]
1917 pub struct TileCacheLoggerUpdateLists {
1920 #[cfg(not(any(feature = "capture", feature = "replay")))]
1921 impl TileCacheLoggerUpdateLists {
1922 pub fn new() -> Self { TileCacheLoggerUpdateLists {} }
1923 fn is_empty(&self) -> bool { true }
1926 /// Log tile cache activity for one single frame.
1927 /// Also stores the commands sent to the interning data_stores
1928 /// so we can see which items were created or destroyed this frame,
1929 /// and correlate that with tile invalidation activity.
1930 pub struct TileCacheLoggerFrame {
1931 /// slices in the frame, one per take_context call
1932 pub slices: Vec<TileCacheLoggerSlice>,
1933 /// interning activity
1934 pub update_lists: TileCacheLoggerUpdateLists
1937 impl TileCacheLoggerFrame {
1938 pub fn new() -> Self {
1939 TileCacheLoggerFrame {
1941 update_lists: TileCacheLoggerUpdateLists::new()
1945 pub fn is_empty(&self) -> bool {
1946 self.slices.is_empty() && self.update_lists.is_empty()
1950 /// Log tile cache activity whenever anything happens in take_context.
1951 pub struct TileCacheLogger {
1952 /// next write pointer
1953 pub write_index : usize,
1954 /// ron serialization of tile caches;
1955 pub frames: Vec<TileCacheLoggerFrame>
1958 impl TileCacheLogger {
1962 let mut frames = Vec::with_capacity(num_frames);
1963 for _i in 0..num_frames { // no Clone so no resize
1964 frames.push(TileCacheLoggerFrame::new());
1972 pub fn is_enabled(&self) -> bool {
1973 !self.frames.is_empty()
1976 #[cfg(feature = "capture")]
1979 serialized_slice: String,
1980 local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>
1982 if !self.is_enabled() {
1985 self.frames[self.write_index].slices.push(
1986 TileCacheLoggerSlice {
1988 local_to_world_transform });
1991 #[cfg(feature = "capture")]
1992 pub fn serialize_updates(&mut self, updates: &InternerUpdates) {
1993 if !self.is_enabled() {
1996 self.frames[self.write_index].update_lists.serialize_updates(updates);
1999 /// see if anything was written in this frame, and if so,
2000 /// advance the write index in a circular way and clear the
2001 /// recorded string.
2002 pub fn advance(&mut self) {
2003 if !self.is_enabled() || self.frames[self.write_index].is_empty() {
2006 self.write_index = self.write_index + 1;
2007 if self.write_index >= self.frames.len() {
2008 self.write_index = 0;
2010 self.frames[self.write_index] = TileCacheLoggerFrame::new();
2013 #[cfg(feature = "capture")]
2014 pub fn save_capture(
2015 &self, root: &PathBuf
2017 if !self.is_enabled() {
2022 info!("saving tile cache log");
2023 let path_tile_cache = root.join("tile_cache");
2024 if !path_tile_cache.is_dir() {
2025 fs::create_dir(&path_tile_cache).unwrap();
2028 let mut files_written = 0;
2029 for ix in 0..self.frames.len() {
2030 // ...and start with write_index, since that's the oldest entry
2031 // that we're about to overwrite. However when we get to
2032 // save_capture, we've add()ed entries but haven't advance()d yet,
2033 // so the actual oldest entry is write_index + 1
2034 let index = (self.write_index + 1 + ix) % self.frames.len();
2035 if self.frames[index].is_empty() {
2039 let filename = path_tile_cache.join(format!("frame{:05}.ron", files_written));
2040 let mut output = File::create(filename).unwrap();
2041 output.write_all(b"// slice data\n").unwrap();
2042 output.write_all(b"[\n").unwrap();
2043 for item in &self.frames[index].slices {
2044 output.write_all(b"( transform:\n").unwrap();
2046 ron::ser::to_string_pretty(
2047 &item.local_to_world_transform, Default::default()).unwrap();
2048 output.write_all(transform.as_bytes()).unwrap();
2049 output.write_all(b",\n tile_cache:\n").unwrap();
2050 output.write_all(item.serialized_slice.as_bytes()).unwrap();
2051 output.write_all(b"\n),\n").unwrap();
2053 output.write_all(b"]\n\n").unwrap();
2055 output.write_all(b"// @@@ chunk @@@\n\n").unwrap();
2057 output.write_all(b"// interning data\n").unwrap();
2058 output.write_all(self.frames[index].update_lists.to_ron().as_bytes()).unwrap();
2060 files_written = files_written + 1;
2065 /// Represents the native surfaces created for a picture cache, if using
2066 /// a native compositor. An opaque and alpha surface is always created,
2067 /// but tiles are added to a surface based on current opacity. If the
2068 /// calculated opacity of a tile changes, the tile is invalidated and
2069 /// attached to a different native surface. This means that we don't
2070 /// need to invalidate the entire surface if only some tiles are changing
2071 /// opacity. It also means we can take advantage of opaque tiles on cache
2072 /// slices where only some of the tiles are opaque. There is an assumption
2073 /// that creating a native surface is cheap, and only when a tile is added
2074 /// to a surface is there a significant cost. This assumption holds true
2075 /// for the current native compositor implementations on Windows and Mac.
2076 pub struct NativeSurface {
2077 /// Native surface for opaque tiles
2078 pub opaque: NativeSurfaceId,
2079 /// Native surface for alpha tiles
2080 pub alpha: NativeSurfaceId,
2083 /// Hash key for an external native compositor surface
2084 #[derive(PartialEq, Eq, Hash)]
2085 pub struct ExternalNativeSurfaceKey {
2086 /// The YUV/RGB image keys that are used to draw this surface.
2087 pub image_keys: [ImageKey; 3],
2088 /// The current device size of the surface.
2089 pub size: DeviceIntSize,
2090 /// True if this is an 'external' compositor surface created via
2091 /// Compositor::create_external_surface.
2092 pub is_external_surface: bool,
2095 /// Information about a native compositor surface cached between frames.
2096 pub struct ExternalNativeSurface {
2097 /// If true, the surface was used this frame. Used for a simple form
2098 /// of GC to remove old surfaces.
2099 pub used_this_frame: bool,
2100 /// The native compositor surface handle
2101 pub native_surface_id: NativeSurfaceId,
2102 /// List of image keys, and current image generations, that are drawn in this surface.
2103 /// The image generations are used to check if the compositor surface is dirty and
2104 /// needs to be updated.
2105 pub image_dependencies: [ImageDependency; 3],
2108 /// The key that identifies a tile cache instance. For now, it's simple the index of
2109 /// the slice as it was created during scene building.
2110 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2111 #[cfg_attr(feature = "capture", derive(Serialize))]
2112 #[cfg_attr(feature = "replay", derive(Deserialize))]
2113 pub struct SliceId(usize);
2116 pub fn new(index: usize) -> Self {
2121 /// Information that is required to reuse or create a new tile cache. Created
2122 /// during scene building and passed to the render backend / frame builder.
2123 pub struct TileCacheParams {
2124 // Index of the slice (also effectively the key of the tile cache, though we use SliceId where that matters)
2126 // Flags describing content of this cache (e.g. scrollbars)
2127 pub slice_flags: SliceFlags,
2128 // The anchoring spatial node / scroll root
2129 pub spatial_node_index: SpatialNodeIndex,
2130 // Optional background color of this tilecache. If present, can be used as an optimization
2131 // to enable opaque blending and/or subpixel AA in more places.
2132 pub background_color: Option<ColorF>,
2133 // List of clips shared by all prims that are promoted to this tile cache
2134 pub shared_clips: Vec<ClipInstance>,
2135 // The clip chain handle representing `shared_clips`
2136 pub shared_clip_chain: ClipChainId,
2137 // Virtual surface sizes are always square, so this represents both the width and height
2138 pub virtual_surface_size: i32,
2139 // The number of compositor surfaces that are being requested for this tile cache.
2140 // This is only a suggestion - the tile cache will clamp this as a reasonable number
2141 // and only promote a limited number of surfaces.
2142 pub compositor_surface_count: usize,
2145 /// Defines which sub-slice (effectively a z-index) a primitive exists on within
2146 /// a picture cache instance.
2147 #[cfg_attr(feature = "capture", derive(Serialize))]
2148 #[cfg_attr(feature = "replay", derive(Deserialize))]
2149 #[derive(Debug, Copy, Clone, PartialEq)]
2150 pub struct SubSliceIndex(u8);
2152 impl SubSliceIndex {
2153 pub const DEFAULT: SubSliceIndex = SubSliceIndex(0);
2155 pub fn new(index: usize) -> Self {
2156 SubSliceIndex(index as u8)
2159 /// Return true if this sub-slice is the primary sub-slice (for now, we assume
2160 /// that only the primary sub-slice may be opaque and support subpixel AA, for example).
2161 pub fn is_primary(&self) -> bool {
2166 /// Wrapper struct around an external surface descriptor with a little more information
2167 /// that the picture caching code needs.
2168 pub struct CompositorSurface {
2169 // External surface descriptor used by compositing logic
2170 pub descriptor: ExternalSurfaceDescriptor,
2171 // The compositor surface rect + any intersecting prims. Later prims that intersect
2172 // with this must be added to the next sub-slice.
2173 prohibited_rect: PictureRect,
2174 // If the compositor surface content is opaque.
2175 pub is_opaque: bool,
2178 /// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most
2179 /// picture cache instances will have only a single sub-slice. The exception to this is when
2180 /// a picture cache has compositor surfaces, in which case sub slices are used to interleave
2181 /// content under or order the compositor surface(s).
2182 pub struct SubSlice {
2183 /// Hash of tiles present in this picture.
2184 pub tiles: FastHashMap<TileOffset, Box<Tile>>,
2185 /// The allocated compositor surfaces for this picture cache. May be None if
2186 /// not using native compositor, or if the surface was destroyed and needs
2187 /// to be reallocated next time this surface contains valid tiles.
2188 pub native_surface: Option<NativeSurface>,
2189 /// List of compositor surfaces that have been promoted from primitives
2190 /// in this tile cache.
2191 pub compositor_surfaces: Vec<CompositorSurface>,
2195 /// Construct a new sub-slice
2198 tiles: FastHashMap::default(),
2199 native_surface: None,
2200 compositor_surfaces: Vec::new(),
2204 /// Reset the list of compositor surfaces that follow this sub-slice.
2205 /// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface.
2206 fn reset(&mut self) {
2207 self.compositor_surfaces.clear();
2210 /// Resize the tile grid to match a new tile bounds
2211 fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> {
2212 let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
2213 self.tiles.reserve(new_tile_rect.size.area() as usize);
2215 for y in new_tile_rect.origin.y .. new_tile_rect.origin.y + new_tile_rect.size.height {
2216 for x in new_tile_rect.origin.x .. new_tile_rect.origin.x + new_tile_rect.size.width {
2217 let key = TileOffset::new(x, y);
2218 let tile = old_tiles
2220 .unwrap_or_else(|| {
2221 Box::new(Tile::new(key))
2223 self.tiles.insert(key, tile);
2231 /// Represents a cache of tiles that make up a picture primitives.
2232 pub struct TileCacheInstance {
2233 /// Index of the tile cache / slice for this frame builder. It's determined
2234 /// by the setup_picture_caching method during flattening, which splits the
2235 /// picture tree into multiple slices. It's used as a simple input to the tile
2236 /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed
2237 /// between display lists - this seems very unlikely to occur on most pages, but
2238 /// can be revisited if we ever notice that.
2240 /// Propagated information about the slice
2241 pub slice_flags: SliceFlags,
2242 /// The currently selected tile size to use for this cache
2243 pub current_tile_size: DeviceIntSize,
2244 /// The list of sub-slices in this tile cache
2245 pub sub_slices: Vec<SubSlice>,
2246 /// The positioning node for this tile cache.
2247 pub spatial_node_index: SpatialNodeIndex,
2248 /// List of opacity bindings, with some extra information
2249 /// about whether they changed since last frame.
2250 opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
2251 /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
2252 old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
2253 /// A helper to compare transforms between previous and current frame.
2254 spatial_node_comparer: SpatialNodeComparer,
2255 /// List of color bindings, with some extra information
2256 /// about whether they changed since last frame.
2257 color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
2258 /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
2259 old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
2260 /// The current dirty region tracker for this picture.
2261 pub dirty_region: DirtyRegion,
2262 /// Current size of tiles in picture units.
2263 tile_size: PictureSize,
2264 /// Tile coords of the currently allocated grid.
2265 tile_rect: TileRect,
2266 /// Pre-calculated versions of the tile_rect above, used to speed up the
2267 /// calculations in get_tile_coords_for_rect.
2268 tile_bounds_p0: TileOffset,
2269 tile_bounds_p1: TileOffset,
2270 /// Local rect (unclipped) of the picture this cache covers.
2271 pub local_rect: PictureRect,
2272 /// The local clip rect, from the shared clips of this picture.
2273 pub local_clip_rect: PictureRect,
2274 /// The surface index that this tile cache will be drawn into.
2275 surface_index: SurfaceIndex,
2276 /// The background color from the renderer. If this is set opaque, we know it's
2277 /// fine to clear the tiles to this and allow subpixel text on the first slice.
2278 pub background_color: Option<ColorF>,
2279 /// Information about the calculated backdrop content of this cache.
2280 pub backdrop: BackdropInfo,
2281 /// The allowed subpixel mode for this surface, which depends on the detected
2282 /// opacity of the background.
2283 pub subpixel_mode: SubpixelMode,
2284 /// A list of clip handles that exist on every (top-level) primitive in this picture.
2285 /// It's often the case that these are root / fixed position clips. By handling them
2286 /// here, we can avoid applying them to the items, which reduces work, but more importantly
2287 /// reduces invalidations.
2288 pub shared_clips: Vec<ClipInstance>,
2289 /// The clip chain that represents the shared_clips above. Used to build the local
2290 /// clip rect for this tile cache.
2291 shared_clip_chain: ClipChainId,
2292 /// The current transform of the picture cache root spatial node
2293 root_transform: ScaleOffset,
2294 /// The number of frames until this cache next evaluates what tile size to use.
2295 /// If a picture rect size is regularly changing just around a size threshold,
2296 /// we don't want to constantly invalidate and reallocate different tile size
2297 /// configuration each frame.
2298 frames_until_size_eval: usize,
2299 /// The current fractional offset of the cached picture
2300 fract_offset: PictureVector2D,
2301 /// The current device fractional offset of the cached picture
2302 device_fract_offset: DeviceVector2D,
2303 /// For DirectComposition, virtual surfaces don't support negative coordinates. However,
2304 /// picture cache tile coordinates can be negative. To handle this, we apply an offset
2305 /// to each tile in DirectComposition. We want to change this as little as possible,
2306 /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate
2307 /// which is outside the virtual surface bounds, we must change this to allow
2308 /// correct remapping of the coordinates passed to BeginDraw in DC.
2309 virtual_offset: DeviceIntPoint,
2310 /// keep around the hash map used as compare_cache to avoid reallocating it each
2312 compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
2313 /// The current device position of this cache. Used to set the compositor
2314 /// offset of the surface when building the visual tree.
2315 pub device_position: DevicePoint,
2316 /// The currently considered tile size override. Used to check if we should
2317 /// re-evaluate tile size, even if the frame timer hasn't expired.
2318 tile_size_override: Option<DeviceIntSize>,
2319 /// z-buffer ID assigned to the opaque backdrop, if there is one, in this slice
2320 pub z_id_backdrop: ZBufferId,
2321 /// A cache of compositor surfaces that are retained between frames
2322 pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
2323 /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting
2327 enum SurfacePromotionResult {
2334 impl TileCacheInstance {
2335 pub fn new(params: TileCacheParams) -> Self {
2336 // Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure
2337 // we don't create a huge number of OS compositor tiles and sub-slices.
2338 let sub_slice_count = params.compositor_surface_count.min(MAX_COMPOSITOR_SURFACES) + 1;
2340 let mut sub_slices = Vec::with_capacity(sub_slice_count);
2341 for _ in 0 .. sub_slice_count {
2342 sub_slices.push(SubSlice::new());
2346 slice: params.slice,
2347 slice_flags: params.slice_flags,
2348 spatial_node_index: params.spatial_node_index,
2350 opacity_bindings: FastHashMap::default(),
2351 old_opacity_bindings: FastHashMap::default(),
2352 spatial_node_comparer: SpatialNodeComparer::new(),
2353 color_bindings: FastHashMap::default(),
2354 old_color_bindings: FastHashMap::default(),
2355 dirty_region: DirtyRegion::new(params.spatial_node_index),
2356 tile_size: PictureSize::zero(),
2357 tile_rect: TileRect::zero(),
2358 tile_bounds_p0: TileOffset::zero(),
2359 tile_bounds_p1: TileOffset::zero(),
2360 local_rect: PictureRect::zero(),
2361 local_clip_rect: PictureRect::zero(),
2362 surface_index: SurfaceIndex(0),
2363 background_color: params.background_color,
2364 backdrop: BackdropInfo::empty(),
2365 subpixel_mode: SubpixelMode::Allow,
2366 root_transform: ScaleOffset::identity(),
2367 shared_clips: params.shared_clips,
2368 shared_clip_chain: params.shared_clip_chain,
2369 current_tile_size: DeviceIntSize::zero(),
2370 frames_until_size_eval: 0,
2371 fract_offset: PictureVector2D::zero(),
2372 device_fract_offset: DeviceVector2D::zero(),
2373 // Default to centering the virtual offset in the middle of the DC virtual surface
2374 virtual_offset: DeviceIntPoint::new(
2375 params.virtual_surface_size / 2,
2376 params.virtual_surface_size / 2,
2378 compare_cache: FastHashMap::default(),
2379 device_position: DevicePoint::zero(),
2380 tile_size_override: None,
2381 z_id_backdrop: ZBufferId::invalid(),
2382 external_native_surface_cache: FastHashMap::default(),
2383 frame_id: FrameId::INVALID,
2387 /// Return the total number of tiles allocated by this tile cache
2388 pub fn tile_count(&self) -> usize {
2389 self.tile_rect.size.area() as usize * self.sub_slices.len()
2392 /// Reset this tile cache with the updated parameters from a new scene
2393 /// that has arrived. This allows the tile cache to be retained across
2395 pub fn prepare_for_new_scene(
2397 params: TileCacheParams,
2398 resource_cache: &mut ResourceCache,
2400 // We should only receive updated state for matching slice key
2401 assert_eq!(self.slice, params.slice);
2403 // Determine how many sub-slices we need, based on how many compositor surface prims are
2404 // in the supplied primitive list.
2405 let required_sub_slice_count = params.compositor_surface_count.min(MAX_COMPOSITOR_SURFACES) + 1;
2407 if self.sub_slices.len() != required_sub_slice_count {
2408 self.tile_rect = TileRect::zero();
2410 if self.sub_slices.len() > required_sub_slice_count {
2411 let old_sub_slices = self.sub_slices.split_off(required_sub_slice_count);
2413 for mut sub_slice in old_sub_slices {
2414 for tile in sub_slice.tiles.values_mut() {
2415 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2416 if let Some(id) = id.take() {
2417 resource_cache.destroy_compositor_tile(id);
2422 if let Some(native_surface) = sub_slice.native_surface {
2423 resource_cache.destroy_compositor_surface(native_surface.opaque);
2424 resource_cache.destroy_compositor_surface(native_surface.alpha);
2428 while self.sub_slices.len() < required_sub_slice_count {
2429 self.sub_slices.push(SubSlice::new());
2434 // Store the parameters from the scene builder for this slice. Other
2435 // params in the tile cache are retained and reused, or are always
2436 // updated during pre/post_update.
2437 self.slice_flags = params.slice_flags;
2438 self.spatial_node_index = params.spatial_node_index;
2439 self.background_color = params.background_color;
2440 self.shared_clips = params.shared_clips;
2441 self.shared_clip_chain = params.shared_clip_chain;
2443 // Since the slice flags may have changed, ensure we re-evaluate the
2444 // appropriate tile size for this cache next update.
2445 self.frames_until_size_eval = 0;
2448 /// Destroy any manually managed resources before this picture cache is
2449 /// destroyed, such as native compositor surfaces.
2452 resource_cache: &mut ResourceCache,
2454 for sub_slice in self.sub_slices {
2455 if let Some(native_surface) = sub_slice.native_surface {
2456 resource_cache.destroy_compositor_surface(native_surface.opaque);
2457 resource_cache.destroy_compositor_surface(native_surface.alpha);
2461 for (_, external_surface) in self.external_native_surface_cache {
2462 resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
2466 /// Get the tile coordinates for a given rectangle.
2467 fn get_tile_coords_for_rect(
2470 ) -> (TileOffset, TileOffset) {
2471 // Get the tile coordinates in the picture space.
2472 let mut p0 = TileOffset::new(
2473 (rect.origin.x / self.tile_size.width).floor() as i32,
2474 (rect.origin.y / self.tile_size.height).floor() as i32,
2477 let mut p1 = TileOffset::new(
2478 ((rect.origin.x + rect.size.width) / self.tile_size.width).ceil() as i32,
2479 ((rect.origin.y + rect.size.height) / self.tile_size.height).ceil() as i32,
2482 // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on.
2483 p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
2484 p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
2485 p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
2486 p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
2491 /// Update transforms, opacity, color bindings and tile rects.
2494 pic_rect: PictureRect,
2495 surface_index: SurfaceIndex,
2496 frame_context: &FrameVisibilityContext,
2497 frame_state: &mut FrameVisibilityState,
2499 self.surface_index = surface_index;
2500 self.local_rect = pic_rect;
2501 self.local_clip_rect = PictureRect::max_rect();
2503 for sub_slice in &mut self.sub_slices {
2507 // Backdrop surfaces get the first z_id. Subslices and compositor surfaces then get
2508 // allocated a z_id each.
2509 self.z_id_backdrop = frame_state.composite_state.z_generator.next();
2511 // Reset the opaque rect + subpixel mode, as they are calculated
2512 // during the prim dependency checks.
2513 self.backdrop = BackdropInfo::empty();
2515 let pic_to_world_mapper = SpaceMapper::new_with_target(
2516 ROOT_SPATIAL_NODE_INDEX,
2517 self.spatial_node_index,
2518 frame_context.global_screen_world_rect,
2519 frame_context.spatial_tree,
2522 // If there is a valid set of shared clips, build a clip chain instance for this,
2523 // which will provide a local clip rect. This is useful for establishing things
2524 // like whether the backdrop rect supplied by Gecko can be considered opaque.
2525 if self.shared_clip_chain != ClipChainId::NONE {
2526 let shared_clips = &mut frame_state.scratch.picture.clip_chain_ids;
2527 shared_clips.clear();
2529 let map_local_to_surface = SpaceMapper::new(
2530 self.spatial_node_index,
2534 let mut current_clip_chain_id = self.shared_clip_chain;
2535 while current_clip_chain_id != ClipChainId::NONE {
2536 shared_clips.push(current_clip_chain_id);
2537 let clip_chain_node = &frame_state.clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
2538 current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
2541 frame_state.clip_store.set_active_clips(
2542 LayoutRect::max_rect(),
2543 self.spatial_node_index,
2544 map_local_to_surface.ref_spatial_node_index,
2546 frame_context.spatial_tree,
2547 &mut frame_state.data_stores.clip,
2550 let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
2551 pic_rect.cast_unit(),
2552 &map_local_to_surface,
2553 &pic_to_world_mapper,
2554 frame_context.spatial_tree,
2555 frame_state.gpu_cache,
2556 frame_state.resource_cache,
2557 frame_context.global_device_pixel_scale,
2558 &frame_context.global_screen_world_rect,
2559 &mut frame_state.data_stores.clip,
2564 // Ensure that if the entire picture cache is clipped out, the local
2565 // clip rect is zero. This makes sure we don't register any occluders
2566 // that are actually off-screen.
2567 self.local_clip_rect = clip_chain_instance.map_or(PictureRect::zero(), |clip_chain_instance| {
2568 clip_chain_instance.pic_clip_rect
2572 // Advance the current frame ID counter for this picture cache (must be done
2573 // after any retained prev state is taken above).
2574 self.frame_id.advance();
2576 // Notify the spatial node comparer that a new frame has started, and the
2577 // current reference spatial node for this tile cache.
2578 self.spatial_node_comparer.next_frame(self.spatial_node_index);
2580 // At the start of the frame, step through each current compositor surface
2581 // and mark it as unused. Later, this is used to free old compositor surfaces.
2582 // TODO(gw): In future, we might make this more sophisticated - for example,
2583 // retaining them for >1 frame if unused, or retaining them in some
2584 // kind of pool to reduce future allocations.
2585 for external_native_surface in self.external_native_surface_cache.values_mut() {
2586 external_native_surface.used_this_frame = false;
2589 // Only evaluate what tile size to use fairly infrequently, so that we don't end
2590 // up constantly invalidating and reallocating tiles if the picture rect size is
2591 // changing near a threshold value.
2592 if self.frames_until_size_eval == 0 ||
2593 self.tile_size_override != frame_context.config.tile_size_override {
2595 // Work out what size tile is appropriate for this picture cache.
2596 let desired_tile_size = match frame_context.config.tile_size_override {
2597 Some(tile_size_override) => {
2601 if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) {
2602 if pic_rect.size.width <= pic_rect.size.height {
2603 TILE_SIZE_SCROLLBAR_VERTICAL
2605 TILE_SIZE_SCROLLBAR_HORIZONTAL
2608 frame_state.resource_cache.texture_cache.default_picture_tile_size()
2613 // If the desired tile size has changed, then invalidate and drop any
2615 if desired_tile_size != self.current_tile_size {
2616 for sub_slice in &mut self.sub_slices {
2617 // Destroy any native surfaces on the tiles that will be dropped due
2619 if let Some(native_surface) = sub_slice.native_surface.take() {
2620 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2621 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2623 sub_slice.tiles.clear();
2625 self.tile_rect = TileRect::zero();
2626 self.current_tile_size = desired_tile_size;
2629 // Reset counter until next evaluating the desired tile size. This is an
2631 self.frames_until_size_eval = 120;
2632 self.tile_size_override = frame_context.config.tile_size_override;
2635 // Map an arbitrary point in picture space to world space, to work out
2636 // what the fractional translation is that's applied by this scroll root.
2637 // TODO(gw): I'm not 100% sure this is right. At least, in future, we should
2638 // make a specific API for this, and/or enforce that the picture
2639 // cache transform only includes scale and/or translation (we
2640 // already ensure it doesn't have perspective).
2641 let world_origin = pic_to_world_mapper
2642 .map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0)))
2643 .expect("bug: unable to map origin to world space")
2646 // Get the desired integer device coordinate
2647 let device_origin = world_origin * frame_context.global_device_pixel_scale;
2648 let desired_device_origin = device_origin.round();
2649 self.device_position = desired_device_origin;
2650 self.device_fract_offset = desired_device_origin - device_origin;
2652 // Unmap from device space to world space rect
2653 let ref_world_rect = WorldRect::new(
2654 desired_device_origin / frame_context.global_device_pixel_scale,
2655 WorldSize::new(1.0, 1.0),
2658 // Unmap from world space to picture space; this should be the fractional offset
2659 // required in picture space to align in device space
2660 self.fract_offset = pic_to_world_mapper
2661 .unmap(&ref_world_rect)
2662 .expect("bug: unable to unmap ref world rect")
2666 // Do a hacky diff of opacity binding values from the last frame. This is
2667 // used later on during tile invalidation tests.
2668 let current_properties = frame_context.scene_properties.float_properties();
2669 mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings);
2671 self.opacity_bindings.clear();
2672 for (id, value) in current_properties {
2673 let changed = match self.old_opacity_bindings.get(id) {
2674 Some(old_property) => !old_property.value.approx_eq(value),
2677 self.opacity_bindings.insert(*id, OpacityBindingInfo {
2683 // Do a hacky diff of color binding values from the last frame. This is
2684 // used later on during tile invalidation tests.
2685 let current_properties = frame_context.scene_properties.color_properties();
2686 mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
2688 self.color_bindings.clear();
2689 for (id, value) in current_properties {
2690 let changed = match self.old_color_bindings.get(id) {
2691 Some(old_property) => old_property.value != (*value).into(),
2694 self.color_bindings.insert(*id, ColorBindingInfo {
2695 value: (*value).into(),
2700 let world_tile_size = WorldSize::new(
2701 self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
2702 self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
2705 // We know that this is an exact rectangle, since we (for now) only support tile
2706 // caches where the scroll root is in the root coordinate system.
2707 let local_tile_rect = pic_to_world_mapper
2708 .unmap(&WorldRect::new(WorldPoint::zero(), world_tile_size))
2709 .expect("bug: unable to get local tile rect");
2711 self.tile_size = local_tile_rect.size;
2713 let screen_rect_in_pic_space = pic_to_world_mapper
2714 .unmap(&frame_context.global_screen_world_rect)
2715 .expect("unable to unmap screen rect");
2717 // Inflate the needed rect a bit, so that we retain tiles that we have drawn
2718 // but have just recently gone off-screen. This means that we avoid re-drawing
2719 // tiles if the user is scrolling up and down small amounts, at the cost of
2720 // a bit of extra texture memory.
2721 let desired_rect_in_pic_space = screen_rect_in_pic_space
2722 .inflate(0.0, 1.0 * self.tile_size.height);
2724 let needed_rect_in_pic_space = desired_rect_in_pic_space
2725 .intersection(&pic_rect)
2726 .unwrap_or_else(PictureRect::zero);
2728 let p0 = needed_rect_in_pic_space.origin;
2729 let p1 = needed_rect_in_pic_space.bottom_right();
2731 let x0 = (p0.x / local_tile_rect.size.width).floor() as i32;
2732 let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32;
2734 let y0 = (p0.y / local_tile_rect.size.height).floor() as i32;
2735 let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32;
2737 let x_tiles = x1 - x0;
2738 let y_tiles = y1 - y0;
2739 let new_tile_rect = TileRect::new(
2740 TileOffset::new(x0, y0),
2741 TileSize::new(x_tiles, y_tiles),
2744 // Determine whether the current bounds of the tile grid will exceed the
2745 // bounds of the DC virtual surface, taking into account the current
2746 // virtual offset. If so, we need to invalidate all tiles, and set up
2747 // a new virtual offset, centered around the current tile grid.
2749 let virtual_surface_size = frame_context.config.compositor_kind.get_virtual_surface_size();
2750 // We only need to invalidate in this case if the underlying platform
2751 // uses virtual surfaces.
2752 if virtual_surface_size > 0 {
2753 // Get the extremities of the tile grid after virtual offset is applied
2754 let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width;
2755 let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height;
2756 let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width;
2757 let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height;
2759 let need_new_virtual_offset = tx0 < 0 ||
2761 tx1 >= virtual_surface_size ||
2762 ty1 >= virtual_surface_size;
2764 if need_new_virtual_offset {
2765 // Calculate a new virtual offset, centered around the middle of the
2766 // current tile grid. This means we won't need to invalidate and get
2767 // a new offset for a long time!
2768 self.virtual_offset = DeviceIntPoint::new(
2769 (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width,
2770 (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height,
2773 // Invalidate all native tile surfaces. They will be re-allocated next time
2774 // they are scheduled to be rasterized.
2775 for sub_slice in &mut self.sub_slices {
2776 for tile in sub_slice.tiles.values_mut() {
2777 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2778 if let Some(id) = id.take() {
2779 frame_state.resource_cache.destroy_compositor_tile(id);
2780 tile.surface = None;
2781 // Invalidate the entire tile to force a redraw.
2782 // TODO(gw): Add a new invalidation reason for virtual offset changing
2783 tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2788 // Destroy the native virtual surfaces. They will be re-allocated next time a tile
2789 // that references them is scheduled to draw.
2790 if let Some(native_surface) = sub_slice.native_surface.take() {
2791 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2792 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2798 // Rebuild the tile grid if the picture cache rect has changed.
2799 if new_tile_rect != self.tile_rect {
2800 for sub_slice in &mut self.sub_slices {
2801 let mut old_tiles = sub_slice.resize(new_tile_rect);
2803 // When old tiles that remain after the loop, dirty rects are not valid.
2804 if !old_tiles.is_empty() {
2805 frame_state.composite_state.dirty_rects_are_valid = false;
2808 // Any old tiles that remain after the loop above are going to be dropped. For
2809 // simple composite mode, the texture cache handle will expire and be collected
2810 // by the texture cache. For native compositor mode, we need to explicitly
2811 // invoke a callback to the client to destroy that surface.
2812 frame_state.composite_state.destroy_native_tiles(
2813 old_tiles.values_mut(),
2814 frame_state.resource_cache,
2819 // This is duplicated information from tile_rect, but cached here to avoid
2820 // redundant calculations during get_tile_coords_for_rect
2821 self.tile_bounds_p0 = TileOffset::new(x0, y0);
2822 self.tile_bounds_p1 = TileOffset::new(x1, y1);
2823 self.tile_rect = new_tile_rect;
2825 let mut world_culling_rect = WorldRect::zero();
2827 let mut ctx = TilePreUpdateContext {
2828 pic_to_world_mapper,
2829 fract_offset: self.fract_offset,
2830 device_fract_offset: self.device_fract_offset,
2831 background_color: self.background_color,
2832 global_screen_world_rect: frame_context.global_screen_world_rect,
2833 tile_size: self.tile_size,
2834 frame_id: self.frame_id,
2837 // Pre-update each tile
2838 for sub_slice in &mut self.sub_slices {
2839 for tile in sub_slice.tiles.values_mut() {
2840 tile.pre_update(&ctx);
2842 // Only include the tiles that are currently in view into the world culling
2843 // rect. This is a very important optimization for a couple of reasons:
2844 // (1) Primitives that intersect with tiles in the grid that are not currently
2845 // visible can be skipped from primitive preparation, clip chain building
2846 // and tile dependency updates.
2847 // (2) When we need to allocate an off-screen surface for a child picture (for
2848 // example a CSS filter) we clip the size of the GPU surface to the world
2849 // culling rect below (to ensure we draw enough of it to be sampled by any
2850 // tiles that reference it). Making the world culling rect only affected
2851 // by visible tiles (rather than the entire virtual tile display port) can
2852 // result in allocating _much_ smaller GPU surfaces for cases where the
2853 // true off-screen surface size is very large.
2854 if tile.is_visible {
2855 world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
2859 // The background color can only be applied to the first sub-slice.
2860 ctx.background_color = None;
2863 // If compositor mode is changed, need to drop all incompatible tiles.
2864 match frame_context.config.compositor_kind {
2865 CompositorKind::Draw { .. } => {
2866 for sub_slice in &mut self.sub_slices {
2867 for tile in sub_slice.tiles.values_mut() {
2868 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
2869 if let Some(id) = id.take() {
2870 frame_state.resource_cache.destroy_compositor_tile(id);
2872 tile.surface = None;
2873 // Invalidate the entire tile to force a redraw.
2874 tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2878 if let Some(native_surface) = sub_slice.native_surface.take() {
2879 frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
2880 frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
2884 for (_, external_surface) in self.external_native_surface_cache.drain() {
2885 frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
2888 CompositorKind::Native { .. } => {
2889 // This could hit even when compositor mode is not changed,
2890 // then we need to check if there are incompatible tiles.
2891 for sub_slice in &mut self.sub_slices {
2892 for tile in sub_slice.tiles.values_mut() {
2893 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface {
2894 tile.surface = None;
2895 // Invalidate the entire tile to force a redraw.
2896 tile.invalidate(None, InvalidationReason::CompositorKindChanged);
2906 fn can_promote_to_surface(
2908 flags: PrimitiveFlags,
2909 prim_clip_chain: &ClipChainInstance,
2910 prim_spatial_node_index: SpatialNodeIndex,
2911 is_root_tile_cache: bool,
2912 sub_slice_index: usize,
2913 frame_context: &FrameVisibilityContext,
2914 ) -> SurfacePromotionResult {
2915 // Check if this primitive _wants_ to be promoted to a compositor surface.
2916 if !flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
2917 return SurfacePromotionResult::Failed;
2920 // For now, only support a small (arbitrary) number of compositor surfaces.
2921 if sub_slice_index == MAX_COMPOSITOR_SURFACES {
2922 return SurfacePromotionResult::Failed;
2925 // If a complex clip is being applied to this primitive, it can't be
2926 // promoted directly to a compositor surface (we might be able to
2927 // do this in limited cases in future, some native compositors do
2928 // support rounded rect clips, for example)
2929 if prim_clip_chain.needs_mask {
2930 return SurfacePromotionResult::Failed;
2933 // If not on the root picture cache, it has some kind of
2934 // complex effect (such as a filter, mix-blend-mode or 3d transform).
2935 if !is_root_tile_cache {
2936 return SurfacePromotionResult::Failed;
2939 let mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
2940 ROOT_SPATIAL_NODE_INDEX,
2941 prim_spatial_node_index,
2942 frame_context.global_screen_world_rect,
2943 &frame_context.spatial_tree);
2944 let transform = mapper.get_transform();
2945 if !transform.is_2d_scale_translation() {
2946 return SurfacePromotionResult::Failed;
2948 if transform.m11 < 0.0 {
2949 return SurfacePromotionResult::Failed;
2952 if self.slice_flags.contains(SliceFlags::IS_BLEND_CONTAINER) {
2953 return SurfacePromotionResult::Failed;
2956 SurfacePromotionResult::Success {
2957 flip_y: transform.m22 < 0.0,
2961 fn setup_compositor_surfaces_yuv(
2963 sub_slice_index: usize,
2964 prim_info: &mut PrimitiveDependencyInfo,
2965 flags: PrimitiveFlags,
2966 local_prim_rect: LayoutRect,
2967 prim_spatial_node_index: SpatialNodeIndex,
2968 pic_clip_rect: PictureRect,
2969 frame_context: &FrameVisibilityContext,
2970 image_dependencies: &[ImageDependency;3],
2971 api_keys: &[ImageKey; 3],
2972 resource_cache: &mut ResourceCache,
2973 composite_state: &mut CompositeState,
2974 gpu_cache: &mut GpuCache,
2975 image_rendering: ImageRendering,
2976 color_depth: ColorDepth,
2977 color_space: YuvColorSpace,
2980 for &key in api_keys {
2981 // TODO: See comment in setup_compositor_surfaces_rgb.
2982 resource_cache.request_image(ImageRequest {
2984 rendering: image_rendering,
2991 self.setup_compositor_surfaces_impl(
2996 prim_spatial_node_index,
2999 ExternalSurfaceDependency::Yuv {
3000 image_dependencies: *image_dependencies,
3003 rescale: color_depth.rescaling_factor(),
3013 fn setup_compositor_surfaces_rgb(
3015 sub_slice_index: usize,
3016 prim_info: &mut PrimitiveDependencyInfo,
3017 flags: PrimitiveFlags,
3018 local_prim_rect: LayoutRect,
3019 prim_spatial_node_index: SpatialNodeIndex,
3020 pic_clip_rect: PictureRect,
3021 frame_context: &FrameVisibilityContext,
3022 image_dependency: ImageDependency,
3024 resource_cache: &mut ResourceCache,
3025 composite_state: &mut CompositeState,
3026 gpu_cache: &mut GpuCache,
3027 image_rendering: ImageRendering,
3030 let mut api_keys = [ImageKey::DUMMY; 3];
3031 api_keys[0] = api_key;
3033 // TODO: The picture compositing code requires images promoted
3034 // into their own picture cache slices to be requested every
3035 // frame even if they are not visible. However the image updates
3036 // are only reached on the prepare pass for visible primitives.
3037 // So we make sure to trigger an image request when promoting
3039 resource_cache.request_image(ImageRequest {
3041 rendering: image_rendering,
3047 let is_opaque = resource_cache.get_image_properties(api_key)
3048 .map_or(false, |properties| properties.descriptor.is_opaque());
3050 self.setup_compositor_surfaces_impl(
3055 prim_spatial_node_index,
3058 ExternalSurfaceDependency::Rgb {
3070 // returns false if composition is not available for this surface,
3071 // and the non-compositor path should be used to draw it instead.
3072 fn setup_compositor_surfaces_impl(
3074 sub_slice_index: usize,
3075 prim_info: &mut PrimitiveDependencyInfo,
3076 flags: PrimitiveFlags,
3077 local_prim_rect: LayoutRect,
3078 prim_spatial_node_index: SpatialNodeIndex,
3079 pic_clip_rect: PictureRect,
3080 frame_context: &FrameVisibilityContext,
3081 dependency: ExternalSurfaceDependency,
3082 api_keys: &[ImageKey; 3],
3083 resource_cache: &mut ResourceCache,
3084 composite_state: &mut CompositeState,
3085 image_rendering: ImageRendering,
3088 let map_local_to_surface = SpaceMapper::new_with_target(
3089 self.spatial_node_index,
3090 prim_spatial_node_index,
3092 frame_context.spatial_tree,
3095 // Map the primitive local rect into picture space.
3096 let prim_rect = match map_local_to_surface.map(&local_prim_rect) {
3098 None => return true,
3101 // If the rect is invalid, no need to create dependencies.
3102 if prim_rect.size.is_empty() {
3106 let pic_to_world_mapper = SpaceMapper::new_with_target(
3107 ROOT_SPATIAL_NODE_INDEX,
3108 self.spatial_node_index,
3109 frame_context.global_screen_world_rect,
3110 frame_context.spatial_tree,
3113 let world_clip_rect = pic_to_world_mapper
3114 .map(&prim_info.prim_clip_box.to_rect())
3115 .expect("bug: unable to map clip to world space");
3117 let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
3122 let world_rect = pic_to_world_mapper
3124 .expect("bug: unable to map the primitive to world space");
3125 let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
3127 // TODO(gw): Is there any case where if the primitive ends up on a fractional
3128 // boundary we want to _skip_ promoting to a compositor surface and
3129 // draw it as part of the content?
3130 let (surface_rect, transform) = match composite_state.compositor_kind {
3131 CompositorKind::Draw { .. } => {
3132 (device_rect, Transform3D::identity())
3134 CompositorKind::Native { .. } => {
3135 // If we have a Native Compositor, then we can support doing the transformation
3136 // as part of compositing. Use the local prim rect for the external surface, and
3137 // compute the full local to device transform to provide to the compositor.
3138 let surface_to_world_mapper : SpaceMapper<PicturePixel, WorldPixel> = SpaceMapper::new_with_target(
3139 ROOT_SPATIAL_NODE_INDEX,
3140 prim_spatial_node_index,
3141 frame_context.global_screen_world_rect,
3142 frame_context.spatial_tree,
3144 let prim_origin = Vector3D::new(local_prim_rect.origin.x, local_prim_rect.origin.y, 0.0);
3145 let world_to_device_scale = Transform3D::from_scale(frame_context.global_device_pixel_scale);
3146 let transform = surface_to_world_mapper.get_transform().pre_translate(prim_origin).then(&world_to_device_scale);
3148 (local_prim_rect.cast_unit(), transform)
3152 let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
3154 if surface_rect.size.width >= MAX_COMPOSITOR_SURFACES_SIZE ||
3155 surface_rect.size.height >= MAX_COMPOSITOR_SURFACES_SIZE {
3159 // If this primitive is an external image, and supports being used
3160 // directly by a native compositor, then lookup the external image id
3161 // so we can pass that through.
3162 let external_image_id = if flags.contains(PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE) {
3163 resource_cache.get_image_properties(api_keys[0])
3164 .and_then(|properties| properties.external_image)
3165 .and_then(|image| Some(image.id))
3170 // When using native compositing, we need to find an existing native surface
3171 // handle to use, or allocate a new one. For existing native surfaces, we can
3172 // also determine whether this needs to be updated, depending on whether the
3173 // image generation(s) of the planes have changed since last composite.
3174 let (native_surface_id, update_params) = match composite_state.compositor_kind {
3175 CompositorKind::Draw { .. } => {
3178 CompositorKind::Native { .. } => {
3179 let native_surface_size = surface_rect.size.round().to_i32();
3181 let key = ExternalNativeSurfaceKey {
3182 image_keys: *api_keys,
3183 size: native_surface_size,
3184 is_external_surface: external_image_id.is_some(),
3187 let native_surface = self.external_native_surface_cache
3189 .or_insert_with(|| {
3190 // No existing surface, so allocate a new compositor surface.
3191 let native_surface_id = match external_image_id {
3192 Some(_external_image) => {
3193 // If we have a suitable external image, then create an external
3194 // surface to attach to.
3195 resource_cache.create_compositor_external_surface(is_opaque)
3198 // Otherwise create a normal compositor surface and a single
3199 // compositor tile that covers the entire surface.
3200 let native_surface_id =
3201 resource_cache.create_compositor_surface(
3202 DeviceIntPoint::zero(),
3203 native_surface_size,
3207 let tile_id = NativeTileId {
3208 surface_id: native_surface_id,
3212 resource_cache.create_compositor_tile(tile_id);
3218 ExternalNativeSurface {
3219 used_this_frame: true,
3221 image_dependencies: [ImageDependency::INVALID; 3],
3225 // Mark that the surface is referenced this frame so that the
3226 // backing native surface handle isn't freed.
3227 native_surface.used_this_frame = true;
3229 let update_params = match external_image_id {
3230 Some(external_image) => {
3231 // If this is an external image surface, then there's no update
3232 // to be done. Just attach the current external image to the surface
3234 resource_cache.attach_compositor_external_image(
3235 native_surface.native_surface_id,
3241 // If the image dependencies match, there is no need to update
3242 // the backing native surface.
3244 ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
3245 if image_dependencies == native_surface.image_dependencies {
3248 Some(native_surface_size)
3251 ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
3252 if image_dependency == native_surface.image_dependencies[0] {
3255 Some(native_surface_size)
3262 (Some(native_surface.native_surface_id), update_params)
3266 // For compositor surfaces, if we didn't find an earlier sub-slice to add to,
3267 // we know we can append to the current slice.
3268 assert!(sub_slice_index < self.sub_slices.len() - 1);
3269 let sub_slice = &mut self.sub_slices[sub_slice_index];
3271 // Each compositor surface allocates a unique z-id
3272 sub_slice.compositor_surfaces.push(CompositorSurface {
3273 prohibited_rect: pic_clip_rect,
3275 descriptor: ExternalSurfaceDescriptor {
3276 local_rect: prim_info.prim_clip_box.to_rect(),
3277 local_clip_rect: prim_info.prim_clip_box.to_rect(),
3283 transform: transform.cast_unit(),
3284 z_id: ZBufferId::invalid(),
3293 /// Update the dependencies for each tile for a given primitive instance.
3294 pub fn update_prim_dependencies(
3296 prim_instance: &mut PrimitiveInstance,
3297 prim_spatial_node_index: SpatialNodeIndex,
3298 local_prim_rect: LayoutRect,
3299 frame_context: &FrameVisibilityContext,
3300 data_stores: &DataStores,
3301 clip_store: &ClipStore,
3302 pictures: &[PicturePrimitive],
3303 resource_cache: &mut ResourceCache,
3304 color_bindings: &ColorBindingStorage,
3305 surface_stack: &[SurfaceIndex],
3306 composite_state: &mut CompositeState,
3307 gpu_cache: &mut GpuCache,
3308 is_root_tile_cache: bool,
3310 // This primitive exists on the last element on the current surface stack.
3311 profile_scope!("update_prim_dependencies");
3312 let prim_surface_index = *surface_stack.last().unwrap();
3313 let prim_clip_chain = &prim_instance.vis.clip_chain;
3315 // If the primitive is directly drawn onto this picture cache surface, then
3316 // the pic_clip_rect is in the same space. If not, we need to map it from
3317 // the surface space into the picture cache space.
3318 let on_picture_surface = prim_surface_index == self.surface_index;
3319 let pic_clip_rect = if on_picture_surface {
3320 prim_clip_chain.pic_clip_rect
3322 // We want to get the rect in the tile cache surface space that this primitive
3323 // occupies, in order to enable correct invalidation regions. Each surface
3324 // that exists in the chain between this primitive and the tile cache surface
3325 // may have an arbitrary inflation factor (for example, in the case of a series
3326 // of nested blur elements). To account for this, step through the current
3327 // surface stack, mapping the primitive rect into each surface space, including
3328 // the inflation factor from each intermediate surface.
3329 let mut current_pic_clip_rect = prim_clip_chain.pic_clip_rect;
3330 let mut current_spatial_node_index = frame_context
3331 .surfaces[prim_surface_index.0]
3332 .surface_spatial_node_index;
3334 for surface_index in surface_stack.iter().rev() {
3335 let surface = &frame_context.surfaces[surface_index.0];
3337 let map_local_to_surface = SpaceMapper::new_with_target(
3338 surface.surface_spatial_node_index,
3339 current_spatial_node_index,
3341 frame_context.spatial_tree,
3344 // Map the rect into the parent surface, and inflate if this surface requires
3345 // it. If the rect can't be mapping (e.g. due to an invalid transform) then
3346 // just bail out from the dependencies and cull this primitive.
3347 current_pic_clip_rect = match map_local_to_surface.map(¤t_pic_clip_rect) {
3349 rect.inflate(surface.inflation_factor, surface.inflation_factor)
3356 current_spatial_node_index = surface.surface_spatial_node_index;
3359 current_pic_clip_rect
3362 // Get the tile coordinates in the picture space.
3363 let (p0, p1) = self.get_tile_coords_for_rect(&pic_clip_rect);
3365 // If the primitive is outside the tiling rects, it's known to not
3367 if p0.x == p1.x || p0.y == p1.y {
3371 // Build the list of resources that this primitive has dependencies on.
3372 let mut prim_info = PrimitiveDependencyInfo::new(
3373 prim_instance.uid(),
3374 pic_clip_rect.to_box2d(),
3377 let mut sub_slice_index = self.sub_slices.len() - 1;
3379 // Only need to evaluate sub-slice regions if we have compositor surfaces present
3380 if sub_slice_index > 0 {
3381 // Find the first sub-slice we can add this primitive to (we want to add
3382 // prims to the primary surface if possible, so they get subpixel AA).
3383 for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() {
3384 let mut intersects_prohibited_region = false;
3386 for surface in &mut sub_slice.compositor_surfaces {
3387 if pic_clip_rect.intersects(&surface.prohibited_rect) {
3388 surface.prohibited_rect = surface.prohibited_rect.union(&pic_clip_rect);
3390 intersects_prohibited_region = true;
3394 if !intersects_prohibited_region {
3395 sub_slice_index = i;
3401 // Include the prim spatial node, if differs relative to cache root.
3402 if prim_spatial_node_index != self.spatial_node_index {
3403 prim_info.spatial_nodes.push(prim_spatial_node_index);
3406 // If there was a clip chain, add any clip dependencies to the list for this tile.
3407 let clip_instances = &clip_store
3408 .clip_node_instances[prim_clip_chain.clips_range.to_range()];
3409 for clip_instance in clip_instances {
3410 prim_info.clips.push(clip_instance.handle.uid());
3412 // If the clip has the same spatial node, the relative transform
3413 // will always be the same, so there's no need to depend on it.
3414 if clip_instance.spatial_node_index != self.spatial_node_index
3415 && !prim_info.spatial_nodes.contains(&clip_instance.spatial_node_index) {
3416 prim_info.spatial_nodes.push(clip_instance.spatial_node_index);
3420 // Certain primitives may select themselves to be a backdrop candidate, which is
3421 // then applied below.
3422 let mut backdrop_candidate = None;
3424 // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
3425 // use it to calculate the local bounding rect for the tiles. If we include them
3426 // then we may calculate a bounding rect that is too large, since it won't include
3427 // the clip bounds of the picture. Excluding them from the bounding rect here
3428 // fixes any correctness issues (the clips themselves are considered when we
3429 // consider the bounds of the primitives that are *children* of the picture),
3430 // however it does potentially result in some un-necessary invalidations of a
3431 // tile (in cases where the picture local rect affects the tile, but the clip
3432 // rect eventually means it doesn't affect that tile).
3433 // TODO(gw): Get picture clips earlier (during the initial picture traversal
3434 // pass) so that we can calculate these correctly.
3435 match prim_instance.kind {
3436 PrimitiveInstanceKind::Picture { pic_index,.. } => {
3437 // Pictures can depend on animated opacity bindings.
3438 let pic = &pictures[pic_index.0];
3439 if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode {
3440 prim_info.opacity_bindings.push(binding.into());
3443 PrimitiveInstanceKind::Rectangle { data_handle, color_binding_index, .. } => {
3444 // Rectangles can only form a backdrop candidate if they are known opaque.
3445 // TODO(gw): We could resolve the opacity binding here, but the common
3446 // case for background rects is that they don't have animated opacity.
3447 let color = match data_stores.prim[data_handle].kind {
3448 PrimitiveTemplateKind::Rectangle { color, .. } => {
3449 frame_context.scene_properties.resolve_color(&color)
3451 _ => unreachable!(),
3454 backdrop_candidate = Some(BackdropInfo {
3455 opaque_rect: pic_clip_rect,
3456 kind: Some(BackdropKind::Color { color }),
3460 if color_binding_index != ColorBindingIndex::INVALID {
3461 prim_info.color_binding = Some(color_bindings[color_binding_index].into());
3464 PrimitiveInstanceKind::Image { data_handle, ref mut is_compositor_surface, .. } => {
3465 let image_key = &data_stores.image[data_handle];
3466 let image_data = &image_key.kind;
3468 let mut promote_to_surface = false;
3469 let mut promote_with_flip_y = false;
3470 match self.can_promote_to_surface(image_key.common.flags,
3472 prim_spatial_node_index,
3476 SurfacePromotionResult::Failed => {
3478 SurfacePromotionResult::Success{flip_y} => {
3479 promote_to_surface = true;
3480 promote_with_flip_y = flip_y;
3484 // Native OS compositors (DC and CA, at least) support premultiplied alpha
3485 // only. If we have an image that's not pre-multiplied alpha, we can't promote it.
3486 if image_data.alpha_type == AlphaType::Alpha {
3487 promote_to_surface = false;
3490 if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
3491 // For an image to be a possible opaque backdrop, it must:
3492 // - Have a valid, opaque image descriptor
3493 // - Not use tiling (since they can fail to draw)
3494 // - Not having any spacing / padding
3495 if image_properties.descriptor.is_opaque() &&
3496 image_properties.tiling.is_none() &&
3497 image_data.tile_spacing == LayoutSize::zero() {
3498 backdrop_candidate = Some(BackdropInfo {
3499 opaque_rect: pic_clip_rect,
3505 if promote_to_surface {
3506 promote_to_surface = self.setup_compositor_surfaces_rgb(
3509 image_key.common.flags,
3511 prim_spatial_node_index,
3515 key: image_data.key,
3516 generation: resource_cache.get_image_generation(image_data.key),
3522 image_data.image_rendering,
3523 promote_with_flip_y,
3527 *is_compositor_surface = promote_to_surface;
3529 if promote_to_surface {
3530 prim_instance.vis.state = VisibilityState::Culled;
3533 prim_info.images.push(ImageDependency {
3534 key: image_data.key,
3535 generation: resource_cache.get_image_generation(image_data.key),
3539 PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => {
3540 let prim_data = &data_stores.yuv_image[data_handle];
3541 let mut promote_to_surface = match self.can_promote_to_surface(
3542 prim_data.common.flags,
3544 prim_spatial_node_index,
3548 SurfacePromotionResult::Failed => false,
3549 SurfacePromotionResult::Success{flip_y} => !flip_y,
3552 // TODO(gw): When we support RGBA images for external surfaces, we also
3553 // need to check if opaque (YUV images are implicitly opaque).
3555 // If this primitive is being promoted to a surface, construct an external
3556 // surface descriptor for use later during batching and compositing. We only
3557 // add the image keys for this primitive as a dependency if this is _not_
3558 // a promoted surface, since we don't want the tiles to invalidate when the
3559 // video content changes, if it's a compositor surface!
3560 if promote_to_surface {
3561 // Build dependency for each YUV plane, with current image generation for
3562 // later detection of when the composited surface has changed.
3563 let mut image_dependencies = [ImageDependency::INVALID; 3];
3564 for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
3565 *dep = ImageDependency {
3567 generation: resource_cache.get_image_generation(key),
3571 promote_to_surface = self.setup_compositor_surfaces_yuv(
3574 prim_data.common.flags,
3576 prim_spatial_node_index,
3579 &image_dependencies,
3580 &prim_data.kind.yuv_key,
3584 prim_data.kind.image_rendering,
3585 prim_data.kind.color_depth,
3586 prim_data.kind.color_space,
3587 prim_data.kind.format,
3591 // Store on the YUV primitive instance whether this is a promoted surface.
3592 // This is used by the batching code to determine whether to draw the
3593 // image to the content tiles, or just a transparent z-write.
3594 *is_compositor_surface = promote_to_surface;
3596 if promote_to_surface {
3597 prim_instance.vis.state = VisibilityState::Culled;
3600 prim_info.images.extend(
3601 prim_data.kind.yuv_key.iter().map(|key| {
3604 generation: resource_cache.get_image_generation(*key),
3610 PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
3611 let border_data = &data_stores.image_border[data_handle].kind;
3612 prim_info.images.push(ImageDependency {
3613 key: border_data.request.key,
3614 generation: resource_cache.get_image_generation(border_data.request.key),
3617 PrimitiveInstanceKind::Clear { .. } => {
3618 backdrop_candidate = Some(BackdropInfo {
3619 opaque_rect: pic_clip_rect,
3620 kind: Some(BackdropKind::Clear),
3623 PrimitiveInstanceKind::LinearGradient { data_handle, .. }
3624 | PrimitiveInstanceKind::CachedLinearGradient { data_handle, .. } => {
3625 let gradient_data = &data_stores.linear_grad[data_handle];
3626 if gradient_data.stops_opacity.is_opaque
3627 && gradient_data.tile_spacing == LayoutSize::zero()
3629 backdrop_candidate = Some(BackdropInfo {
3630 opaque_rect: pic_clip_rect,
3635 PrimitiveInstanceKind::ConicGradient { data_handle, .. } => {
3636 let gradient_data = &data_stores.conic_grad[data_handle];
3637 if gradient_data.stops_opacity.is_opaque
3638 && gradient_data.tile_spacing == LayoutSize::zero()
3640 backdrop_candidate = Some(BackdropInfo {
3641 opaque_rect: pic_clip_rect,
3646 PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
3647 let gradient_data = &data_stores.radial_grad[data_handle];
3648 if gradient_data.stops_opacity.is_opaque
3649 && gradient_data.tile_spacing == LayoutSize::zero()
3651 backdrop_candidate = Some(BackdropInfo {
3652 opaque_rect: pic_clip_rect,
3657 PrimitiveInstanceKind::LineDecoration { .. } |
3658 PrimitiveInstanceKind::NormalBorder { .. } |
3659 PrimitiveInstanceKind::TextRun { .. } |
3660 PrimitiveInstanceKind::Backdrop { .. } => {
3661 // These don't contribute dependencies
3665 // If this primitive considers itself a backdrop candidate, apply further
3666 // checks to see if it matches all conditions to be a backdrop.
3667 let mut vis_flags = PrimitiveVisibilityFlags::empty();
3669 let sub_slice = &mut self.sub_slices[sub_slice_index];
3671 if let Some(backdrop_candidate) = backdrop_candidate {
3672 let is_suitable_backdrop = match backdrop_candidate.kind {
3673 Some(BackdropKind::Clear) => {
3674 // Clear prims are special - they always end up in their own slice,
3675 // and always set the backdrop. In future, we hope to completely
3676 // remove clear prims, since they don't integrate with the compositing
3680 Some(BackdropKind::Color { .. }) | None => {
3681 // Check a number of conditions to see if we can consider this
3682 // primitive as an opaque backdrop rect. Several of these are conservative
3683 // checks and could be relaxed in future. However, these checks
3684 // are quick and capture the common cases of background rects and images.
3685 // Specifically, we currently require:
3686 // - The primitive is on the main picture cache surface.
3687 // - Same coord system as picture cache (ensures rects are axis-aligned).
3688 // - No clip masks exist.
3689 let same_coord_system = {
3690 let prim_spatial_node = &frame_context.spatial_tree
3691 .spatial_nodes[prim_spatial_node_index.0 as usize];
3692 let surface_spatial_node = &frame_context.spatial_tree
3693 .spatial_nodes[self.spatial_node_index.0 as usize];
3695 prim_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id
3698 same_coord_system && on_picture_surface
3702 if sub_slice_index == 0 &&
3703 is_suitable_backdrop &&
3704 sub_slice.compositor_surfaces.is_empty() &&
3705 !prim_clip_chain.needs_mask {
3707 if backdrop_candidate.opaque_rect.contains_rect(&self.backdrop.opaque_rect) {
3708 self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
3711 if let Some(kind) = backdrop_candidate.kind {
3712 if backdrop_candidate.opaque_rect.contains_rect(&self.local_rect) {
3713 // If we have a color backdrop, mark the visibility flags
3714 // of the primitive so it is skipped during batching (and
3715 // also clears any previous primitives).
3716 if let BackdropKind::Color { .. } = kind {
3717 vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP;
3720 self.backdrop.kind = Some(kind);
3726 // Record any new spatial nodes in the used list.
3727 for spatial_node_index in &prim_info.spatial_nodes {
3728 self.spatial_node_comparer.register_used_transform(
3729 *spatial_node_index,
3731 frame_context.spatial_tree,
3735 // Truncate the lengths of dependency arrays to the max size we can handle.
3736 // Any arrays this size or longer will invalidate every frame.
3737 prim_info.clips.truncate(MAX_PRIM_SUB_DEPS);
3738 prim_info.opacity_bindings.truncate(MAX_PRIM_SUB_DEPS);
3739 prim_info.spatial_nodes.truncate(MAX_PRIM_SUB_DEPS);
3740 prim_info.images.truncate(MAX_PRIM_SUB_DEPS);
3742 // Normalize the tile coordinates before adding to tile dependencies.
3743 // For each affected tile, mark any of the primitive dependencies.
3744 for y in p0.y .. p1.y {
3745 for x in p0.x .. p1.x {
3746 // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile?
3747 let key = TileOffset::new(x, y);
3748 let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
3750 tile.add_prim_dependency(&prim_info);
3754 prim_instance.vis.state = VisibilityState::Coarse {
3755 filter: BatchFilter {
3756 rect_in_pic_space: pic_clip_rect,
3757 sub_slice_index: SubSliceIndex::new(sub_slice_index),
3763 /// Print debug information about this picture cache to a tree printer.
3765 // TODO(gw): This initial implementation is very basic - just printing
3766 // the picture cache state to stdout. In future, we can
3767 // make this dump each frame to a file, and produce a report
3768 // stating which frames had invalidations. This will allow
3769 // diff'ing the invalidation states in a visual tool.
3770 let mut pt = PrintTree::new("Picture Cache");
3772 pt.new_level(format!("Slice {:?}", self.slice));
3774 pt.add_item(format!("fract_offset: {:?}", self.fract_offset));
3775 pt.add_item(format!("background_color: {:?}", self.background_color));
3777 for (sub_slice_index, sub_slice) in self.sub_slices.iter().enumerate() {
3778 pt.new_level(format!("SubSlice {:?}", sub_slice_index));
3780 for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y {
3781 for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x {
3782 let key = TileOffset::new(x, y);
3783 let tile = &sub_slice.tiles[&key];
3784 tile.print(&mut pt);
3794 fn calculate_subpixel_mode(&self) -> SubpixelMode {
3795 let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
3797 // If the overall tile cache is known opaque, subpixel AA is allowed everywhere
3798 if has_opaque_bg_color {
3799 return SubpixelMode::Allow;
3802 // If we didn't find any valid opaque backdrop, no subpixel AA allowed
3803 if self.backdrop.opaque_rect.is_empty() {
3804 return SubpixelMode::Deny;
3807 // If the opaque backdrop rect covers the entire tile cache surface,
3808 // we can allow subpixel AA anywhere, skipping the per-text-run tests
3809 // later on during primitive preparation.
3810 if self.backdrop.opaque_rect.contains_rect(&self.local_rect) {
3811 return SubpixelMode::Allow;
3814 // If none of the simple cases above match, we need test where we can support subpixel AA.
3815 // TODO(gw): In future, it may make sense to have > 1 inclusion rect,
3816 // but this handles the common cases.
3817 // TODO(gw): If a text run gets animated such that it's moving in a way that is
3818 // sometimes intersecting with the video rect, this can result in subpixel
3819 // AA flicking on/off for that text run. It's probably very rare, but
3820 // something we should handle in future.
3821 SubpixelMode::Conditional {
3822 allowed_rect: self.backdrop.opaque_rect,
3826 /// Apply any updates after prim dependency updates. This applies
3827 /// any late tile invalidations, and sets up the dirty rect and
3828 /// set of tile blits.
3831 frame_context: &FrameVisibilityContext,
3832 frame_state: &mut FrameVisibilityState,
3834 self.dirty_region.reset(self.spatial_node_index);
3835 self.subpixel_mode = self.calculate_subpixel_mode();
3837 let map_pic_to_world = SpaceMapper::new_with_target(
3838 ROOT_SPATIAL_NODE_INDEX,
3839 self.spatial_node_index,
3840 frame_context.global_screen_world_rect,
3841 frame_context.spatial_tree,
3844 // Register the opaque region of this tile cache as an occluder, which
3845 // is used later in the frame to occlude other tiles.
3846 if !self.backdrop.opaque_rect.is_empty() {
3847 let backdrop_rect = self.backdrop.opaque_rect
3848 .intersection(&self.local_rect)
3850 r.intersection(&self.local_clip_rect)
3853 if let Some(backdrop_rect) = backdrop_rect {
3854 let world_backdrop_rect = map_pic_to_world
3855 .map(&backdrop_rect)
3856 .expect("bug: unable to map backdrop to world space");
3858 // Since we register the entire backdrop rect, use the opaque z-id for the
3859 // picture cache slice.
3860 frame_state.composite_state.register_occluder(
3862 world_backdrop_rect,
3867 // A simple GC of the native external surface cache, to remove and free any
3868 // surfaces that were not referenced during the update_prim_dependencies pass.
3869 self.external_native_surface_cache.retain(|_, surface| {
3870 if !surface.used_this_frame {
3871 // If we removed an external surface, we need to mark the dirty rects as
3872 // invalid so a full composite occurs on the next frame.
3873 frame_state.composite_state.dirty_rects_are_valid = false;
3875 frame_state.resource_cache.destroy_compositor_surface(surface.native_surface_id);
3878 surface.used_this_frame
3881 // Detect if the picture cache was scrolled or scaled. In this case,
3882 // the device space dirty rects aren't applicable (until we properly
3883 // integrate with OS compositors that can handle scrolling slices).
3884 let root_transform = frame_context
3886 .get_relative_transform(
3887 self.spatial_node_index,
3888 ROOT_SPATIAL_NODE_INDEX,
3890 let root_transform = match root_transform {
3891 CoordinateSpaceMapping::Local => ScaleOffset::identity(),
3892 CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset,
3893 CoordinateSpaceMapping::Transform(..) => panic!("bug: picture caches don't support complex transforms"),
3895 const EPSILON: f32 = 0.001;
3896 let root_translation_changed =
3897 !root_transform.offset.x.approx_eq_eps(&self.root_transform.offset.x, &EPSILON) ||
3898 !root_transform.offset.y.approx_eq_eps(&self.root_transform.offset.y, &EPSILON);
3899 let root_scale_changed =
3900 !root_transform.scale.x.approx_eq_eps(&self.root_transform.scale.x, &EPSILON) ||
3901 !root_transform.scale.y.approx_eq_eps(&self.root_transform.scale.y, &EPSILON);
3903 if root_translation_changed || root_scale_changed || frame_context.config.force_invalidation {
3904 self.root_transform = root_transform;
3905 frame_state.composite_state.dirty_rects_are_valid = false;
3908 let pic_to_world_mapper = SpaceMapper::new_with_target(
3909 ROOT_SPATIAL_NODE_INDEX,
3910 self.spatial_node_index,
3911 frame_context.global_screen_world_rect,
3912 frame_context.spatial_tree,
3915 let mut ctx = TilePostUpdateContext {
3916 pic_to_world_mapper,
3917 global_device_pixel_scale: frame_context.global_device_pixel_scale,
3918 local_clip_rect: self.local_clip_rect,
3919 backdrop: Some(self.backdrop),
3920 opacity_bindings: &self.opacity_bindings,
3921 color_bindings: &self.color_bindings,
3922 current_tile_size: self.current_tile_size,
3923 local_rect: self.local_rect,
3924 z_id: self.z_id_backdrop,
3925 invalidate_all: root_scale_changed || frame_context.config.force_invalidation,
3928 let mut state = TilePostUpdateState {
3929 resource_cache: frame_state.resource_cache,
3930 composite_state: frame_state.composite_state,
3931 compare_cache: &mut self.compare_cache,
3932 spatial_node_comparer: &mut self.spatial_node_comparer,
3935 // Step through each tile and invalidate if the dependencies have changed. Determine
3936 // the current opacity setting and whether it's changed.
3937 for sub_slice in &mut self.sub_slices {
3938 for tile in sub_slice.tiles.values_mut() {
3939 tile.post_update(&ctx, &mut state, frame_context);
3942 for compositor_surface in &mut sub_slice.compositor_surfaces {
3943 compositor_surface.descriptor.z_id = state.composite_state.z_generator.next();
3946 // After the first sub-slice, the backdrop is no longer relevant
3947 ctx.backdrop = None;
3948 ctx.z_id = state.composite_state.z_generator.next();
3951 // Register any opaque external compositor surfaces as potential occluders. This
3952 // is especially useful when viewing video in full-screen mode, as it is
3953 // able to occlude every background tile (avoiding allocation, rasterizion
3954 // and compositing).
3956 for sub_slice in &self.sub_slices {
3957 for compositor_surface in &sub_slice.compositor_surfaces {
3958 if compositor_surface.is_opaque {
3959 let local_surface_rect = compositor_surface
3962 .intersection(&compositor_surface.descriptor.local_clip_rect)
3964 r.intersection(&self.local_clip_rect)
3967 if let Some(local_surface_rect) = local_surface_rect {
3968 let world_surface_rect = map_pic_to_world
3969 .map(&local_surface_rect)
3970 .expect("bug: unable to map external surface to world space");
3972 frame_state.composite_state.register_occluder(
3973 compositor_surface.descriptor.z_id,
3983 pub struct PictureScratchBuffer {
3984 surface_stack: Vec<SurfaceIndex>,
3985 clip_chain_ids: Vec<ClipChainId>,
3988 impl Default for PictureScratchBuffer {
3989 fn default() -> Self {
3990 PictureScratchBuffer {
3991 surface_stack: Vec::new(),
3992 clip_chain_ids: Vec::new(),
3997 impl PictureScratchBuffer {
3998 pub fn begin_frame(&mut self) {
3999 self.surface_stack.clear();
4000 self.clip_chain_ids.clear();
4003 pub fn recycle(&mut self, recycler: &mut Recycler) {
4004 recycler.recycle_vec(&mut self.surface_stack);
4008 /// Maintains a stack of picture and surface information, that
4009 /// is used during the initial picture traversal.
4010 pub struct PictureUpdateState<'a> {
4011 surfaces: &'a mut Vec<SurfaceInfo>,
4012 surface_stack: Vec<SurfaceIndex>,
4015 impl<'a> PictureUpdateState<'a> {
4017 buffers: &mut PictureScratchBuffer,
4018 surfaces: &'a mut Vec<SurfaceInfo>,
4019 pic_index: PictureIndex,
4020 picture_primitives: &mut [PicturePrimitive],
4021 frame_context: &FrameBuildingContext,
4022 gpu_cache: &mut GpuCache,
4023 clip_store: &ClipStore,
4024 data_stores: &mut DataStores,
4026 profile_scope!("UpdatePictures");
4027 profile_marker!("UpdatePictures");
4029 let mut state = PictureUpdateState {
4031 surface_stack: buffers.surface_stack.take().cleared(),
4034 state.surface_stack.push(SurfaceIndex(0));
4045 buffers.surface_stack = state.surface_stack.take();
4048 /// Return the current surface
4049 fn current_surface(&self) -> &SurfaceInfo {
4050 &self.surfaces[self.surface_stack.last().unwrap().0]
4053 /// Return the current surface (mutable)
4054 fn current_surface_mut(&mut self) -> &mut SurfaceInfo {
4055 &mut self.surfaces[self.surface_stack.last().unwrap().0]
4058 /// Push a new surface onto the update stack.
4061 surface: SurfaceInfo,
4063 let surface_index = SurfaceIndex(self.surfaces.len());
4064 self.surfaces.push(surface);
4065 self.surface_stack.push(surface_index);
4069 /// Pop a surface on the way up the picture traversal
4070 fn pop_surface(&mut self) -> SurfaceIndex{
4071 self.surface_stack.pop().unwrap()
4074 /// Update a picture, determining surface configuration,
4075 /// rasterization roots, and (in future) whether there
4076 /// are cached surfaces that can be used by this picture.
4079 pic_index: PictureIndex,
4080 picture_primitives: &mut [PicturePrimitive],
4081 frame_context: &FrameBuildingContext,
4082 gpu_cache: &mut GpuCache,
4083 clip_store: &ClipStore,
4084 data_stores: &mut DataStores,
4086 if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
4090 for child_pic_index in &prim_list.child_pictures {
4101 picture_primitives[pic_index.0].post_update(
4111 #[derive(Debug, Copy, Clone, PartialEq)]
4112 #[cfg_attr(feature = "capture", derive(Serialize))]
4113 pub struct SurfaceIndex(pub usize);
4115 pub const ROOT_SURFACE_INDEX: SurfaceIndex = SurfaceIndex(0);
4117 /// Describes the render task configuration for a picture surface.
4119 pub enum SurfaceRenderTasks {
4120 /// The common type of surface is a single render task
4121 Simple(RenderTaskId),
4122 /// Some surfaces draw their content, and then have further tasks applied
4123 /// to that input (such as blur passes for shadows). These tasks have a root
4124 /// (the output of the surface), and a port (for attaching child task dependencies
4125 /// to the content).
4126 Chained { root_task_id: RenderTaskId, port_task_id: RenderTaskId },
4127 /// Picture caches are a single surface consisting of multiple render
4128 /// tasks, one per tile with dirty content.
4129 Tiled(Vec<RenderTaskId>),
4132 /// Information about an offscreen surface. For now,
4133 /// it contains information about the size and coordinate
4134 /// system of the surface. In the future, it will contain
4135 /// information about the contents of the surface, which
4136 /// will allow surfaces to be cached / retained between
4137 /// frames and display lists.
4139 pub struct SurfaceInfo {
4140 /// A local rect defining the size of this surface, in the
4141 /// coordinate system of the surface itself.
4142 pub rect: PictureRect,
4143 /// Part of the surface that we know to be opaque.
4144 pub opaque_rect: PictureRect,
4145 /// Helper structs for mapping local rects in different
4146 /// coordinate systems into the surface coordinates.
4147 pub map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
4148 /// Defines the positioning node for the surface itself,
4149 /// and the rasterization root for this surface.
4150 pub raster_spatial_node_index: SpatialNodeIndex,
4151 pub surface_spatial_node_index: SpatialNodeIndex,
4152 /// This is set when the render task is created.
4153 pub render_tasks: Option<SurfaceRenderTasks>,
4154 /// How much the local surface rect should be inflated (for blur radii).
4155 pub inflation_factor: f32,
4156 /// The device pixel ratio specific to this surface.
4157 pub device_pixel_scale: DevicePixelScale,
4158 /// The scale factors of the surface to raster transform.
4159 pub scale_factors: (f32, f32),
4160 /// The allocated device rect for this surface
4161 pub device_rect: Option<DeviceRect>,
4166 surface_spatial_node_index: SpatialNodeIndex,
4167 raster_spatial_node_index: SpatialNodeIndex,
4168 inflation_factor: f32,
4169 world_rect: WorldRect,
4170 spatial_tree: &SpatialTree,
4171 device_pixel_scale: DevicePixelScale,
4172 scale_factors: (f32, f32),
4174 let map_surface_to_world = SpaceMapper::new_with_target(
4175 ROOT_SPATIAL_NODE_INDEX,
4176 surface_spatial_node_index,
4181 let pic_bounds = map_surface_to_world
4182 .unmap(&map_surface_to_world.bounds)
4183 .unwrap_or_else(PictureRect::max_rect);
4185 let map_local_to_surface = SpaceMapper::new(
4186 surface_spatial_node_index,
4191 rect: PictureRect::zero(),
4192 opaque_rect: PictureRect::zero(),
4193 map_local_to_surface,
4195 raster_spatial_node_index,
4196 surface_spatial_node_index,
4204 pub fn get_device_rect(&self) -> DeviceRect {
4205 self.device_rect.expect("bug: queried before surface was initialized")
4210 #[cfg_attr(feature = "capture", derive(Serialize))]
4211 pub struct RasterConfig {
4212 /// How this picture should be composited into
4213 /// the parent surface.
4214 pub composite_mode: PictureCompositeMode,
4215 /// Index to the surface descriptor for this
4217 pub surface_index: SurfaceIndex,
4218 /// Whether this picture establishes a rasterization root.
4219 pub establishes_raster_root: bool,
4220 /// Scaling factor applied to fit within MAX_SURFACE_SIZE when
4221 /// establishing a raster root.
4222 /// Most code doesn't need to know about it, since it is folded
4223 /// into device_pixel_scale when the rendertask is set up.
4224 /// However e.g. text rasterization uses it to ensure consistent
4225 /// on-screen font size.
4226 pub root_scaling_factor: f32,
4227 /// The world rect of this picture clipped to the current culling
4228 /// rect. This is used for determining the size of the render
4229 /// target rect for this surface, and calculating raster scale
4231 pub clipped_bounding_rect: WorldRect,
4235 /// A set of flags describing why a picture may need a backing surface.
4236 #[cfg_attr(feature = "capture", derive(Serialize))]
4237 pub struct BlitReason: u32 {
4238 /// Mix-blend-mode on a child that requires isolation.
4240 /// Clip node that _might_ require a surface.
4242 /// Preserve-3D requires a surface for plane-splitting.
4243 const PRESERVE3D = 4;
4244 /// A backdrop that is reused which requires a surface.
4249 /// Specifies how this Picture should be composited
4250 /// onto the target it belongs to.
4252 #[derive(Debug, Clone)]
4253 #[cfg_attr(feature = "capture", derive(Serialize))]
4254 pub enum PictureCompositeMode {
4255 /// Apply CSS mix-blend-mode effect.
4256 MixBlend(MixBlendMode),
4257 /// Apply a CSS filter (except component transfer).
4259 /// Apply a component transfer filter.
4260 ComponentTransferFilter(FilterDataHandle),
4261 /// Draw to intermediate surface, copy straight across. This
4262 /// is used for CSS isolation, and plane splitting.
4264 /// Used to cache a picture as a series of tiles.
4268 /// Apply an SVG filter
4269 SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
4272 impl PictureCompositeMode {
4273 pub fn inflate_picture_rect(&self, picture_rect: PictureRect, scale_factors: (f32, f32)) -> PictureRect {
4274 let mut result_rect = picture_rect;
4276 PictureCompositeMode::Filter(filter) => match filter {
4277 Filter::Blur(width, height) => {
4278 let width_factor = clamp_blur_radius(*width, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
4279 let height_factor = clamp_blur_radius(*height, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
4280 result_rect = picture_rect.inflate(width_factor, height_factor);
4282 Filter::DropShadows(shadows) => {
4283 let mut max_inflation: f32 = 0.0;
4284 for shadow in shadows {
4285 max_inflation = max_inflation.max(shadow.blur_radius);
4287 max_inflation = clamp_blur_radius(max_inflation, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
4288 result_rect = picture_rect.inflate(max_inflation, max_inflation);
4292 PictureCompositeMode::SvgFilter(primitives, _) => {
4293 let mut output_rects = Vec::with_capacity(primitives.len());
4294 for (cur_index, primitive) in primitives.iter().enumerate() {
4295 let output_rect = match primitive.kind {
4296 FilterPrimitiveKind::Blur(ref primitive) => {
4297 let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
4298 let width_factor = primitive.width.round() * BLUR_SAMPLE_SCALE;
4299 let height_factor = primitive.height.round() * BLUR_SAMPLE_SCALE;
4300 input.inflate(width_factor, height_factor)
4302 FilterPrimitiveKind::DropShadow(ref primitive) => {
4303 let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
4304 let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
4305 let shadow_rect = input.inflate(inflation_factor, inflation_factor);
4306 input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0)))
4308 FilterPrimitiveKind::Blend(ref primitive) => {
4309 primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
4310 .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
4312 FilterPrimitiveKind::Composite(ref primitive) => {
4313 primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
4314 .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
4316 FilterPrimitiveKind::Identity(ref primitive) =>
4317 primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
4318 FilterPrimitiveKind::Opacity(ref primitive) =>
4319 primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
4320 FilterPrimitiveKind::ColorMatrix(ref primitive) =>
4321 primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
4322 FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
4323 primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
4324 FilterPrimitiveKind::Offset(ref primitive) => {
4325 let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
4326 input_rect.translate(primitive.offset * Scale::new(1.0))
4329 FilterPrimitiveKind::Flood(..) => picture_rect,
4331 output_rects.push(output_rect);
4332 result_rect = result_rect.union(&output_rect);
4341 /// Enum value describing the place of a picture in a 3D context.
4342 #[derive(Clone, Debug)]
4343 #[cfg_attr(feature = "capture", derive(Serialize))]
4344 pub enum Picture3DContext<C> {
4345 /// The picture is not a part of 3D context sub-hierarchy.
4347 /// The picture is a part of 3D context.
4349 /// Additional data per child for the case of this a root of 3D hierarchy.
4350 root_data: Option<Vec<C>>,
4351 /// The spatial node index of an "ancestor" element, i.e. one
4352 /// that establishes the transformed element's containing block.
4354 /// See CSS spec draft for more details:
4355 /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
4356 ancestor_index: SpatialNodeIndex,
4360 /// Information about a preserve-3D hierarchy child that has been plane-split
4361 /// and ordered according to the view direction.
4362 #[derive(Clone, Debug)]
4363 #[cfg_attr(feature = "capture", derive(Serialize))]
4364 pub struct OrderedPictureChild {
4365 pub anchor: PlaneSplitAnchor,
4366 pub spatial_node_index: SpatialNodeIndex,
4367 pub gpu_address: GpuCacheAddress,
4371 /// A set of flags describing why a picture may need a backing surface.
4372 #[cfg_attr(feature = "capture", derive(Serialize))]
4373 pub struct ClusterFlags: u32 {
4374 /// Whether this cluster is visible when the position node is a backface.
4375 const IS_BACKFACE_VISIBLE = 1;
4376 /// This flag is set during the first pass picture traversal, depending on whether
4377 /// the cluster is visible or not. It's read during the second pass when primitives
4378 /// consult their owning clusters to see if the primitive itself is visible.
4379 const IS_VISIBLE = 2;
4380 /// Is a backdrop-filter cluster that requires special handling during post_update.
4381 const IS_BACKDROP_FILTER = 4;
4385 /// Descriptor for a cluster of primitives. For now, this is quite basic but will be
4386 /// extended to handle more spatial clustering of primitives.
4387 #[cfg_attr(feature = "capture", derive(Serialize))]
4388 pub struct PrimitiveCluster {
4389 /// The positioning node for this cluster.
4390 pub spatial_node_index: SpatialNodeIndex,
4391 /// The bounding rect of the cluster, in the local space of the spatial node.
4392 /// This is used to quickly determine the overall bounding rect for a picture
4393 /// during the first picture traversal, which is needed for local scale
4394 /// determination, and render task size calculations.
4395 bounding_rect: LayoutRect,
4396 /// a part of the cluster that we know to be opaque if any. Does not always
4397 /// describe the entire opaque region, but all content within that rect must
4399 pub opaque_rect: LayoutRect,
4400 /// The range of primitive instance indices associated with this cluster.
4401 pub prim_range: Range<usize>,
4402 /// Various flags / state for this cluster.
4403 pub flags: ClusterFlags,
4406 impl PrimitiveCluster {
4407 /// Construct a new primitive cluster for a given positioning node.
4409 spatial_node_index: SpatialNodeIndex,
4410 flags: ClusterFlags,
4411 first_instance_index: usize,
4414 bounding_rect: LayoutRect::zero(),
4415 opaque_rect: LayoutRect::zero(),
4418 prim_range: first_instance_index..first_instance_index
4422 /// Return true if this cluster is compatible with the given params
4423 pub fn is_compatible(
4425 spatial_node_index: SpatialNodeIndex,
4426 flags: ClusterFlags,
4428 self.flags == flags && self.spatial_node_index == spatial_node_index
4431 pub fn prim_range(&self) -> Range<usize> {
4432 self.prim_range.clone()
4435 /// Add a primitive instance to this cluster, at the start or end
4438 culling_rect: &LayoutRect,
4439 instance_index: usize,
4441 debug_assert_eq!(instance_index, self.prim_range.end);
4442 self.bounding_rect = self.bounding_rect.union(culling_rect);
4443 self.prim_range.end += 1;
4447 /// A list of primitive instances that are added to a picture
4448 /// This ensures we can keep a list of primitives that
4449 /// are pictures, for a fast initial traversal of the picture
4450 /// tree without walking the instance list.
4451 #[cfg_attr(feature = "capture", derive(Serialize))]
4452 pub struct PrimitiveList {
4453 /// List of primitives grouped into clusters.
4454 pub clusters: Vec<PrimitiveCluster>,
4455 pub prim_instances: Vec<PrimitiveInstance>,
4456 pub child_pictures: Vec<PictureIndex>,
4457 /// The number of preferred compositor surfaces that were found when
4458 /// adding prims to this list.
4459 pub compositor_surface_count: usize,
4462 impl PrimitiveList {
4463 /// Construct an empty primitive list. This is
4464 /// just used during the take_context / restore_context
4465 /// borrow check dance, which will be removed as the
4466 /// picture traversal pass is completed.
4467 pub fn empty() -> Self {
4469 clusters: Vec::new(),
4470 prim_instances: Vec::new(),
4471 child_pictures: Vec::new(),
4472 compositor_surface_count: 0,
4476 /// Add a primitive instance to the end of the list
4479 prim_instance: PrimitiveInstance,
4480 prim_rect: LayoutRect,
4481 spatial_node_index: SpatialNodeIndex,
4482 prim_flags: PrimitiveFlags,
4484 let mut flags = ClusterFlags::empty();
4486 // Pictures are always put into a new cluster, to make it faster to
4487 // iterate all pictures in a given primitive list.
4488 match prim_instance.kind {
4489 PrimitiveInstanceKind::Picture { pic_index, .. } => {
4490 self.child_pictures.push(pic_index);
4492 PrimitiveInstanceKind::Backdrop { .. } => {
4493 flags.insert(ClusterFlags::IS_BACKDROP_FILTER);
4498 if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
4499 flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE);
4502 if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
4503 self.compositor_surface_count += 1;
4506 let culling_rect = prim_instance.clip_set.local_clip_rect
4507 .intersection(&prim_rect)
4508 .unwrap_or_else(LayoutRect::zero);
4510 // Primitive lengths aren't evenly distributed among primitive lists:
4511 // We often have a large amount of single primitive lists, a
4512 // few below 20~30 primitives, and even fewer lists (maybe a couple)
4513 // in the multiple hundreds with nothing in between.
4514 // We can see in profiles that reallocating vectors while pushing
4515 // primitives is taking a large amount of the total scene build time,
4516 // so we take advantage of what we know about the length distributions
4517 // to go for an adapted vector growth pattern that avoids over-allocating
4518 // for the many small allocations while avoiding a lot of reallocation by
4519 // quickly converging to the common sizes.
4520 // Rust's default vector growth strategy (when pushing elements one by one)
4521 // is to double the capacity every time.
4522 let prims_len = self.prim_instances.len();
4523 if prims_len == self.prim_instances.capacity() {
4524 let next_alloc = match prims_len {
4525 1 ..= 31 => 32 - prims_len,
4526 32 ..= 256 => 512 - prims_len,
4530 self.prim_instances.reserve(next_alloc);
4533 let instance_index = prims_len;
4534 self.prim_instances.push(prim_instance);
4536 if let Some(cluster) = self.clusters.last_mut() {
4537 if cluster.is_compatible(spatial_node_index, flags) {
4538 cluster.add_instance(&culling_rect, instance_index);
4543 // Same idea with clusters, using a different distribution.
4544 let clusters_len = self.clusters.len();
4545 if clusters_len == self.clusters.capacity() {
4546 let next_alloc = match clusters_len {
4547 1 ..= 15 => 16 - clusters_len,
4548 16 ..= 127 => 128 - clusters_len,
4549 _ => clusters_len * 2,
4552 self.clusters.reserve(next_alloc);
4555 let mut cluster = PrimitiveCluster::new(
4561 cluster.add_instance(&culling_rect, instance_index);
4562 self.clusters.push(cluster);
4565 /// Returns true if there are no clusters (and thus primitives)
4566 pub fn is_empty(&self) -> bool {
4567 self.clusters.is_empty()
4571 /// Defines configuration options for a given picture primitive.
4572 #[cfg_attr(feature = "capture", derive(Serialize))]
4573 pub struct PictureOptions {
4574 /// If true, WR should inflate the bounding rect of primitives when
4575 /// using a filter effect that requires inflation.
4576 pub inflate_if_required: bool,
4579 impl Default for PictureOptions {
4580 fn default() -> Self {
4582 inflate_if_required: true,
4587 #[cfg_attr(feature = "capture", derive(Serialize))]
4588 pub struct PicturePrimitive {
4589 /// List of primitives, and associated info for this picture.
4590 pub prim_list: PrimitiveList,
4592 #[cfg_attr(feature = "capture", serde(skip))]
4593 pub state: Option<PictureState>,
4595 /// If true, apply the local clip rect to primitive drawn
4596 /// in this picture.
4597 pub apply_local_clip_rect: bool,
4598 /// If false and transform ends up showing the back of the picture,
4599 /// it will be considered invisible.
4600 pub is_backface_visible: bool,
4602 pub primary_render_task_id: Option<RenderTaskId>,
4603 /// If a mix-blend-mode, contains the render task for
4604 /// the readback of the framebuffer that we use to sample
4605 /// from in the mix-blend-mode shader.
4606 /// For drop-shadow filter, this will store the original
4607 /// picture task which would be rendered on screen after
4609 pub secondary_render_task_id: Option<RenderTaskId>,
4610 /// How this picture should be composited.
4611 /// If None, don't composite - just draw directly on parent surface.
4612 pub requested_composite_mode: Option<PictureCompositeMode>,
4614 pub raster_config: Option<RasterConfig>,
4615 pub context_3d: Picture3DContext<OrderedPictureChild>,
4617 // Optional cache handles for storing extra data
4618 // in the GPU cache, depending on the type of
4620 pub extra_gpu_data_handles: SmallVec<[GpuCacheHandle; 1]>,
4622 /// The spatial node index of this picture when it is
4623 /// composited into the parent picture.
4624 pub spatial_node_index: SpatialNodeIndex,
4626 /// The conservative local rect of this picture. It is
4627 /// built dynamically during the first picture traversal.
4628 /// It is composed of already snapped primitives.
4629 pub estimated_local_rect: LayoutRect,
4631 /// The local rect of this picture. It is built
4632 /// dynamically during the frame visibility update. It
4633 /// differs from the estimated_local_rect because it
4634 /// will not contain culled primitives, takes into
4635 /// account surface inflation and the whole clip chain.
4636 /// It is frequently the same, but may be quite
4637 /// different depending on how much was culled.
4638 pub precise_local_rect: LayoutRect,
4640 /// Store the state of the previous precise local rect
4641 /// for this picture. We need this in order to know when
4642 /// to invalidate segments / drop-shadow gpu cache handles.
4643 pub prev_precise_local_rect: LayoutRect,
4645 /// If false, this picture needs to (re)build segments
4646 /// if it supports segment rendering. This can occur
4647 /// if the local rect of the picture changes due to
4648 /// transform animation and/or scrolling.
4649 pub segments_are_valid: bool,
4651 /// The config options for this picture.
4652 pub options: PictureOptions,
4654 /// Set to true if we know for sure the picture is fully opaque.
4655 pub is_opaque: bool,
4658 impl PicturePrimitive {
4659 pub fn print<T: PrintTreePrinter>(
4662 self_index: PictureIndex,
4665 pt.new_level(format!("{:?}", self_index));
4666 pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len()));
4667 pt.add_item(format!("estimated_local_rect: {:?}", self.estimated_local_rect));
4668 pt.add_item(format!("precise_local_rect: {:?}", self.precise_local_rect));
4669 pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
4670 pt.add_item(format!("raster_config: {:?}", self.raster_config));
4671 pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
4673 for child_pic_index in &self.prim_list.child_pictures {
4674 pictures[child_pic_index.0].print(pictures, *child_pic_index, pt);
4680 /// Returns true if this picture supports segmented rendering.
4681 pub fn can_use_segments(&self) -> bool {
4682 match self.raster_config {
4683 // TODO(gw): Support brush segment rendering for filter and mix-blend
4684 // shaders. It's possible this already works, but I'm just
4685 // applying this optimization to Blit mode for now.
4686 Some(RasterConfig { composite_mode: PictureCompositeMode::MixBlend(..), .. }) |
4687 Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(..), .. }) |
4688 Some(RasterConfig { composite_mode: PictureCompositeMode::ComponentTransferFilter(..), .. }) |
4689 Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) |
4690 Some(RasterConfig { composite_mode: PictureCompositeMode::SvgFilter(..), .. }) |
4694 Some(RasterConfig { composite_mode: PictureCompositeMode::Blit(reason), ..}) => {
4695 reason == BlitReason::CLIP
4700 fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
4701 match self.requested_composite_mode {
4702 Some(PictureCompositeMode::Filter(ref mut filter)) => {
4704 Filter::Opacity(ref binding, ref mut value) => {
4705 *value = properties.resolve_float(binding);
4716 pub fn is_visible(&self) -> bool {
4717 match self.requested_composite_mode {
4718 Some(PictureCompositeMode::Filter(ref filter)) => {
4725 // TODO(gw): We have the PictureOptions struct available. We
4726 // should move some of the parameter list in this
4727 // method to be part of the PictureOptions, and
4728 // avoid adding new parameters here.
4730 requested_composite_mode: Option<PictureCompositeMode>,
4731 context_3d: Picture3DContext<OrderedPictureChild>,
4732 apply_local_clip_rect: bool,
4733 flags: PrimitiveFlags,
4734 prim_list: PrimitiveList,
4735 spatial_node_index: SpatialNodeIndex,
4736 options: PictureOptions,
4741 primary_render_task_id: None,
4742 secondary_render_task_id: None,
4743 requested_composite_mode,
4744 raster_config: None,
4746 extra_gpu_data_handles: SmallVec::new(),
4747 apply_local_clip_rect,
4748 is_backface_visible: flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
4750 estimated_local_rect: LayoutRect::zero(),
4751 precise_local_rect: LayoutRect::zero(),
4752 prev_precise_local_rect: LayoutRect::zero(),
4754 segments_are_valid: false,
4759 pub fn take_context(
4761 pic_index: PictureIndex,
4762 surface_spatial_node_index: SpatialNodeIndex,
4763 raster_spatial_node_index: SpatialNodeIndex,
4764 parent_surface_index: SurfaceIndex,
4765 parent_subpixel_mode: SubpixelMode,
4766 frame_state: &mut FrameBuildingState,
4767 frame_context: &FrameBuildingContext,
4768 scratch: &mut PrimitiveScratchBuffer,
4769 tile_cache_logger: &mut TileCacheLogger,
4770 tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
4771 ) -> Option<(PictureContext, PictureState, PrimitiveList)> {
4772 self.primary_render_task_id = None;
4773 self.secondary_render_task_id = None;
4775 if !self.is_visible() {
4779 profile_scope!("take_context");
4781 // Extract the raster and surface spatial nodes from the raster
4782 // config, if this picture establishes a surface. Otherwise just
4783 // pass in the spatial node indices from the parent context.
4784 let (raster_spatial_node_index, surface_spatial_node_index, surface_index, inflation_factor) = match self.raster_config {
4785 Some(ref raster_config) => {
4786 let surface = &frame_state.surfaces[raster_config.surface_index.0];
4789 surface.raster_spatial_node_index,
4790 self.spatial_node_index,
4791 raster_config.surface_index,
4792 surface.inflation_factor,
4797 raster_spatial_node_index,
4798 surface_spatial_node_index,
4799 parent_surface_index,
4805 let map_pic_to_world = SpaceMapper::new_with_target(
4806 ROOT_SPATIAL_NODE_INDEX,
4807 surface_spatial_node_index,
4808 frame_context.global_screen_world_rect,
4809 frame_context.spatial_tree,
4812 let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
4813 .unwrap_or_else(PictureRect::max_rect);
4815 let map_local_to_pic = SpaceMapper::new(
4816 surface_spatial_node_index,
4820 let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
4821 surface_spatial_node_index,
4822 raster_spatial_node_index,
4823 frame_context.global_screen_world_rect,
4824 frame_context.spatial_tree,
4827 let plane_splitter = match self.context_3d {
4828 Picture3DContext::Out => {
4831 Picture3DContext::In { root_data: Some(_), .. } => {
4832 Some(PlaneSplitter::new())
4834 Picture3DContext::In { root_data: None, .. } => {
4839 match self.raster_config {
4840 Some(RasterConfig { surface_index, composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => {
4841 let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
4842 let mut debug_info = SliceDebugInfo::new();
4843 let mut surface_tasks = Vec::with_capacity(tile_cache.tile_count());
4844 let mut surface_device_rect = DeviceRect::zero();
4845 let device_pixel_scale = frame_state
4846 .surfaces[surface_index.0]
4847 .device_pixel_scale;
4849 // Get the overall world space rect of the picture cache. Used to clip
4850 // the tile rects below for occlusion testing to the relevant area.
4851 let world_clip_rect = map_pic_to_world
4852 .map(&tile_cache.local_clip_rect)
4853 .expect("bug: unable to map clip rect");
4854 let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
4856 for (sub_slice_index, sub_slice) in tile_cache.sub_slices.iter_mut().enumerate() {
4857 for tile in sub_slice.tiles.values_mut() {
4858 surface_device_rect = surface_device_rect.union(&tile.device_valid_rect);
4860 if tile.is_visible {
4861 // Get the world space rect that this tile will actually occupy on screem
4862 let device_draw_rect = device_clip_rect.intersection(&tile.device_valid_rect);
4864 // If that draw rect is occluded by some set of tiles in front of it,
4865 // then mark it as not visible and skip drawing. When it's not occluded
4866 // it will fail this test, and get rasterized by the render task setup
4868 match device_draw_rect {
4869 Some(device_draw_rect) => {
4870 // Only check for occlusion on visible tiles that are fixed position.
4871 if tile_cache.spatial_node_index == ROOT_SPATIAL_NODE_INDEX &&
4872 frame_state.composite_state.occluders.is_tile_occluded(tile.z_id, device_draw_rect) {
4873 // If this tile has an allocated native surface, free it, since it's completely
4874 // occluded. We will need to re-allocate this surface if it becomes visible,
4875 // but that's likely to be rare (e.g. when there is no content display list
4876 // for a frame or two during a tab switch).
4877 let surface = tile.surface.as_mut().expect("no tile surface set!");
4879 if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
4880 if let Some(id) = id.take() {
4881 frame_state.resource_cache.destroy_compositor_tile(id);
4885 tile.is_visible = false;
4887 if frame_context.fb_config.testing {
4888 debug_info.tiles.insert(
4890 TileDebugInfo::Occluded,
4898 tile.is_visible = false;
4903 // If we get here, we want to ensure that the surface remains valid in the texture
4904 // cache, _even if_ it's not visible due to clipping or being scrolled off-screen.
4905 // This ensures that we retain valid tiles that are off-screen, but still in the
4906 // display port of this tile cache instance.
4907 if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() {
4908 if let SurfaceTextureDescriptor::TextureCache { ref handle, .. } = descriptor {
4909 frame_state.resource_cache.texture_cache.request(
4911 frame_state.gpu_cache,
4916 // If the tile has been found to be off-screen / clipped, skip any further processing.
4917 if !tile.is_visible {
4918 if frame_context.fb_config.testing {
4919 debug_info.tiles.insert(
4921 TileDebugInfo::Culled,
4928 if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
4929 tile.root.draw_debug_rects(
4932 tile.current_descriptor.local_valid_rect,
4934 frame_context.global_device_pixel_scale,
4937 let label_offset = DeviceVector2D::new(
4938 20.0 + sub_slice_index as f32 * 20.0,
4939 30.0 + sub_slice_index as f32 * 20.0,
4941 let tile_device_rect = tile.world_tile_rect * frame_context.global_device_pixel_scale;
4942 if tile_device_rect.size.height >= label_offset.y {
4943 let surface = tile.surface.as_ref().expect("no tile surface set!");
4945 scratch.push_debug_string(
4946 tile_device_rect.origin + label_offset,
4948 format!("{:?}: s={} is_opaque={} surface={} sub={}",
4959 if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() {
4961 SurfaceTextureDescriptor::TextureCache { ref handle, .. } => {
4962 // Invalidate if the backing texture was evicted.
4963 if frame_state.resource_cache.texture_cache.is_allocated(handle) {
4964 // Request the backing texture so it won't get evicted this frame.
4965 // We specifically want to mark the tile texture as used, even
4966 // if it's detected not visible below and skipped. This is because
4967 // we maintain the set of tiles we care about based on visibility
4968 // during pre_update. If a tile still exists after that, we are
4969 // assuming that it's either visible or we want to retain it for
4970 // a while in case it gets scrolled back onto screen soon.
4971 // TODO(gw): Consider switching to manual eviction policy?
4972 frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache);
4974 // If the texture was evicted on a previous frame, we need to assume
4975 // that the entire tile rect is dirty.
4976 tile.invalidate(None, InvalidationReason::NoTexture);
4979 SurfaceTextureDescriptor::Native { id, .. } => {
4981 // There is no current surface allocation, so ensure the entire tile is invalidated
4982 tile.invalidate(None, InvalidationReason::NoSurface);
4988 // Ensure that the dirty rect doesn't extend outside the local valid rect.
4989 tile.local_dirty_rect = tile.local_dirty_rect
4990 .intersection(&tile.current_descriptor.local_valid_rect)
4991 .unwrap_or_else(PictureRect::zero);
4993 // Update the world/device dirty rect
4994 let world_dirty_rect = map_pic_to_world.map(&tile.local_dirty_rect).expect("bug");
4996 let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round();
4997 tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale)
4999 .intersection(&device_rect)
5000 .unwrap_or_else(DeviceRect::zero);
5003 if frame_context.fb_config.testing {
5004 debug_info.tiles.insert(
5006 TileDebugInfo::Valid,
5013 // Add this dirty rect to the dirty region tracker. This must be done outside the if statement below,
5014 // so that we include in the dirty region tiles that are handled by a background color only (no
5015 // surface allocation).
5016 tile_cache.dirty_region.add_dirty_region(
5017 tile.local_dirty_rect,
5018 SubSliceIndex::new(sub_slice_index),
5019 frame_context.spatial_tree,
5022 // Ensure that this texture is allocated.
5023 if let TileSurface::Texture { ref mut descriptor } = tile.surface.as_mut().unwrap() {
5025 SurfaceTextureDescriptor::TextureCache { ref mut handle } => {
5026 if !frame_state.resource_cache.texture_cache.is_allocated(handle) {
5027 frame_state.resource_cache.texture_cache.update_picture_cache(
5028 tile_cache.current_tile_size,
5030 frame_state.gpu_cache,
5034 SurfaceTextureDescriptor::Native { id } => {
5036 // Allocate a native surface id if we're in native compositing mode,
5037 // and we don't have a surface yet (due to first frame, or destruction
5038 // due to tile size changing etc).
5039 if sub_slice.native_surface.is_none() {
5040 let opaque = frame_state
5042 .create_compositor_surface(
5043 tile_cache.virtual_offset,
5044 tile_cache.current_tile_size,
5048 let alpha = frame_state
5050 .create_compositor_surface(
5051 tile_cache.virtual_offset,
5052 tile_cache.current_tile_size,
5056 sub_slice.native_surface = Some(NativeSurface {
5062 // Create the tile identifier and allocate it.
5063 let surface_id = if tile.is_opaque {
5064 sub_slice.native_surface.as_ref().unwrap().opaque
5066 sub_slice.native_surface.as_ref().unwrap().alpha
5069 let tile_id = NativeTileId {
5071 x: tile.tile_offset.x,
5072 y: tile.tile_offset.y,
5075 frame_state.resource_cache.create_compositor_tile(tile_id);
5077 *id = Some(tile_id);
5082 let content_origin_f = tile.world_tile_rect.origin * device_pixel_scale;
5083 let content_origin = content_origin_f.round();
5084 debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.01);
5085 debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.01);
5087 let surface = descriptor.resolve(
5088 frame_state.resource_cache,
5089 tile_cache.current_tile_size,
5092 let scissor_rect = tile.device_dirty_rect
5093 .translate(-device_rect.origin.to_vector())
5097 let valid_rect = tile.device_valid_rect
5098 .translate(-device_rect.origin.to_vector())
5102 let task_size = tile_cache.current_tile_size;
5104 let batch_filter = BatchFilter {
5105 rect_in_pic_space: tile.local_dirty_rect,
5106 sub_slice_index: SubSliceIndex::new(sub_slice_index),
5109 let render_task_id = frame_state.rg_builder.add().init(
5111 RenderTaskLocation::Static {
5112 surface: StaticRenderTaskSurface::PictureCache {
5115 rect: task_size.into(),
5117 RenderTaskKind::new_picture(
5119 tile_cache.current_tile_size.to_f32(),
5122 surface_spatial_node_index,
5131 surface_tasks.push(render_task_id);
5134 if frame_context.fb_config.testing {
5135 debug_info.tiles.insert(
5137 TileDebugInfo::Dirty(DirtyTileDebugInfo {
5138 local_valid_rect: tile.current_descriptor.local_valid_rect,
5139 local_dirty_rect: tile.local_dirty_rect,
5144 // If the entire tile valid region is dirty, we can update the fract offset
5145 // at which the tile was rendered.
5146 if tile.device_dirty_rect.contains_rect(&tile.device_valid_rect) {
5147 tile.device_fract_offset = tile_cache.device_fract_offset;
5150 // Now that the tile is valid, reset the dirty rect.
5151 tile.local_dirty_rect = PictureRect::zero();
5152 tile.is_valid = true;
5156 // If invalidation debugging is enabled, dump the picture cache state to a tree printer.
5157 if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) {
5161 // If testing mode is enabled, write some information about the current state
5162 // of this picture cache (made available in RenderResults).
5163 if frame_context.fb_config.testing {
5164 frame_state.composite_state
5165 .picture_cache_debug
5173 frame_state.init_surface_tiled(
5176 surface_device_rect,
5179 Some(ref mut raster_config) => {
5180 let pic_rect = self.precise_local_rect.cast_unit();
5182 let mut device_pixel_scale = frame_state
5183 .surfaces[raster_config.surface_index.0]
5184 .device_pixel_scale;
5186 let scale_factors = frame_state
5187 .surfaces[raster_config.surface_index.0]
5190 // If the primitive has a filter that can sample with an offset, the clip rect has
5191 // to take it into account.
5192 let clip_inflation = match raster_config.composite_mode {
5193 PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
5194 let mut max_offset = vec2(0.0, 0.0);
5195 let mut min_offset = vec2(0.0, 0.0);
5196 for shadow in shadows {
5197 let offset = layout_vector_as_picture_vector(shadow.offset);
5198 max_offset = max_offset.max(offset);
5199 min_offset = min_offset.min(offset);
5202 // Get the shadow offsets in world space.
5203 let raster_min = map_pic_to_raster.map_vector(min_offset);
5204 let raster_max = map_pic_to_raster.map_vector(max_offset);
5205 let world_min = map_raster_to_world.map_vector(raster_min);
5206 let world_max = map_raster_to_world.map_vector(raster_max);
5208 // Grow the clip in the opposite direction of the shadow's offset.
5209 SideOffsets2D::from_vectors_outer(
5210 -world_max.max(vec2(0.0, 0.0)),
5211 -world_min.min(vec2(0.0, 0.0)),
5214 _ => SideOffsets2D::zero(),
5217 let (mut clipped, mut unclipped) = match get_raster_rects(
5220 &map_raster_to_world,
5221 raster_config.clipped_bounding_rect.outer_rect(clip_inflation),
5229 let transform = map_pic_to_raster.get_transform();
5231 /// If the picture (raster_config) establishes a raster root,
5232 /// its requested resolution won't be clipped by the parent or
5233 /// viewport; so we need to make sure the requested resolution is
5234 /// "reasonable", ie. <= MAX_SURFACE_SIZE. If not, scale the
5235 /// picture down until it fits that limit. This results in a new
5236 /// device_rect, a new unclipped rect, and a new device_pixel_scale.
5238 /// Since the adjusted device_pixel_scale is passed into the
5239 /// RenderTask (and then the shader via RenderTaskData) this mostly
5240 /// works transparently, reusing existing support for variable DPI
5241 /// support. The on-the-fly scaling can be seen as on-the-fly,
5242 /// per-task DPI adjustment. Logical pixels are unaffected.
5244 /// The scaling factor is returned to the caller; blur radius,
5245 /// font size, etc. need to be scaled accordingly.
5246 fn adjust_scale_for_max_surface_size(
5247 raster_config: &RasterConfig,
5248 max_target_size: i32,
5249 pic_rect: PictureRect,
5250 map_pic_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
5251 map_raster_to_world: &SpaceMapper<RasterPixel, WorldPixel>,
5252 clipped_prim_bounding_rect: WorldRect,
5253 device_pixel_scale : &mut DevicePixelScale,
5254 device_rect: &mut DeviceRect,
5255 unclipped: &mut DeviceRect) -> Option<f32>
5257 let limit = if raster_config.establishes_raster_root {
5260 max_target_size as f32
5262 if device_rect.size.width > limit || device_rect.size.height > limit {
5263 // round_out will grow by 1 integer pixel if origin is on a
5264 // fractional position, so keep that margin for error with -1:
5265 let scale = (limit as f32 - 1.0) /
5266 (f32::max(device_rect.size.width, device_rect.size.height));
5267 *device_pixel_scale = *device_pixel_scale * Scale::new(scale);
5268 let new_device_rect = device_rect.to_f32() * Scale::new(scale);
5269 *device_rect = new_device_rect.round_out();
5271 *unclipped = match get_raster_rects(
5274 &map_raster_to_world,
5275 clipped_prim_bounding_rect,
5278 Some(info) => info.1,
5291 let primary_render_task_id;
5292 match raster_config.composite_mode {
5293 PictureCompositeMode::TileCache { .. } => {
5294 unreachable!("handled above");
5296 PictureCompositeMode::Filter(Filter::Blur(width, height)) => {
5297 let width_std_deviation = clamp_blur_radius(width, scale_factors) * device_pixel_scale.0;
5298 let height_std_deviation = clamp_blur_radius(height, scale_factors) * device_pixel_scale.0;
5299 let mut blur_std_deviation = DeviceSize::new(
5300 width_std_deviation * scale_factors.0,
5301 height_std_deviation * scale_factors.1
5303 let mut device_rect = if self.options.inflate_if_required {
5304 let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor;
5305 let inflation_factor = inflation_factor * device_pixel_scale.0;
5307 // The clipped field is the part of the picture that is visible
5308 // on screen. The unclipped field is the screen-space rect of
5309 // the complete picture, if no screen / clip-chain was applied
5310 // (this includes the extra space for blur region). To ensure
5311 // that we draw a large enough part of the picture to get correct
5312 // blur results, inflate that clipped area by the blur range, and
5313 // then intersect with the total screen rect, to minimize the
5316 .inflate(inflation_factor * scale_factors.0, inflation_factor * scale_factors.1)
5317 .intersection(&unclipped)
5323 let mut original_size = device_rect.size;
5325 // Adjust the size to avoid introducing sampling errors during the down-scaling passes.
5326 // what would be even better is to rasterize the picture at the down-scaled size
5328 device_rect.size = BlurTask::adjusted_blur_source_size(
5333 if let Some(scale) = adjust_scale_for_max_surface_size(
5334 raster_config, frame_context.fb_config.max_target_size,
5335 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5336 raster_config.clipped_bounding_rect,
5337 &mut device_pixel_scale, &mut device_rect, &mut unclipped,
5339 blur_std_deviation = blur_std_deviation * scale;
5340 original_size = original_size.to_f32() * scale;
5341 raster_config.root_scaling_factor = scale;
5344 let uv_rect_kind = calculate_uv_rect_kind(
5351 let task_size = device_rect.size.to_i32();
5353 let picture_task_id = frame_state.rg_builder.add().init(
5354 RenderTask::new_dynamic(
5356 RenderTaskKind::new_picture(
5361 surface_spatial_node_index,
5367 ).with_uv_rect_kind(uv_rect_kind)
5370 let blur_render_task_id = RenderTask::new_blur(
5373 frame_state.rg_builder,
5374 RenderTargetKind::Color,
5376 original_size.to_i32(),
5379 primary_render_task_id = Some(blur_render_task_id);
5381 frame_state.init_surface_chain(
5382 raster_config.surface_index,
5383 blur_render_task_id,
5385 parent_surface_index,
5389 PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
5390 let mut max_std_deviation = 0.0;
5391 for shadow in shadows {
5392 max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius);
5394 max_std_deviation = clamp_blur_radius(max_std_deviation, scale_factors) * device_pixel_scale.0;
5395 let max_blur_range = max_std_deviation * BLUR_SAMPLE_SCALE;
5397 // We cast clipped to f32 instead of casting unclipped to i32
5398 // because unclipped can overflow an i32.
5399 let mut device_rect = clipped
5400 .inflate(max_blur_range * scale_factors.0, max_blur_range * scale_factors.1)
5401 .intersection(&unclipped)
5404 device_rect.size = BlurTask::adjusted_blur_source_size(
5407 max_std_deviation * scale_factors.0,
5408 max_std_deviation * scale_factors.1
5412 if let Some(scale) = adjust_scale_for_max_surface_size(
5413 raster_config, frame_context.fb_config.max_target_size,
5414 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5415 raster_config.clipped_bounding_rect,
5416 &mut device_pixel_scale, &mut device_rect, &mut unclipped,
5418 // std_dev adjusts automatically from using device_pixel_scale
5419 raster_config.root_scaling_factor = scale;
5422 let uv_rect_kind = calculate_uv_rect_kind(
5429 let task_size = device_rect.size.to_i32();
5431 let picture_task_id = frame_state.rg_builder.add().init(
5432 RenderTask::new_dynamic(
5434 RenderTaskKind::new_picture(
5439 surface_spatial_node_index,
5445 ).with_uv_rect_kind(uv_rect_kind)
5448 // Add this content picture as a dependency of the parent surface, to
5449 // ensure it isn't free'd after the shadow uses it as an input.
5450 frame_state.add_child_render_task(
5451 parent_surface_index,
5455 let mut blur_tasks = BlurTaskCache::default();
5457 self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
5459 let mut blur_render_task_id = picture_task_id;
5460 for shadow in shadows {
5461 let blur_radius = clamp_blur_radius(shadow.blur_radius, scale_factors) * device_pixel_scale.0;
5462 blur_render_task_id = RenderTask::new_blur(
5464 blur_radius * scale_factors.0,
5465 blur_radius * scale_factors.1,
5468 frame_state.rg_builder,
5469 RenderTargetKind::Color,
5470 Some(&mut blur_tasks),
5471 device_rect.size.to_i32(),
5475 primary_render_task_id = Some(blur_render_task_id);
5476 self.secondary_render_task_id = Some(picture_task_id);
5478 frame_state.init_surface_chain(
5479 raster_config.surface_index,
5480 blur_render_task_id,
5482 parent_surface_index,
5486 PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
5488 frame_context.fb_config.gpu_supports_advanced_blend,
5489 frame_context.fb_config.advanced_blend_is_coherent,
5490 frame_context.fb_config.dual_source_blending_is_enabled &&
5491 frame_context.fb_config.dual_source_blending_is_supported,
5493 if let Some(scale) = adjust_scale_for_max_surface_size(
5494 raster_config, frame_context.fb_config.max_target_size,
5495 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5496 raster_config.clipped_bounding_rect,
5497 &mut device_pixel_scale, &mut clipped, &mut unclipped,
5499 raster_config.root_scaling_factor = scale;
5502 let uv_rect_kind = calculate_uv_rect_kind(
5509 let parent_surface = &frame_state.surfaces[parent_surface_index.0];
5510 let parent_raster_spatial_node_index = parent_surface.raster_spatial_node_index;
5511 let parent_device_pixel_scale = parent_surface.device_pixel_scale;
5513 // Create a space mapper that will allow mapping from the local rect
5514 // of the mix-blend primitive into the space of the surface that we
5515 // need to read back from. Note that we use the parent's raster spatial
5516 // node here, so that we are in the correct device space of the parent
5517 // surface, whether it establishes a raster root or not.
5518 let map_pic_to_parent = SpaceMapper::new_with_target(
5519 parent_raster_spatial_node_index,
5520 self.spatial_node_index,
5521 RasterRect::max_rect(), // TODO(gw): May need a conservative estimate?
5522 frame_context.spatial_tree,
5524 let pic_in_raster_space = map_pic_to_parent
5526 .expect("bug: unable to map mix-blend content into parent");
5528 // Apply device pixel ratio for parent surface to get into device
5529 // pixels for that surface.
5530 let backdrop_rect = raster_rect_to_device_pixels(
5531 pic_in_raster_space,
5532 parent_device_pixel_scale,
5535 let parent_surface_rect = parent_surface.get_device_rect();
5537 // If there is no available parent surface to read back from (for example, if
5538 // the parent surface is affected by a clip that doesn't affect the child
5539 // surface), then create a dummy 16x16 readback. In future, we could alter
5540 // the composite mode of this primitive to skip the mix-blend, but for simplicity
5541 // we just create a dummy readback for now.
5543 let readback_task_id = match backdrop_rect.intersection(&parent_surface_rect) {
5544 Some(available_rect) => {
5545 // Calculate the UV coords necessary for the shader to sampler
5546 // from the primitive rect within the readback region. This is
5547 // 0..1 for aligned surfaces, but doing it this way allows
5548 // accurate sampling if the primitive bounds have fractional values.
5549 let backdrop_uv = calculate_uv_rect_kind(
5551 &map_pic_to_parent.get_transform(),
5553 parent_device_pixel_scale,
5556 frame_state.rg_builder.add().init(
5557 RenderTask::new_dynamic(
5558 available_rect.size.to_i32(),
5559 RenderTaskKind::new_readback(Some(available_rect.origin)),
5560 ).with_uv_rect_kind(backdrop_uv)
5564 frame_state.rg_builder.add().init(
5565 RenderTask::new_dynamic(
5566 DeviceIntSize::new(16, 16),
5567 RenderTaskKind::new_readback(None),
5573 frame_state.add_child_render_task(
5574 parent_surface_index,
5578 self.secondary_render_task_id = Some(readback_task_id);
5580 let task_size = clipped.size.to_i32();
5582 let render_task_id = frame_state.rg_builder.add().init(
5583 RenderTask::new_dynamic(
5585 RenderTaskKind::new_picture(
5590 surface_spatial_node_index,
5596 ).with_uv_rect_kind(uv_rect_kind)
5599 primary_render_task_id = Some(render_task_id);
5601 frame_state.init_surface(
5602 raster_config.surface_index,
5604 parent_surface_index,
5608 PictureCompositeMode::Filter(..) => {
5610 if let Some(scale) = adjust_scale_for_max_surface_size(
5611 raster_config, frame_context.fb_config.max_target_size,
5612 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5613 raster_config.clipped_bounding_rect,
5614 &mut device_pixel_scale, &mut clipped, &mut unclipped,
5616 raster_config.root_scaling_factor = scale;
5619 let uv_rect_kind = calculate_uv_rect_kind(
5626 let task_size = clipped.size.to_i32();
5628 let render_task_id = frame_state.rg_builder.add().init(
5629 RenderTask::new_dynamic(
5631 RenderTaskKind::new_picture(
5636 surface_spatial_node_index,
5642 ).with_uv_rect_kind(uv_rect_kind)
5645 primary_render_task_id = Some(render_task_id);
5647 frame_state.init_surface(
5648 raster_config.surface_index,
5650 parent_surface_index,
5654 PictureCompositeMode::ComponentTransferFilter(..) => {
5655 if let Some(scale) = adjust_scale_for_max_surface_size(
5656 raster_config, frame_context.fb_config.max_target_size,
5657 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5658 raster_config.clipped_bounding_rect,
5659 &mut device_pixel_scale, &mut clipped, &mut unclipped,
5661 raster_config.root_scaling_factor = scale;
5664 let uv_rect_kind = calculate_uv_rect_kind(
5671 let task_size = clipped.size.to_i32();
5673 let render_task_id = frame_state.rg_builder.add().init(
5674 RenderTask::new_dynamic(
5676 RenderTaskKind::new_picture(
5681 surface_spatial_node_index,
5687 ).with_uv_rect_kind(uv_rect_kind)
5690 primary_render_task_id = Some(render_task_id);
5692 frame_state.init_surface(
5693 raster_config.surface_index,
5695 parent_surface_index,
5699 PictureCompositeMode::MixBlend(..) |
5700 PictureCompositeMode::Blit(_) => {
5701 if let Some(scale) = adjust_scale_for_max_surface_size(
5702 raster_config, frame_context.fb_config.max_target_size,
5703 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5704 raster_config.clipped_bounding_rect,
5705 &mut device_pixel_scale, &mut clipped, &mut unclipped,
5707 raster_config.root_scaling_factor = scale;
5710 let uv_rect_kind = calculate_uv_rect_kind(
5717 let task_size = clipped.size.to_i32();
5719 let render_task_id = frame_state.rg_builder.add().init(
5720 RenderTask::new_dynamic(
5722 RenderTaskKind::new_picture(
5727 surface_spatial_node_index,
5733 ).with_uv_rect_kind(uv_rect_kind)
5736 primary_render_task_id = Some(render_task_id);
5738 frame_state.init_surface(
5739 raster_config.surface_index,
5741 parent_surface_index,
5745 PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
5747 if let Some(scale) = adjust_scale_for_max_surface_size(
5748 raster_config, frame_context.fb_config.max_target_size,
5749 pic_rect, &map_pic_to_raster, &map_raster_to_world,
5750 raster_config.clipped_bounding_rect,
5751 &mut device_pixel_scale, &mut clipped, &mut unclipped,
5753 raster_config.root_scaling_factor = scale;
5756 let uv_rect_kind = calculate_uv_rect_kind(
5763 let task_size = clipped.size.to_i32();
5765 let picture_task_id = frame_state.rg_builder.add().init(
5766 RenderTask::new_dynamic(
5768 RenderTaskKind::new_picture(
5773 surface_spatial_node_index,
5779 ).with_uv_rect_kind(uv_rect_kind)
5782 let filter_task_id = RenderTask::new_svg_filter(
5785 frame_state.rg_builder,
5786 clipped.size.to_i32(),
5792 primary_render_task_id = Some(filter_task_id);
5794 frame_state.init_surface_chain(
5795 raster_config.surface_index,
5798 parent_surface_index,
5804 self.primary_render_task_id = primary_render_task_id;
5806 // Update the device pixel ratio in the surface, in case it was adjusted due
5807 // to the surface being too large. This ensures the correct scale is available
5808 // in case it's used as input to a parent mix-blend-mode readback.
5810 .surfaces[raster_config.surface_index.0]
5811 .device_pixel_scale = device_pixel_scale;
5817 #[cfg(feature = "capture")]
5819 if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
5820 if let Some(PictureCompositeMode::TileCache { slice_id }) = self.requested_composite_mode {
5821 if let Some(ref tile_cache) = tile_caches.get(&slice_id) {
5822 // extract just the fields that we're interested in
5823 let mut tile_cache_tiny = TileCacheInstanceSerializer {
5824 slice: tile_cache.slice,
5825 tiles: FastHashMap::default(),
5826 background_color: tile_cache.background_color,
5827 fract_offset: tile_cache.fract_offset
5829 // TODO(gw): Debug output only writes the primary sub-slice for now
5830 for (key, tile) in &tile_cache.sub_slices.first().unwrap().tiles {
5831 tile_cache_tiny.tiles.insert(*key, TileSerializer {
5832 rect: tile.local_tile_rect,
5833 current_descriptor: tile.current_descriptor.clone(),
5834 device_fract_offset: tile.device_fract_offset,
5836 root: tile.root.clone(),
5837 background_color: tile.background_color,
5838 invalidation_reason: tile.invalidation_reason.clone()
5841 let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap();
5842 tile_cache_logger.add(text, map_pic_to_world.get_transform());
5847 #[cfg(not(feature = "capture"))]
5849 let _tile_cache_logger = tile_cache_logger; // unused variable fix
5852 let state = PictureState {
5853 //TODO: check for MAX_CACHE_SIZE here?
5857 map_raster_to_world,
5861 let mut dirty_region_count = 0;
5863 // If this is a picture cache, push the dirty region to ensure any
5864 // child primitives are culled and clipped to the dirty rect(s).
5865 if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) = self.raster_config {
5866 let dirty_region = tile_caches[&slice_id].dirty_region.clone();
5867 frame_state.push_dirty_region(dirty_region);
5868 dirty_region_count += 1;
5871 if inflation_factor > 0.0 {
5872 let inflated_region = frame_state.current_dirty_region().inflate(
5874 frame_context.spatial_tree,
5876 frame_state.push_dirty_region(inflated_region);
5877 dirty_region_count += 1;
5880 // Disallow subpixel AA if an intermediate surface is needed.
5881 // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
5882 let subpixel_mode = match self.raster_config {
5883 Some(RasterConfig { ref composite_mode, .. }) => {
5884 let subpixel_mode = match composite_mode {
5885 PictureCompositeMode::TileCache { slice_id } => {
5886 tile_caches[&slice_id].subpixel_mode
5888 PictureCompositeMode::Blit(..) |
5889 PictureCompositeMode::ComponentTransferFilter(..) |
5890 PictureCompositeMode::Filter(..) |
5891 PictureCompositeMode::MixBlend(..) |
5892 PictureCompositeMode::SvgFilter(..) => {
5893 // TODO(gw): We can take advantage of the same logic that
5894 // exists in the opaque rect detection for tile
5895 // caches, to allow subpixel text on other surfaces
5896 // that can be detected as opaque.
5908 // Still disable subpixel AA if parent forbids it
5909 let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) {
5910 (SubpixelMode::Allow, SubpixelMode::Allow) => {
5911 // Both parent and this surface unconditionally allow subpixel AA
5914 (SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect }) => {
5915 // Parent allows, but we are conditional subpixel AA
5916 SubpixelMode::Conditional {
5920 (SubpixelMode::Conditional { allowed_rect }, SubpixelMode::Allow) => {
5921 // Propagate conditional subpixel mode to child pictures that allow subpixel AA
5922 SubpixelMode::Conditional {
5926 (SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => {
5927 unreachable!("bug: only top level picture caches have conditional subpixel");
5929 (SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => {
5930 // Either parent or this surface explicitly deny subpixel, these take precedence
5935 let context = PictureContext {
5937 apply_local_clip_rect: self.apply_local_clip_rect,
5938 raster_spatial_node_index,
5939 surface_spatial_node_index,
5945 let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
5947 Some((context, state, prim_list))
5950 pub fn restore_context(
5952 prim_list: PrimitiveList,
5953 context: PictureContext,
5954 state: PictureState,
5955 frame_state: &mut FrameBuildingState,
5957 // Pop any dirty regions this picture set
5958 for _ in 0 .. context.dirty_region_count {
5959 frame_state.pop_dirty_region();
5962 self.prim_list = prim_list;
5963 self.state = Some(state);
5966 pub fn take_state(&mut self) -> PictureState {
5967 self.state.take().expect("bug: no state present!")
5970 /// Add a primitive instance to the plane splitter. The function would generate
5971 /// an appropriate polygon, clip it against the frustum, and register with the
5972 /// given plane splitter.
5973 pub fn add_split_plane(
5974 splitter: &mut PlaneSplitter,
5975 spatial_tree: &SpatialTree,
5976 prim_spatial_node_index: SpatialNodeIndex,
5977 original_local_rect: LayoutRect,
5978 combined_local_clip_rect: &LayoutRect,
5979 world_rect: WorldRect,
5980 plane_split_anchor: PlaneSplitAnchor,
5982 let transform = spatial_tree
5983 .get_world_transform(prim_spatial_node_index);
5984 let matrix = transform.clone().into_transform().cast();
5986 // Apply the local clip rect here, before splitting. This is
5987 // because the local clip rect can't be applied in the vertex
5988 // shader for split composites, since we are drawing polygons
5989 // rather that rectangles. The interpolation still works correctly
5990 // since we determine the UVs by doing a bilerp with a factor
5991 // from the original local rect.
5992 let local_rect = match original_local_rect
5993 .intersection(combined_local_clip_rect)
5995 Some(rect) => rect.cast(),
5996 None => return false,
5998 let world_rect = world_rect.cast();
6001 CoordinateSpaceMapping::Local => {
6002 let polygon = Polygon::from_rect(
6003 local_rect * Scale::new(1.0),
6006 splitter.add(polygon);
6008 CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => {
6009 let inv_matrix = scale_offset.inverse().to_transform().cast();
6010 let polygon = Polygon::from_transformed_rect_with_inverse(
6016 splitter.add(polygon);
6018 CoordinateSpaceMapping::ScaleOffset(_) |
6019 CoordinateSpaceMapping::Transform(_) => {
6020 let mut clipper = Clipper::new();
6021 let results = clipper.clip_transformed(
6029 if let Ok(results) = results {
6030 for poly in results {
6040 pub fn resolve_split_planes(
6042 splitter: &mut PlaneSplitter,
6043 gpu_cache: &mut GpuCache,
6044 spatial_tree: &SpatialTree,
6046 let ordered = match self.context_3d {
6047 Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
6048 _ => panic!("Expected to find 3D context root"),
6052 // Process the accumulated split planes and order them for rendering.
6053 // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
6054 let sorted = splitter.sort(vec3(0.0, 0.0, 1.0));
6055 ordered.reserve(sorted.len());
6056 for poly in sorted {
6057 let cluster = &self.prim_list.clusters[poly.anchor.cluster_index];
6058 let spatial_node_index = cluster.spatial_node_index;
6059 let transform = match spatial_tree
6060 .get_world_transform(spatial_node_index)
6063 Some(transform) => transform.into_transform(),
6064 // logging this would be a bit too verbose
6068 let local_points = [
6069 transform.transform_point3d(poly.points[0].cast()),
6070 transform.transform_point3d(poly.points[1].cast()),
6071 transform.transform_point3d(poly.points[2].cast()),
6072 transform.transform_point3d(poly.points[3].cast()),
6075 // If any of the points are un-transformable, just drop this
6076 // plane from drawing.
6077 if local_points.iter().any(|p| p.is_none()) {
6081 let p0 = local_points[0].unwrap();
6082 let p1 = local_points[1].unwrap();
6083 let p2 = local_points[2].unwrap();
6084 let p3 = local_points[3].unwrap();
6086 [p0.x, p0.y, p1.x, p1.y].into(),
6087 [p2.x, p2.y, p3.x, p3.y].into(),
6089 let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
6090 let gpu_address = gpu_cache.get_address(&gpu_handle);
6092 ordered.push(OrderedPictureChild {
6093 anchor: poly.anchor,
6100 /// Called during initial picture traversal, before we know the
6101 /// bounding rect of children. It is possible to determine the
6102 /// surface / raster config now though.
6105 state: &mut PictureUpdateState,
6106 frame_context: &FrameBuildingContext,
6107 ) -> Option<PrimitiveList> {
6108 // Reset raster config in case we early out below.
6109 self.raster_config = None;
6111 // Resolve animation properties, and early out if the filter
6112 // properties make this picture invisible.
6113 if !self.resolve_scene_properties(frame_context.scene_properties) {
6117 // For out-of-preserve-3d pictures, the backface visibility is determined by
6118 // the local transform only.
6119 // Note: we aren't taking the transform relativce to the parent picture,
6120 // since picture tree can be more dense than the corresponding spatial tree.
6121 if !self.is_backface_visible {
6122 if let Picture3DContext::Out = self.context_3d {
6123 match frame_context.spatial_tree.get_local_visible_face(self.spatial_node_index) {
6124 VisibleFace::Front => {}
6125 VisibleFace::Back => return None,
6130 // See if this picture actually needs a surface for compositing.
6131 // TODO(gw): FPC: Remove the actual / requested composite mode distinction.
6132 let actual_composite_mode = self.requested_composite_mode.clone();
6134 if let Some(composite_mode) = actual_composite_mode {
6135 // Retrieve the positioning node information for the parent surface.
6136 let parent_raster_node_index = state.current_surface().raster_spatial_node_index;
6137 let parent_device_pixel_scale = state.current_surface().device_pixel_scale;
6138 let surface_spatial_node_index = self.spatial_node_index;
6140 let surface_to_parent_transform = frame_context.spatial_tree
6141 .get_relative_transform(surface_spatial_node_index, parent_raster_node_index);
6143 // Check if there is perspective or if an SVG filter is applied, and thus whether a new
6144 // rasterization root should be established.
6145 let establishes_raster_root = match composite_mode {
6146 PictureCompositeMode::TileCache { .. } => {
6147 // Picture caches are special cased - they never need to establish a raster root. In future,
6148 // we will probably remove TileCache as a specific composite mode.
6151 PictureCompositeMode::SvgFilter(..) => {
6152 // Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root.
6155 PictureCompositeMode::MixBlend(..) |
6156 PictureCompositeMode::Filter(..) |
6157 PictureCompositeMode::ComponentTransferFilter(..) |
6158 PictureCompositeMode::Blit(..) => {
6159 // TODO(gw): As follow ups, individually move each of these composite modes to create raster roots.
6160 surface_to_parent_transform.is_perspective()
6164 let (raster_spatial_node_index, device_pixel_scale) = if establishes_raster_root {
6165 // If a raster root is established, this surface should be scaled based on the scale factors of the surface raster to parent raster transform.
6166 // This scaling helps ensure that the content in this surface does not become blurry or pixelated when composited in the parent surface.
6167 let scale_factors = surface_to_parent_transform.scale_factors();
6169 // Pick the largest scale factor of the transform for the scaling factor.
6170 // Currently, we ensure that the scaling factor is >= 1.0 as a smaller scale factor can result in blurry output.
6171 let scaling_factor = scale_factors.0.max(scale_factors.1).max(1.0);
6173 let device_pixel_scale = parent_device_pixel_scale * Scale::new(scaling_factor);
6174 (surface_spatial_node_index, device_pixel_scale)
6176 (parent_raster_node_index, parent_device_pixel_scale)
6179 let scale_factors = frame_context
6181 .get_relative_transform(surface_spatial_node_index, raster_spatial_node_index)
6184 // This inflation factor is to be applied to all primitives within the surface.
6185 // Only inflate if the caller hasn't already inflated the bounding rects for this filter.
6186 let mut inflation_factor = 0.0;
6187 if self.options.inflate_if_required {
6188 match composite_mode {
6189 PictureCompositeMode::Filter(Filter::Blur(width, height)) => {
6190 let blur_radius = f32::max(clamp_blur_radius(width, scale_factors), clamp_blur_radius(height, scale_factors));
6191 // The amount of extra space needed for primitives inside
6192 // this picture to ensure the visibility check is correct.
6193 inflation_factor = blur_radius * BLUR_SAMPLE_SCALE;
6195 PictureCompositeMode::SvgFilter(ref primitives, _) => {
6197 for primitive in primitives {
6198 if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
6199 max = f32::max(max, blur.width);
6200 max = f32::max(max, blur.height);
6203 inflation_factor = clamp_blur_radius(max, scale_factors) * BLUR_SAMPLE_SCALE;
6205 PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
6206 // TODO(gw): This is incorrect, since we don't consider the drop shadow
6207 // offset. However, fixing that is a larger task, so this is
6208 // an improvement on the current case (this at least works where
6209 // the offset of the drop-shadow is ~0, which is often true).
6211 // Can't use max_by_key here since f32 isn't Ord
6212 let mut max_blur_radius: f32 = 0.0;
6213 for shadow in shadows {
6214 max_blur_radius = max_blur_radius.max(shadow.blur_radius);
6217 inflation_factor = clamp_blur_radius(max_blur_radius, scale_factors) * BLUR_SAMPLE_SCALE;
6223 let surface = SurfaceInfo::new(
6224 surface_spatial_node_index,
6225 raster_spatial_node_index,
6227 frame_context.global_screen_world_rect,
6228 &frame_context.spatial_tree,
6233 self.raster_config = Some(RasterConfig {
6235 establishes_raster_root,
6236 surface_index: state.push_surface(surface),
6237 root_scaling_factor: 1.0,
6238 clipped_bounding_rect: WorldRect::zero(),
6242 Some(mem::replace(&mut self.prim_list, PrimitiveList::empty()))
6245 /// Called after updating child pictures during the initial
6246 /// picture traversal.
6249 prim_list: PrimitiveList,
6250 state: &mut PictureUpdateState,
6251 frame_context: &FrameBuildingContext,
6252 data_stores: &mut DataStores,
6254 // Restore the pictures list used during recursion.
6255 self.prim_list = prim_list;
6257 let surface = state.current_surface_mut();
6259 for cluster in &mut self.prim_list.clusters {
6260 cluster.flags.remove(ClusterFlags::IS_VISIBLE);
6262 // Skip the cluster if backface culled.
6263 if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) {
6264 // For in-preserve-3d primitives and pictures, the backface visibility is
6265 // evaluated relative to the containing block.
6266 if let Picture3DContext::In { ancestor_index, .. } = self.context_3d {
6267 let mut face = VisibleFace::Front;
6268 frame_context.spatial_tree.get_relative_transform_with_face(
6269 cluster.spatial_node_index,
6273 if face == VisibleFace::Back {
6279 // No point including this cluster if it can't be transformed
6280 let spatial_node = &frame_context
6282 .spatial_nodes[cluster.spatial_node_index.0 as usize];
6283 if !spatial_node.invertible {
6287 // Update any primitives/cluster bounding rects that can only be done
6288 // with information available during frame building.
6289 if cluster.flags.contains(ClusterFlags::IS_BACKDROP_FILTER) {
6290 let backdrop_to_world_mapper = SpaceMapper::new_with_target(
6291 ROOT_SPATIAL_NODE_INDEX,
6292 cluster.spatial_node_index,
6293 LayoutRect::max_rect(),
6294 frame_context.spatial_tree,
6297 for prim_instance in &mut self.prim_list.prim_instances[cluster.prim_range()] {
6298 match prim_instance.kind {
6299 PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
6300 // The actual size and clip rect of this primitive are determined by computing the bounding
6301 // box of the projected rect of the backdrop-filter element onto the backdrop.
6302 let prim_data = &mut data_stores.backdrop[data_handle];
6303 let spatial_node_index = prim_data.kind.spatial_node_index;
6305 // We cannot use the relative transform between the backdrop and the element because
6306 // that doesn't take into account any projection transforms that both spatial nodes are children of.
6307 // Instead, we first project from the element to the world space and get a flattened 2D bounding rect
6308 // in the screen space, we then map this rect from the world space to the backdrop space to get the
6309 // proper bounding box where the backdrop-filter needs to be processed.
6311 let prim_to_world_mapper = SpaceMapper::new_with_target(
6312 ROOT_SPATIAL_NODE_INDEX,
6314 LayoutRect::max_rect(),
6315 frame_context.spatial_tree,
6318 // First map to the screen and get a flattened rect
6319 let prim_rect = prim_to_world_mapper.map(&prim_data.kind.border_rect).unwrap_or_else(LayoutRect::zero);
6320 // Backwards project the flattened rect onto the backdrop
6321 let prim_rect = backdrop_to_world_mapper.unmap(&prim_rect).unwrap_or_else(LayoutRect::zero);
6323 // TODO(aosmond): Is this safe? Updating the primitive size during
6324 // frame building is usually problematic since scene building will cache
6325 // the primitive information in the GPU already.
6326 prim_data.common.prim_rect = prim_rect;
6327 prim_instance.clip_set.local_clip_rect = prim_rect;
6329 // Update the cluster bounding rect now that we have the backdrop rect.
6330 cluster.bounding_rect = cluster.bounding_rect.union(&prim_rect);
6333 panic!("BUG: unexpected deferred primitive kind for cluster updates");
6339 // Map the cluster bounding rect into the space of the surface, and
6340 // include it in the surface bounding rect.
6341 surface.map_local_to_surface.set_target_spatial_node(
6342 cluster.spatial_node_index,
6343 frame_context.spatial_tree,
6346 // Mark the cluster visible, since it passed the invertible and
6348 cluster.flags.insert(ClusterFlags::IS_VISIBLE);
6349 if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) {
6350 surface.rect = surface.rect.union(&cluster_rect);
6354 // If this picture establishes a surface, then map the surface bounding
6355 // rect into the parent surface coordinate space, and propagate that up
6357 if let Some(ref mut raster_config) = self.raster_config {
6358 let surface = state.current_surface_mut();
6359 // Inflate the local bounding rect if required by the filter effect.
6360 if self.options.inflate_if_required {
6361 surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.scale_factors);
6364 let mut surface_rect = surface.rect * Scale::new(1.0);
6366 // Pop this surface from the stack
6367 let surface_index = state.pop_surface();
6368 debug_assert_eq!(surface_index, raster_config.surface_index);
6370 // Set the estimated and precise local rects. The precise local rect
6371 // may be changed again during frame visibility.
6372 self.estimated_local_rect = surface_rect;
6373 self.precise_local_rect = surface_rect;
6375 // Drop shadows draw both a content and shadow rect, so need to expand the local
6376 // rect of any surfaces to be composited in parent surfaces correctly.
6377 match raster_config.composite_mode {
6378 PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
6379 for shadow in shadows {
6380 let shadow_rect = self.estimated_local_rect.translate(shadow.offset);
6381 surface_rect = surface_rect.union(&shadow_rect);
6387 // Propagate up to parent surface, now that we know this surface's static rect
6388 let parent_surface = state.current_surface_mut();
6389 parent_surface.map_local_to_surface.set_target_spatial_node(
6390 self.spatial_node_index,
6391 frame_context.spatial_tree,
6393 if let Some(parent_surface_rect) = parent_surface
6394 .map_local_to_surface
6397 parent_surface.rect = parent_surface.rect.union(&parent_surface_rect);
6402 pub fn prepare_for_render(
6404 frame_context: &FrameBuildingContext,
6405 frame_state: &mut FrameBuildingState,
6406 data_stores: &mut DataStores,
6408 let mut pic_state_for_children = self.take_state();
6410 if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
6411 self.resolve_split_planes(
6413 &mut frame_state.gpu_cache,
6414 &frame_context.spatial_tree,
6418 let raster_config = match self.raster_config {
6419 Some(ref mut raster_config) => raster_config,
6425 // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
6426 // to store the same type of data. The exception is the filter
6427 // with a ColorMatrix, which stores the color matrix here. It's
6428 // probably worth tidying this code up to be a bit more consistent.
6429 // Perhaps store the color matrix after the common data, even though
6430 // it's not used by that shader.
6432 match raster_config.composite_mode {
6433 PictureCompositeMode::TileCache { .. } => {}
6434 PictureCompositeMode::Filter(Filter::Blur(..)) => {}
6435 PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
6436 self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
6437 for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) {
6438 if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) {
6439 // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
6440 // [brush specific data]
6441 // [segment_rect, segment data]
6442 let shadow_rect = self.precise_local_rect.translate(shadow.offset);
6444 // ImageBrush colors
6445 request.push(shadow.color.premultiplied());
6446 request.push(PremultipliedColorF::WHITE);
6448 self.precise_local_rect.size.width,
6449 self.precise_local_rect.size.height,
6454 // segment rect / extra data
6455 request.push(shadow_rect);
6456 request.push([0.0, 0.0, 0.0, 0.0]);
6460 PictureCompositeMode::Filter(ref filter) => {
6462 Filter::ColorMatrix(ref m) => {
6463 if self.extra_gpu_data_handles.is_empty() {
6464 self.extra_gpu_data_handles.push(GpuCacheHandle::new());
6466 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
6468 request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
6472 Filter::Flood(ref color) => {
6473 if self.extra_gpu_data_handles.is_empty() {
6474 self.extra_gpu_data_handles.push(GpuCacheHandle::new());
6476 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
6477 request.push(color.to_array());
6483 PictureCompositeMode::ComponentTransferFilter(handle) => {
6484 let filter_data = &mut data_stores.filter_data[handle];
6485 filter_data.update(frame_state);
6487 PictureCompositeMode::MixBlend(..) |
6488 PictureCompositeMode::Blit(_) |
6489 PictureCompositeMode::SvgFilter(..) => {}
6496 // Calculate a single homogeneous screen-space UV for a picture.
6497 fn calculate_screen_uv(
6498 local_pos: &PicturePoint,
6499 transform: &PictureToRasterTransform,
6500 rendered_rect: &DeviceRect,
6501 device_pixel_scale: DevicePixelScale,
6502 ) -> DeviceHomogeneousVector {
6503 let raster_pos = transform.transform_point2d_homogeneous(*local_pos);
6505 DeviceHomogeneousVector::new(
6506 (raster_pos.x * device_pixel_scale.0 - rendered_rect.origin.x * raster_pos.w) / rendered_rect.size.width,
6507 (raster_pos.y * device_pixel_scale.0 - rendered_rect.origin.y * raster_pos.w) / rendered_rect.size.height,
6513 // Calculate a UV rect within an image based on the screen space
6514 // vertex positions of a picture.
6515 fn calculate_uv_rect_kind(
6516 pic_rect: &PictureRect,
6517 transform: &PictureToRasterTransform,
6518 rendered_rect: &DeviceRect,
6519 device_pixel_scale: DevicePixelScale,
6521 let top_left = calculate_screen_uv(
6528 let top_right = calculate_screen_uv(
6529 &pic_rect.top_right(),
6535 let bottom_left = calculate_screen_uv(
6536 &pic_rect.bottom_left(),
6542 let bottom_right = calculate_screen_uv(
6543 &pic_rect.bottom_right(),
6557 fn create_raster_mappers(
6558 surface_spatial_node_index: SpatialNodeIndex,
6559 raster_spatial_node_index: SpatialNodeIndex,
6560 world_rect: WorldRect,
6561 spatial_tree: &SpatialTree,
6562 ) -> (SpaceMapper<RasterPixel, WorldPixel>, SpaceMapper<PicturePixel, RasterPixel>) {
6563 let map_raster_to_world = SpaceMapper::new_with_target(
6564 ROOT_SPATIAL_NODE_INDEX,
6565 raster_spatial_node_index,
6570 let raster_bounds = map_raster_to_world.unmap(&world_rect)
6571 .unwrap_or_else(RasterRect::max_rect);
6573 let map_pic_to_raster = SpaceMapper::new_with_target(
6574 raster_spatial_node_index,
6575 surface_spatial_node_index,
6580 (map_raster_to_world, map_pic_to_raster)
6583 fn get_transform_key(
6584 spatial_node_index: SpatialNodeIndex,
6585 cache_spatial_node_index: SpatialNodeIndex,
6586 spatial_tree: &SpatialTree,
6588 // Note: this is the only place where we don't know beforehand if the tile-affecting
6589 // spatial node is below or above the current picture.
6590 let transform = if cache_spatial_node_index >= spatial_node_index {
6592 .get_relative_transform(
6593 cache_spatial_node_index,
6598 .get_relative_transform(
6600 cache_spatial_node_index,
6606 /// A key for storing primitive comparison results during tile dependency tests.
6607 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
6608 struct PrimitiveComparisonKey {
6609 prev_index: PrimitiveDependencyIndex,
6610 curr_index: PrimitiveDependencyIndex,
6613 /// Information stored an image dependency
6614 #[derive(Debug, Copy, Clone, PartialEq)]
6615 #[cfg_attr(feature = "capture", derive(Serialize))]
6616 #[cfg_attr(feature = "replay", derive(Deserialize))]
6617 pub struct ImageDependency {
6619 pub generation: ImageGeneration,
6622 impl ImageDependency {
6623 pub const INVALID: ImageDependency = ImageDependency {
6624 key: ImageKey::DUMMY,
6625 generation: ImageGeneration::INVALID,
6629 /// A helper struct to compare a primitive and all its sub-dependencies.
6630 struct PrimitiveComparer<'a> {
6631 clip_comparer: CompareHelper<'a, ItemUid>,
6632 transform_comparer: CompareHelper<'a, SpatialNodeKey>,
6633 image_comparer: CompareHelper<'a, ImageDependency>,
6634 opacity_comparer: CompareHelper<'a, OpacityBinding>,
6635 color_comparer: CompareHelper<'a, ColorBinding>,
6636 resource_cache: &'a ResourceCache,
6637 spatial_node_comparer: &'a mut SpatialNodeComparer,
6638 opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
6639 color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
6642 impl<'a> PrimitiveComparer<'a> {
6644 prev: &'a TileDescriptor,
6645 curr: &'a TileDescriptor,
6646 resource_cache: &'a ResourceCache,
6647 spatial_node_comparer: &'a mut SpatialNodeComparer,
6648 opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
6649 color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
6651 let clip_comparer = CompareHelper::new(
6656 let transform_comparer = CompareHelper::new(
6661 let image_comparer = CompareHelper::new(
6666 let opacity_comparer = CompareHelper::new(
6667 &prev.opacity_bindings,
6668 &curr.opacity_bindings,
6671 let color_comparer = CompareHelper::new(
6672 &prev.color_bindings,
6673 &curr.color_bindings,
6683 spatial_node_comparer,
6689 fn reset(&mut self) {
6690 self.clip_comparer.reset();
6691 self.transform_comparer.reset();
6692 self.image_comparer.reset();
6693 self.opacity_comparer.reset();
6694 self.color_comparer.reset();
6697 fn advance_prev(&mut self, prim: &PrimitiveDescriptor) {
6698 self.clip_comparer.advance_prev(prim.clip_dep_count);
6699 self.transform_comparer.advance_prev(prim.transform_dep_count);
6700 self.image_comparer.advance_prev(prim.image_dep_count);
6701 self.opacity_comparer.advance_prev(prim.opacity_binding_dep_count);
6702 self.color_comparer.advance_prev(prim.color_binding_dep_count);
6705 fn advance_curr(&mut self, prim: &PrimitiveDescriptor) {
6706 self.clip_comparer.advance_curr(prim.clip_dep_count);
6707 self.transform_comparer.advance_curr(prim.transform_dep_count);
6708 self.image_comparer.advance_curr(prim.image_dep_count);
6709 self.opacity_comparer.advance_curr(prim.opacity_binding_dep_count);
6710 self.color_comparer.advance_curr(prim.color_binding_dep_count);
6713 /// Check if two primitive descriptors are the same.
6716 prev: &PrimitiveDescriptor,
6717 curr: &PrimitiveDescriptor,
6718 opt_detail: Option<&mut PrimitiveCompareResultDetail>,
6719 ) -> PrimitiveCompareResult {
6720 let resource_cache = self.resource_cache;
6721 let spatial_node_comparer = &mut self.spatial_node_comparer;
6722 let opacity_bindings = self.opacity_bindings;
6723 let color_bindings = self.color_bindings;
6725 // Check equality of the PrimitiveDescriptor
6727 if let Some(detail) = opt_detail {
6728 *detail = PrimitiveCompareResultDetail::Descriptor{ old: *prev, new: *curr };
6730 return PrimitiveCompareResult::Descriptor;
6733 // Check if any of the clips this prim has are different.
6734 let mut clip_result = CompareHelperResult::Equal;
6735 if !self.clip_comparer.is_same(
6736 prev.clip_dep_count,
6737 curr.clip_dep_count,
6741 if opt_detail.is_some() { Some(&mut clip_result) } else { None }
6743 if let Some(detail) = opt_detail { *detail = PrimitiveCompareResultDetail::Clip{ detail: clip_result }; }
6744 return PrimitiveCompareResult::Clip;
6747 // Check if any of the transforms this prim has are different.
6748 let mut transform_result = CompareHelperResult::Equal;
6749 if !self.transform_comparer.is_same(
6750 prev.transform_dep_count,
6751 curr.transform_dep_count,
6753 spatial_node_comparer.are_transforms_equivalent(prev, curr)
6755 if opt_detail.is_some() { Some(&mut transform_result) } else { None },
6757 if let Some(detail) = opt_detail {
6758 *detail = PrimitiveCompareResultDetail::Transform{ detail: transform_result };
6760 return PrimitiveCompareResult::Transform;
6763 // Check if any of the images this prim has are different.
6764 let mut image_result = CompareHelperResult::Equal;
6765 if !self.image_comparer.is_same(
6766 prev.image_dep_count,
6767 curr.image_dep_count,
6770 resource_cache.get_image_generation(curr.key) == curr.generation
6772 if opt_detail.is_some() { Some(&mut image_result) } else { None },
6774 if let Some(detail) = opt_detail {
6775 *detail = PrimitiveCompareResultDetail::Image{ detail: image_result };
6777 return PrimitiveCompareResult::Image;
6780 // Check if any of the opacity bindings this prim has are different.
6781 let mut bind_result = CompareHelperResult::Equal;
6782 if !self.opacity_comparer.is_same(
6783 prev.opacity_binding_dep_count,
6784 curr.opacity_binding_dep_count,
6790 if let OpacityBinding::Binding(id) = curr {
6793 .map_or(true, |info| info.changed) {
6800 if opt_detail.is_some() { Some(&mut bind_result) } else { None },
6802 if let Some(detail) = opt_detail {
6803 *detail = PrimitiveCompareResultDetail::OpacityBinding{ detail: bind_result };
6805 return PrimitiveCompareResult::OpacityBinding;
6808 // Check if any of the color bindings this prim has are different.
6809 let mut bind_result = CompareHelperResult::Equal;
6810 if !self.color_comparer.is_same(
6811 prev.color_binding_dep_count,
6812 curr.color_binding_dep_count,
6818 if let ColorBinding::Binding(id) = curr {
6821 .map_or(true, |info| info.changed) {
6828 if opt_detail.is_some() { Some(&mut bind_result) } else { None },
6830 if let Some(detail) = opt_detail {
6831 *detail = PrimitiveCompareResultDetail::ColorBinding{ detail: bind_result };
6833 return PrimitiveCompareResult::ColorBinding;
6836 PrimitiveCompareResult::Equal
6840 /// Details for a node in a quadtree that tracks dirty rects for a tile.
6841 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
6842 #[cfg_attr(feature = "capture", derive(Serialize))]
6843 #[cfg_attr(feature = "replay", derive(Deserialize))]
6844 pub enum TileNodeKind {
6846 /// The index buffer of primitives that affected this tile previous frame
6847 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
6848 prev_indices: Vec<PrimitiveDependencyIndex>,
6849 /// The index buffer of primitives that affect this tile on this frame
6850 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
6851 curr_indices: Vec<PrimitiveDependencyIndex>,
6852 /// A bitset of which of the last 64 frames have been dirty for this leaf.
6853 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
6855 /// The number of frames since this node split or merged.
6856 #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
6857 frames_since_modified: usize,
6860 /// The four children of this node
6861 children: Vec<TileNode>,
6865 /// The kind of modification that a tile wants to do
6866 #[derive(Copy, Clone, PartialEq, Debug)]
6867 enum TileModification {
6872 /// A node in the dirty rect tracking quadtree.
6873 #[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
6874 #[cfg_attr(feature = "capture", derive(Serialize))]
6875 #[cfg_attr(feature = "replay", derive(Deserialize))]
6876 pub struct TileNode {
6877 /// Leaf or internal node
6878 pub kind: TileNodeKind,
6879 /// Rect of this node in the same space as the tile cache picture
6880 pub rect: PictureBox2D,
6884 /// Construct a new leaf node, with the given primitive dependency index buffer
6885 fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
6887 kind: TileNodeKind::Leaf {
6888 prev_indices: Vec::new(),
6891 frames_since_modified: 0,
6893 rect: PictureBox2D::zero(),
6897 /// Draw debug information about this tile node
6898 fn draw_debug_rects(
6900 pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
6902 local_valid_rect: PictureRect,
6903 scratch: &mut PrimitiveScratchBuffer,
6904 global_device_pixel_scale: DevicePixelScale,
6907 TileNodeKind::Leaf { dirty_tracker, .. } => {
6908 let color = if (dirty_tracker & 1) != 0 {
6910 } else if is_opaque {
6913 debug_colors::YELLOW
6916 if let Some(local_rect) = local_valid_rect.intersection(&self.rect.to_rect()) {
6917 let world_rect = pic_to_world_mapper
6920 let device_rect = world_rect * global_device_pixel_scale;
6922 let outer_color = color.scale_alpha(0.3);
6923 let inner_color = outer_color.scale_alpha(0.5);
6924 scratch.push_debug_rect(
6925 device_rect.inflate(-3.0, -3.0),
6931 TileNodeKind::Node { ref children, .. } => {
6932 for child in children.iter() {
6933 child.draw_debug_rects(
6934 pic_to_world_mapper,
6938 global_device_pixel_scale,
6945 /// Calculate the four child rects for a given node
6947 rect: &PictureBox2D,
6948 result: &mut [PictureBox2D; 4],
6952 let pc = p0 + rect.size() * 0.5;
6960 PicturePoint::new(pc.x, p0.y),
6961 PicturePoint::new(p1.x, pc.y),
6964 PicturePoint::new(p0.x, pc.y),
6965 PicturePoint::new(pc.x, p1.y),
6974 /// Called during pre_update, to clear the current dependencies
6982 TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => {
6983 // Swap current dependencies to be the previous frame
6984 mem::swap(prev_indices, curr_indices);
6985 curr_indices.clear();
6986 // Note that another frame has passed in the dirty bit trackers
6987 *dirty_tracker = *dirty_tracker << 1;
6988 *frames_since_modified += 1;
6990 TileNodeKind::Node { ref mut children, .. } => {
6991 let mut child_rects = [PictureBox2D::zero(); 4];
6992 TileNode::get_child_rects(&rect, &mut child_rects);
6993 assert_eq!(child_rects.len(), children.len());
6995 for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
7002 /// Add a primitive dependency to this node
7005 index: PrimitiveDependencyIndex,
7006 prim_rect: &PictureBox2D,
7009 TileNodeKind::Leaf { ref mut curr_indices, .. } => {
7010 curr_indices.push(index);
7012 TileNodeKind::Node { ref mut children, .. } => {
7013 for child in children.iter_mut() {
7014 if child.rect.intersects(prim_rect) {
7015 child.add_prim(index, prim_rect);
7022 /// Apply a merge or split operation to this tile, if desired
7023 fn maybe_merge_or_split(
7026 curr_prims: &[PrimitiveDescriptor],
7027 max_split_levels: i32,
7029 // Determine if this tile wants to split or merge
7030 let mut tile_mod = None;
7032 fn get_dirty_frames(
7034 frames_since_modified: usize,
7036 // Only consider splitting or merging at least 64 frames since we last changed
7037 if frames_since_modified > 64 {
7038 // Each bit in the tracker is a frame that was recently invalidated
7039 Some(dirty_tracker.count_ones())
7046 TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => {
7047 // Only consider splitting if the tree isn't too deep.
7048 if level < max_split_levels {
7049 if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
7050 // If the tile has invalidated > 50% of the recent number of frames, split.
7051 if dirty_frames > 32 {
7052 tile_mod = Some(TileModification::Split);
7057 TileNodeKind::Node { ref children, .. } => {
7058 // There's two conditions that cause a node to merge its children:
7059 // (1) If _all_ the child nodes are constantly invalidating, then we are wasting
7060 // CPU time tracking dependencies for each child, so merge them.
7061 // (2) If _none_ of the child nodes are recently invalid, then the page content
7062 // has probably changed, and we no longer need to track fine grained dependencies here.
7064 let mut static_count = 0;
7065 let mut changing_count = 0;
7067 for child in children {
7068 // Only consider merging nodes at the edge of the tree.
7069 if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind {
7070 if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
7071 if dirty_frames == 0 {
7072 // Hasn't been invalidated for some time
7074 } else if dirty_frames == 64 {
7075 // Is constantly being invalidated
7076 changing_count += 1;
7081 // Only merge if all the child tiles are in agreement. Otherwise, we have some
7082 // that are invalidating / static, and it's worthwhile tracking dependencies for
7083 // them individually.
7084 if static_count == 4 || changing_count == 4 {
7085 tile_mod = Some(TileModification::Merge);
7092 Some(TileModification::Split) => {
7093 // To split a node, take the current dependency index buffer for this node, and
7094 // split it into child index buffers.
7095 let curr_indices = match self.kind {
7096 TileNodeKind::Node { .. } => {
7097 unreachable!("bug - only leaves can split");
7099 TileNodeKind::Leaf { ref mut curr_indices, .. } => {
7104 let mut child_rects = [PictureBox2D::zero(); 4];
7105 TileNode::get_child_rects(&self.rect, &mut child_rects);
7107 let mut child_indices = [
7114 // Step through the index buffer, and add primitives to each of the children
7115 // that they intersect.
7116 for index in curr_indices {
7117 let prim = &curr_prims[index.0 as usize];
7118 for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) {
7119 if prim.prim_clip_box.intersects(child_rect) {
7120 indices.push(index);
7125 // Create the child nodes and switch from leaf -> node.
7126 let children = child_indices
7128 .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
7131 self.kind = TileNodeKind::Node {
7135 Some(TileModification::Merge) => {
7136 // Construct a merged index buffer by collecting the dependency index buffers
7137 // from each child, and merging them into a de-duplicated index buffer.
7138 let merged_indices = match self.kind {
7139 TileNodeKind::Node { ref mut children, .. } => {
7140 let mut merged_indices = Vec::new();
7142 for child in children.iter() {
7143 let child_indices = match child.kind {
7144 TileNodeKind::Leaf { ref curr_indices, .. } => {
7147 TileNodeKind::Node { .. } => {
7148 unreachable!("bug: child is not a leaf");
7151 merged_indices.extend_from_slice(child_indices);
7154 merged_indices.sort();
7155 merged_indices.dedup();
7159 TileNodeKind::Leaf { .. } => {
7160 unreachable!("bug - trying to merge a leaf");
7164 // Switch from a node to a leaf, with the combined index buffer
7165 self.kind = TileNodeKind::Leaf {
7166 prev_indices: Vec::new(),
7167 curr_indices: merged_indices,
7169 frames_since_modified: 0,
7173 // If this node didn't merge / split, then recurse into children
7174 // to see if they want to split / merge.
7175 if let TileNodeKind::Node { ref mut children, .. } = self.kind {
7176 for child in children.iter_mut() {
7177 child.maybe_merge_or_split(
7188 /// Update the dirty state of this node, building the overall dirty rect
7189 fn update_dirty_rects(
7191 prev_prims: &[PrimitiveDescriptor],
7192 curr_prims: &[PrimitiveDescriptor],
7193 prim_comparer: &mut PrimitiveComparer,
7194 dirty_rect: &mut PictureBox2D,
7195 compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
7196 invalidation_reason: &mut Option<InvalidationReason>,
7197 frame_context: &FrameVisibilityContext,
7200 TileNodeKind::Node { ref mut children, .. } => {
7201 for child in children.iter_mut() {
7202 child.update_dirty_rects(
7208 invalidation_reason,
7213 TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => {
7214 // If the index buffers are of different length, they must be different
7215 if prev_indices.len() == curr_indices.len() {
7216 let mut prev_i0 = 0;
7217 let mut prev_i1 = 0;
7218 prim_comparer.reset();
7220 // Walk each index buffer, comparing primitives
7221 for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) {
7222 let i0 = prev_index.0 as usize;
7223 let i1 = curr_index.0 as usize;
7225 // Advance the dependency arrays for each primitive (this handles
7226 // prims that may be skipped by these index buffers).
7227 for i in prev_i0 .. i0 {
7228 prim_comparer.advance_prev(&prev_prims[i]);
7230 for i in prev_i1 .. i1 {
7231 prim_comparer.advance_curr(&curr_prims[i]);
7234 // Compare the primitives, caching the result in a hash map
7235 // to save comparisons in other tree nodes.
7236 let key = PrimitiveComparisonKey {
7237 prev_index: *prev_index,
7238 curr_index: *curr_index,
7241 #[cfg(any(feature = "capture", feature = "replay"))]
7242 let mut compare_detail = PrimitiveCompareResultDetail::Equal;
7243 #[cfg(any(feature = "capture", feature = "replay"))]
7244 let prim_compare_result_detail =
7245 if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
7246 Some(&mut compare_detail)
7251 #[cfg(not(any(feature = "capture", feature = "replay")))]
7252 let compare_detail = PrimitiveCompareResultDetail::Equal;
7253 #[cfg(not(any(feature = "capture", feature = "replay")))]
7254 let prim_compare_result_detail = None;
7256 let prim_compare_result = *compare_cache
7258 .or_insert_with(|| {
7259 let prev = &prev_prims[i0];
7260 let curr = &curr_prims[i1];
7261 prim_comparer.compare_prim(prev, curr, prim_compare_result_detail)
7264 // If not the same, mark this node as dirty and update the dirty rect
7265 if prim_compare_result != PrimitiveCompareResult::Equal {
7266 if invalidation_reason.is_none() {
7267 *invalidation_reason = Some(InvalidationReason::Content {
7268 prim_compare_result,
7269 prim_compare_result_detail: Some(compare_detail)
7272 *dirty_rect = self.rect.union(dirty_rect);
7273 *dirty_tracker = *dirty_tracker | 1;
7281 if invalidation_reason.is_none() {
7282 // if and only if tile logging is enabled, do the expensive step of
7283 // converting indices back to ItemUids and allocating old and new vectors
7284 // to store them in.
7285 #[cfg(any(feature = "capture", feature = "replay"))]
7287 if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
7288 let old = prev_indices.iter().map( |i| prev_prims[i.0 as usize].prim_uid ).collect();
7289 let new = curr_indices.iter().map( |i| curr_prims[i.0 as usize].prim_uid ).collect();
7290 *invalidation_reason = Some(InvalidationReason::PrimCount {
7294 *invalidation_reason = Some(InvalidationReason::PrimCount {
7299 #[cfg(not(any(feature = "capture", feature = "replay")))]
7301 *invalidation_reason = Some(InvalidationReason::PrimCount {
7306 *dirty_rect = self.rect.union(dirty_rect);
7307 *dirty_tracker = *dirty_tracker | 1;
7314 impl CompositeState {
7315 // A helper function to destroy all native surfaces for a given list of tiles
7316 pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>(
7319 resource_cache: &mut ResourceCache,
7321 // Any old tiles that remain after the loop above are going to be dropped. For
7322 // simple composite mode, the texture cache handle will expire and be collected
7323 // by the texture cache. For native compositor mode, we need to explicitly
7324 // invoke a callback to the client to destroy that surface.
7325 if let CompositorKind::Native { .. } = self.compositor_kind {
7326 for tile in tiles_iter {
7327 // Only destroy native surfaces that have been allocated. It's
7328 // possible for display port tiles to be created that never
7329 // come on screen, and thus never get a native surface allocated.
7330 if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
7331 if let Some(id) = id.take() {
7332 resource_cache.destroy_compositor_tile(id);
7340 pub fn get_raster_rects(
7341 pic_rect: PictureRect,
7342 map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
7343 map_to_world: &SpaceMapper<RasterPixel, WorldPixel>,
7344 prim_bounding_rect: WorldRect,
7345 device_pixel_scale: DevicePixelScale,
7346 ) -> Option<(DeviceRect, DeviceRect)> {
7347 let unclipped_raster_rect = map_to_raster.map(&pic_rect)?;
7349 let unclipped = raster_rect_to_device_pixels(
7350 unclipped_raster_rect,
7354 let unclipped_world_rect = map_to_world.map(&unclipped_raster_rect)?;
7355 let clipped_world_rect = unclipped_world_rect.intersection(&prim_bounding_rect)?;
7357 // We don't have to be able to do the back-projection from world into raster.
7358 // Rendering only cares one way, so if that fails, we fall back to the full rect.
7359 let clipped_raster_rect = match map_to_world.unmap(&clipped_world_rect) {
7360 Some(rect) => rect.intersection(&unclipped_raster_rect)?,
7361 None => return Some((unclipped, unclipped)),
7364 let clipped = raster_rect_to_device_pixels(
7365 clipped_raster_rect,
7369 // Ensure that we won't try to allocate a zero-sized clip render task.
7370 if clipped.is_empty() {
7374 Some((clipped, unclipped))