Bug 1579235 - Part 2 - Refactor some of the tile map handling code. r=nical,Bert
authorGlenn Watson <gw@intuitionlibrary.com>
Thu, 13 Feb 2020 18:08:16 +0000
changeset 513808 0c0eb80974b03f6cc153616fe21c8dc229b25c3e
parent 513807 19c9be9294f4a813cd5e6224e094e1ac42c60139
child 513809 3ba58bc194382fc128e44027779cb85a763bcf0a
push id37122
push usercsabou@mozilla.com
push dateFri, 14 Feb 2020 10:05:11 +0000
treeherdermozilla-central@ccbbd26e4bec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical, Bert
bugs1579235
milestone75.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1579235 - Part 2 - Refactor some of the tile map handling code. r=nical,Bert Simplify some of the logic related to handling multiple compositor surfaces in future, specifically: * Only rebuild the tiles map when the tile rect changes. * Remove need for tiles_to_draw array. Differential Revision: https://phabricator.services.mozilla.com/D62694
gfx/wr/webrender/src/composite.rs
gfx/wr/webrender/src/picture.rs
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -299,18 +299,17 @@ impl CompositeState {
         tile_cache: &TileCacheInstance,
         device_clip_rect: DeviceRect,
         z_id: ZBufferId,
         global_device_pixel_scale: DevicePixelScale,
         resource_cache: &ResourceCache,
     ) {
         let mut visible_tile_count = 0;
 
-        for key in &tile_cache.tiles_to_draw {
-            let tile = &tile_cache.tiles[key];
+        for tile in tile_cache.tiles.values() {
             if !tile.is_visible {
                 // This can occur when a tile is found to be occluded during frame building.
                 continue;
             }
 
             visible_tile_count += 1;
 
             let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round();
@@ -330,18 +329,18 @@ impl CompositeState {
                         tile.is_opaque || tile_cache.is_opaque(),
                     )
                 }
             };
 
             let tile_id = tile_cache.native_surface_id.map(|surface_id| {
                 NativeTileId {
                     surface_id,
-                    x: key.x,
-                    y: key.y,
+                    x: tile.tile_offset.x,
+                    y: tile.tile_offset.y,
                 }
             });
 
             let tile = CompositeTile {
                 surface,
                 rect: device_rect,
                 valid_rect: tile.device_valid_rect.translate(-device_rect.origin.to_vector()),
                 dirty_rect: tile.device_dirty_rect.translate(-device_rect.origin.to_vector()),
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -222,17 +222,17 @@ impl<Src, Dst> From<CoordinateSpaceMappi
 struct PictureInfo {
     /// The spatial node for this picture.
     _spatial_node_index: SpatialNodeIndex,
 }
 
 /// Picture-caching state to keep between scenes.
 pub struct PictureCacheState {
     /// The tiles retained by this picture cache.
-    pub tiles: FastHashMap<TileOffset, Tile>,
+    pub tiles: FastHashMap<TileOffset, Box<Tile>>,
     /// State of the spatial nodes from previous frame
     spatial_nodes: FastHashMap<SpatialNodeIndex, SpatialNodeDependency>,
     /// State of opacity bindings from previous frame
     opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     /// The current transform of the picture cache root spatial node
     root_transform: TransformKey,
     /// The current tile size in device pixels
     current_tile_size: DeviceIntSize,
@@ -240,17 +240,16 @@ pub struct PictureCacheState {
     allocations: PictureCacheRecycledAllocations,
     /// Currently allocated native compositor surface for this picture cache.
     pub native_surface_id: Option<NativeSurfaceId>,
     /// True if the entire picture cache is opaque.
     is_opaque: bool,
 }
 
 pub struct PictureCacheRecycledAllocations {
-    old_tiles: FastHashMap<TileOffset, Tile>,
     old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
 }
 
 /// Stores a list of cached picture tiles that are retained
 /// between new scenes.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct RetainedTiles {
@@ -410,16 +409,19 @@ struct TilePreUpdateContext {
     /// require invalidation of all tiles.
     fract_offset: PictureVector2D,
 
     /// The optional background color of the picture cache instance
     background_color: Option<ColorF>,
 
     /// The visible part of the screen in world coords.
     global_screen_world_rect: WorldRect,
+
+    /// Current size of tiles in picture units.
+    tile_size: PictureSize,
 }
 
 // Immutable context passed to picture cache tiles during post_update
 struct TilePostUpdateContext<'a> {
     /// Maps from picture cache coords -> world space coords.
     pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
 
     /// Global scale factor from world -> device pixels.
@@ -738,16 +740,18 @@ pub struct TileCacheInstanceSerializer {
     pub slice: usize,
     pub tiles: FastHashMap<TileOffset, TileSerializer>,
     pub background_color: Option<ColorF>,
     pub fract_offset: PictureVector2D,
 }
 
 /// Information about a cached tile.
 pub struct Tile {
+    /// The grid position of this tile within the picture cache
+    pub tile_offset: TileOffset,
     /// The current world rect of this tile.
     pub world_tile_rect: WorldRect,
     /// The current local rect of this tile.
     pub local_tile_rect: PictureRect,
     /// The picture space dirty rect for this tile.
     local_dirty_rect: PictureRect,
     /// The device space dirty rect for this tile.
     /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
@@ -784,20 +788,21 @@ pub struct Tile {
     /// The last rendered background color on this tile.
     background_color: Option<ColorF>,
     /// The first reason the tile was invalidated this frame.
     invalidation_reason: Option<InvalidationReason>,
 }
 
 impl Tile {
     /// Construct a new, invalid tile.
-    fn new(
-        id: TileId,
-    ) -> Self {
+    fn new(tile_offset: TileOffset) -> Self {
+        let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
+
         Tile {
+            tile_offset,
             local_tile_rect: PictureRect::zero(),
             world_tile_rect: WorldRect::zero(),
             device_valid_rect: DeviceRect::zero(),
             local_dirty_rect: PictureRect::zero(),
             device_dirty_rect: DeviceRect::zero(),
             surface: None,
             current_descriptor: TileDescriptor::new(),
             prev_descriptor: TileDescriptor::new(),
@@ -908,20 +913,28 @@ impl Tile {
             self.invalidation_reason = Some(reason);
         }
     }
 
     /// Called during pre_update of a tile cache instance. Allows the
     /// tile to setup state before primitive dependency calculations.
     fn pre_update(
         &mut self,
-        local_tile_rect: PictureRect,
         ctx: &TilePreUpdateContext,
     ) {
-        self.local_tile_rect = local_tile_rect;
+        // Ensure each tile is offset by the appropriate amount from the
+        // origin, such that the content origin will be a whole number and
+        // the snapping will be consistent.
+        self.local_tile_rect = PictureRect::new(
+            PicturePoint::new(
+                self.tile_offset.x as f32 * ctx.tile_size.width + ctx.fract_offset.x,
+                self.tile_offset.y as f32 * ctx.tile_size.height + ctx.fract_offset.y,
+            ),
+            ctx.tile_size,
+        );
         self.invalidation_reason  = None;
 
         self.world_tile_rect = ctx.pic_to_world_mapper
             .map(&self.local_tile_rect)
             .expect("bug: map local tile rect");
 
         // Check if this tile is currently on screen.
         self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
@@ -953,17 +966,17 @@ impl Tile {
 
         // Clear any dependencies so that when we rebuild them we
         // can compare if the tile has the same content.
         mem::swap(
             &mut self.current_descriptor,
             &mut self.prev_descriptor,
         );
         self.current_descriptor.clear();
-        self.root.clear(local_tile_rect);
+        self.root.clear(self.local_tile_rect);
     }
 
     /// Add dependencies for a given primitive to this tile.
     fn add_prim_dependency(
         &mut self,
         info: &PrimitiveDependencyInfo,
     ) {
         // If this tile isn't currently visible, we don't want to update the dependencies
@@ -1088,16 +1101,17 @@ impl Tile {
             // be composited. If the tile subsequently gets new primitives added to it, the
             // surface will be re-allocated when it's added to the composite draw list.
             if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
                 if let Some(id) = id.take() {
                     state.resource_cache.destroy_compositor_tile(id);
                 }
             }
 
+            self.is_visible = false;
             return false;
         }
 
         let world_valid_rect = ctx.pic_to_world_mapper
             .map(&self.current_descriptor.local_valid_rect)
             .expect("bug: map local valid rect");
 
         // The device rect is guaranteed to be aligned on a device pixel - the round
@@ -1936,19 +1950,17 @@ pub struct TileCacheInstance {
     pub slice: usize,
     /// Propagated information about the slice
     pub slice_flags: SliceFlags,
     /// The currently selected tile size to use for this cache
     pub current_tile_size: DeviceIntSize,
     /// The positioning node for this tile cache.
     pub spatial_node_index: SpatialNodeIndex,
     /// Hash of tiles present in this picture.
-    pub tiles: FastHashMap<TileOffset, Tile>,
-    /// Switch back and forth between old and new tiles hashmaps to avoid re-allocating.
-    old_tiles: FastHashMap<TileOffset, Tile>,
+    pub tiles: FastHashMap<TileOffset, Box<Tile>>,
     /// A helper struct to map local rects into surface coords.
     map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
     /// A helper struct to map child picture rects into picture cache surface coords.
     map_child_pic_to_surface: SpaceMapper<PicturePixel, PicturePixel>,
     /// List of opacity bindings, with some extra information
     /// about whether they changed since last frame.
     opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating.
@@ -1972,18 +1984,16 @@ pub struct TileCacheInstance {
     /// Pre-calculated versions of the tile_rect above, used to speed up the
     /// calculations in get_tile_coords_for_rect.
     tile_bounds_p0: TileOffset,
     tile_bounds_p1: TileOffset,
     /// Local rect (unclipped) of the picture this cache covers.
     pub local_rect: PictureRect,
     /// The local clip rect, from the shared clips of this picture.
     local_clip_rect: PictureRect,
-    /// A list of tiles that are valid and visible, which should be drawn to the main scene.
-    pub tiles_to_draw: Vec<TileOffset>,
     /// The surface index that this tile cache will be drawn into.
     surface_index: SurfaceIndex,
     /// The background color from the renderer. If this is set opaque, we know it's
     /// fine to clear the tiles to this and allow subpixel text on the first slice.
     pub background_color: Option<ColorF>,
     /// Information about the calculated backdrop content of this cache.
     backdrop: BackdropInfo,
     /// The allowed subpixel mode for this surface, which depends on the detected
@@ -2032,17 +2042,16 @@ impl TileCacheInstance {
         shared_clips: Vec<ClipDataHandle>,
         shared_clip_chain: ClipChainId,
     ) -> Self {
         TileCacheInstance {
             slice,
             slice_flags,
             spatial_node_index,
             tiles: FastHashMap::default(),
-            old_tiles: FastHashMap::default(),
             map_local_to_surface: SpaceMapper::new(
                 ROOT_SPATIAL_NODE_INDEX,
                 PictureRect::zero(),
             ),
             map_child_pic_to_surface: SpaceMapper::new(
                 ROOT_SPATIAL_NODE_INDEX,
                 PictureRect::zero(),
             ),
@@ -2053,17 +2062,16 @@ impl TileCacheInstance {
             used_spatial_nodes: FastHashSet::default(),
             dirty_region: DirtyRegion::new(),
             tile_size: PictureSize::zero(),
             tile_rect: TileRect::zero(),
             tile_bounds_p0: TileOffset::zero(),
             tile_bounds_p1: TileOffset::zero(),
             local_rect: PictureRect::zero(),
             local_clip_rect: PictureRect::zero(),
-            tiles_to_draw: Vec::new(),
             surface_index: SurfaceIndex(0),
             background_color,
             backdrop: BackdropInfo::empty(),
             subpixel_mode: SubpixelMode::Allow,
             root_transform: TransformKey::Local,
             shared_clips,
             shared_clip_chain,
             current_tile_size: DeviceIntSize::zero(),
@@ -2207,21 +2215,16 @@ impl TileCacheInstance {
                         *dest = src;
                     } else {
                         dest.clear();
                         dest.reserve(ideal_len);
                     }
                 }
             }
             recycle_map(
-                self.tiles.len(),
-                &mut self.old_tiles,
-                prev_state.allocations.old_tiles,
-            );
-            recycle_map(
                 self.opacity_bindings.len(),
                 &mut self.old_opacity_bindings,
                 prev_state.allocations.old_opacity_bindings,
             );
             recycle_map(
                 prev_state.allocations.compare_cache.len(),
                 &mut self.compare_cache,
                 prev_state.allocations.compare_cache,
@@ -2355,98 +2358,90 @@ impl TileCacheInstance {
         let x0 = (p0.x / local_tile_rect.size.width).floor() as i32;
         let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32;
 
         let y0 = (p0.y / local_tile_rect.size.height).floor() as i32;
         let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32;
 
         let x_tiles = x1 - x0;
         let y_tiles = y1 - y0;
-        self.tile_rect = TileRect::new(
+        let new_tile_rect = TileRect::new(
             TileOffset::new(x0, y0),
             TileSize::new(x_tiles, y_tiles),
         );
+
+        // Rebuild the tile grid if the picture cache rect has changed.
+        if new_tile_rect != self.tile_rect {
+            let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
+            self.tiles.reserve(new_tile_rect.size.area() as usize);
+
+            for y in y0 .. y1 {
+                for x in x0 .. x1 {
+                    let key = TileOffset::new(x, y);
+                    let tile = old_tiles
+                        .remove(&key)
+                        .unwrap_or_else(|| {
+                            Box::new(Tile::new(key))
+                        });
+                    self.tiles.insert(key, tile);
+                }
+            }
+
+            // When old tiles that remain after the loop, dirty rects are not valid.
+            if !old_tiles.is_empty() {
+                frame_state.composite_state.dirty_rects_are_valid = false;
+            }
+
+            // Any old tiles that remain after the loop above are going to be dropped. For
+            // simple composite mode, the texture cache handle will expire and be collected
+            // by the texture cache. For native compositor mode, we need to explicitly
+            // invoke a callback to the client to destroy that surface.
+            frame_state.composite_state.destroy_native_tiles(
+                old_tiles.values_mut(),
+                frame_state.resource_cache,
+            );
+        }
+
         // This is duplicated information from tile_rect, but cached here to avoid
         // redundant calculations during get_tile_coords_for_rect
         self.tile_bounds_p0 = TileOffset::new(x0, y0);
         self.tile_bounds_p1 = TileOffset::new(x1, y1);
+        self.tile_rect = new_tile_rect;
 
         let mut world_culling_rect = WorldRect::zero();
 
-        mem::swap(&mut self.tiles, &mut self.old_tiles);
-
         let ctx = TilePreUpdateContext {
             pic_to_world_mapper,
             fract_offset: self.fract_offset,
             background_color: self.background_color,
             global_screen_world_rect: frame_context.global_screen_world_rect,
+            tile_size: self.tile_size,
         };
 
-        self.tiles.clear();
-        for y in y0 .. y1 {
-            for x in x0 .. x1 {
-                let key = TileOffset::new(x, y);
-
-                let mut tile = self.old_tiles
-                    .remove(&key)
-                    .unwrap_or_else(|| {
-                        let next_id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
-                        Tile::new(next_id)
-                    });
-
-                // Ensure each tile is offset by the appropriate amount from the
-                // origin, such that the content origin will be a whole number and
-                // the snapping will be consistent.
-                let rect = PictureRect::new(
-                    PicturePoint::new(
-                        x as f32 * self.tile_size.width + self.fract_offset.x,
-                        y as f32 * self.tile_size.height + self.fract_offset.y,
-                    ),
-                    self.tile_size,
-                );
-
-                tile.pre_update(
-                    rect,
-                    &ctx,
-                );
-
-                // Only include the tiles that are currently in view into the world culling
-                // rect. This is a very important optimization for a couple of reasons:
-                // (1) Primitives that intersect with tiles in the grid that are not currently
-                //     visible can be skipped from primitive preparation, clip chain building
-                //     and tile dependency updates.
-                // (2) When we need to allocate an off-screen surface for a child picture (for
-                //     example a CSS filter) we clip the size of the GPU surface to the world
-                //     culling rect below (to ensure we draw enough of it to be sampled by any
-                //     tiles that reference it). Making the world culling rect only affected
-                //     by visible tiles (rather than the entire virtual tile display port) can
-                //     result in allocating _much_ smaller GPU surfaces for cases where the
-                //     true off-screen surface size is very large.
-                if tile.is_visible {
-                    world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
-                }
-
-                self.tiles.insert(key, tile);
+        // Pre-update each tile
+        for tile in self.tiles.values_mut() {
+            tile.pre_update(&ctx);
+
+            // Only include the tiles that are currently in view into the world culling
+            // rect. This is a very important optimization for a couple of reasons:
+            // (1) Primitives that intersect with tiles in the grid that are not currently
+            //     visible can be skipped from primitive preparation, clip chain building
+            //     and tile dependency updates.
+            // (2) When we need to allocate an off-screen surface for a child picture (for
+            //     example a CSS filter) we clip the size of the GPU surface to the world
+            //     culling rect below (to ensure we draw enough of it to be sampled by any
+            //     tiles that reference it). Making the world culling rect only affected
+            //     by visible tiles (rather than the entire virtual tile display port) can
+            //     result in allocating _much_ smaller GPU surfaces for cases where the
+            //     true off-screen surface size is very large.
+            if tile.is_visible {
+                world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
             }
         }
 
-        // When old tiles that remain after the loop, dirty rects are not valid.
-        if !self.old_tiles.is_empty() {
-            frame_state.composite_state.dirty_rects_are_valid = false;
-        }
-
-        // Any old tiles that remain after the loop above are going to be dropped. For
-        // simple composite mode, the texture cache handle will expire and be collected
-        // by the texture cache. For native compositor mode, we need to explicitly
-        // invoke a callback to the client to destroy that surface.
-        frame_state.composite_state.destroy_native_tiles(
-            self.old_tiles.values_mut(),
-            frame_state.resource_cache,
-        );
-
         // If compositor mode is changed, need to drop all incompatible tiles.
         match (frame_context.config.compositor_kind, self.native_surface_id) {
             (CompositorKind::Draw { .. }, Some(_)) => {
                 frame_state.composite_state.destroy_native_tiles(
                     self.tiles.values_mut(),
                     frame_state.resource_cache,
                 );
                 for tile in self.tiles.values_mut() {
@@ -2816,17 +2811,16 @@ impl TileCacheInstance {
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
         frame_context: &FrameVisibilityContext,
         frame_state: &mut FrameVisibilityState,
     ) {
-        self.tiles_to_draw.clear();
         self.dirty_region.clear();
 
         // Register the opaque region of this tile cache as an occluder, which
         // is used later in the frame to occlude other tiles.
         if self.backdrop.rect.is_well_formed_and_nonempty() {
             let backdrop_rect = self.backdrop.rect
                 .intersection(&self.local_rect)
                 .and_then(|r| {
@@ -2924,19 +2918,18 @@ impl TileCacheInstance {
             resource_cache: frame_state.resource_cache,
             composite_state: frame_state.composite_state,
             compare_cache: &mut self.compare_cache,
         };
 
         // Step through each tile and invalidate if the dependencies have changed. Determine
         // the current opacity setting and whether it's changed.
         let mut tile_cache_is_opaque = true;
-        for (key, tile) in self.tiles.iter_mut() {
+        for tile in self.tiles.values_mut() {
             if tile.post_update(&ctx, &mut state, frame_context) {
-                self.tiles_to_draw.push(*key);
                 tile_cache_is_opaque &= tile.is_opaque;
             }
         }
 
         // If opacity changed, the native compositor surface and all tiles get invalidated.
         // (this does nothing if not using native compositor mode).
         // TODO(gw): This property probably changes very rarely, so it is OK to invalidate
         //           everything in this case. If it turns out that this isn't true, we could
@@ -3800,17 +3793,16 @@ impl PicturePrimitive {
                         tiles: tile_cache.tiles,
                         spatial_nodes: tile_cache.spatial_nodes,
                         opacity_bindings: tile_cache.opacity_bindings,
                         root_transform: tile_cache.root_transform,
                         current_tile_size: tile_cache.current_tile_size,
                         native_surface_id: tile_cache.native_surface_id.take(),
                         is_opaque: tile_cache.is_opaque,
                         allocations: PictureCacheRecycledAllocations {
-                            old_tiles: tile_cache.old_tiles,
                             old_opacity_bindings: tile_cache.old_opacity_bindings,
                             compare_cache: tile_cache.compare_cache,
                         },
                     },
                 );
             }
         }
     }
@@ -4225,18 +4217,20 @@ impl PicturePrimitive {
 
                         // Get the overall world space rect of the picture cache. Used to clip
                         // the tile rects below for occlusion testing to the relevant area.
                         let world_clip_rect = map_pic_to_world
                             .map(&tile_cache.local_clip_rect)
                             .expect("bug: unable to map clip rect");
                         let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
 
-                        for key in &tile_cache.tiles_to_draw {
-                            let tile = tile_cache.tiles.get_mut(key).expect("bug: no tile found!");
+                        for tile in tile_cache.tiles.values_mut() {
+                            if !tile.is_visible {
+                                continue;
+                            }
 
                             // Get the world space rect that this tile will actually occupy on screem
                             let device_draw_rect = match device_clip_rect.intersection(&tile.device_valid_rect) {
                                 Some(rect) => rect,
                                 None => {
                                     tile.is_visible = false;
                                     continue;
                                 }
@@ -4362,18 +4356,18 @@ impl PicturePrimitive {
                                                     );
 
                                                 tile_cache.native_surface_id = Some(surface_id);
                                             }
 
                                             // Create the tile identifier and allocate it.
                                             let tile_id = NativeTileId {
                                                 surface_id: tile_cache.native_surface_id.unwrap(),
-                                                x: key.x,
-                                                y: key.y,
+                                                x: tile.tile_offset.x,
+                                                y: tile.tile_offset.y,
                                             };
 
                                             frame_state.resource_cache.create_compositor_tile(tile_id);
 
                                             *id = Some(tile_id);
                                         }
                                     }
                                 }
@@ -5950,17 +5944,17 @@ impl TileNode {
                 }
             }
         }
     }
 }
 
 impl CompositeState {
     // A helper function to destroy all native surfaces for a given list of tiles
-    pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Tile>>(
+    pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>(
         &mut self,
         tiles_iter: I,
         resource_cache: &mut ResourceCache,
     ) {
         // Any old tiles that remain after the loop above are going to be dropped. For
         // simple composite mode, the texture cache handle will expire and be collected
         // by the texture cache. For native compositor mode, we need to explicitly
         // invoke a callback to the client to destroy that surface.