Backed out changeset c61a5f1a655c (bug 1522555) for wrench bustages in reftests/border/border-suite-2.png CLOSED TREE
authorshindli <shindli@mozilla.com>
Thu, 24 Jan 2019 20:54:01 +0200
changeset 515325 ee1dce651a922e79f0296dc23375a32aa3a5465b
parent 515324 21462a8e201840d0d529b313ef44df1236ffe529
child 515326 19cdfa011f088a26f2b6af65b57c19c57215914b
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1522555
milestone66.0a1
backs outc61a5f1a655c186a6f187f5d2cf9f55846331b5b
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
Backed out changeset c61a5f1a655c (bug 1522555) for wrench bustages in reftests/border/border-suite-2.png CLOSED TREE
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/surface.rs
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -385,16 +385,17 @@ impl<'a> DisplayListFlattener<'a> {
             Picture3DContext::Out,
             self.scene.root_pipeline_id.unwrap(),
             None,
             true,
             RasterSpace::Screen,
             prim_list,
             main_scroll_root,
             LayoutRect::max_rect(),
+            &self.clip_store,
             Some(tile_cache),
         ));
 
         let instance = PrimitiveInstance::new(
             LayoutPoint::zero(),
             LayoutRect::max_rect(),
             PrimitiveInstanceKind::Picture {
                 data_handle: pic_data_handle,
@@ -1208,16 +1209,17 @@ impl<'a> DisplayListFlattener<'a> {
         // an intermediate surface for plane splitting purposes.
         let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
             Some(sc) => {
                 // Cut the sequence of flat children before starting a child stacking context,
                 // so that the relative order between them and our current SC is preserved.
                 let extra_instance = sc.cut_flat_item_sequence(
                     &mut self.prim_store,
                     &mut self.interners,
+                    &self.clip_store,
                 );
                 (sc.is_3d(), extra_instance)
             },
             None => (false, None),
         };
 
         if let Some(instance) = extra_3d_instance {
             self.add_primitive_instance_to_3d_root(instance);
@@ -1358,16 +1360,17 @@ impl<'a> DisplayListFlattener<'a> {
                 true,
                 stacking_context.requested_raster_space,
                 PrimitiveList::new(
                     stacking_context.primitives,
                     &self.interners,
                 ),
                 stacking_context.spatial_node_index,
                 max_clip,
+                &self.clip_store,
                 None,
             ))
         );
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
 
         let mut current_pic_index = leaf_pic_index;
@@ -1404,16 +1407,17 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         prims,
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
+                    &self.clip_store,
                     None,
                 ))
             );
 
             cur_instance = create_prim_instance(
                 current_pic_index,
                 PictureCompositeKey::Identity,
                 stacking_context.is_backface_visible,
@@ -1438,16 +1442,17 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
+                    &self.clip_store,
                     None,
                 ))
             );
 
             current_pic_index = filter_pic_index;
             cur_instance = create_prim_instance(
                 current_pic_index,
                 composite_mode.into(),
@@ -1480,16 +1485,17 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
                     max_clip,
+                    &self.clip_store,
                     None,
                 ))
             );
 
             current_pic_index = blend_pic_index;
             cur_instance = create_prim_instance(
                 blend_pic_index,
                 composite_mode.into(),
@@ -1815,16 +1821,17 @@ impl<'a> DisplayListFlattener<'a> {
                                 is_passthrough,
                                 raster_space,
                                 PrimitiveList::new(
                                     prims,
                                     &self.interners,
                                 ),
                                 pending_shadow.clip_and_scroll.spatial_node_index,
                                 max_clip,
+                                &self.clip_store,
                                 None,
                             ))
                         );
 
                         let shadow_pic_key = PictureKey::new(
                             true,
                             LayoutSize::zero(),
                             Picture { composite_mode_key },
@@ -2558,16 +2565,17 @@ impl FlattenedStackingContext {
     }
 
     /// For a Preserve3D context, cut the sequence of the immediate flat children
     /// recorded so far and generate a picture from them.
     pub fn cut_flat_item_sequence(
         &mut self,
         prim_store: &mut PrimitiveStore,
         interners: &mut Interners,
+        clip_store: &ClipStore,
     ) -> Option<PrimitiveInstance> {
         if !self.is_3d() || self.primitives.is_empty() {
             return None
         }
         let flat_items_context_3d = match self.context_3d {
             Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
                 root_data: None,
                 ancestor_index,
@@ -2585,16 +2593,17 @@ impl FlattenedStackingContext {
                 true,
                 self.requested_raster_space,
                 PrimitiveList::new(
                     mem::replace(&mut self.primitives, Vec::new()),
                     interners,
                 ),
                 self.spatial_node_index,
                 LayoutRect::max_rect(),
+                clip_store,
                 None,
             ))
         );
 
         let prim_instance = create_prim_instance(
             pic_index,
             PictureCompositeKey::Identity,
             self.is_backface_visible,
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -110,16 +110,17 @@ mod render_task;
 mod renderer;
 mod resource_cache;
 mod scene;
 mod scene_builder;
 mod segment;
 mod shade;
 mod spatial_node;
 mod storage;
+mod surface;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
 mod shader_source {
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
 }
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
 use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode, LayoutSize};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
 use api::{DebugFlags, DeviceVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip::{ClipChainId, ClipChainNode, ClipItem};
+use clip::{ClipStore, ClipChainId, ClipChainNode, ClipItem};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 use debug_colors;
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use intern::ItemUid;
 use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
@@ -27,16 +27,17 @@ use prim_store::{OpacityBindingStorage, 
 use print_tree::PrintTreePrinter;
 use render_backend::DataStores;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::Interners;
 use smallvec::SmallVec;
+use surface::{SurfaceDescriptor};
 use std::{mem, u16};
 use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
 use texture_cache::{Eviction, TextureCacheHandle};
 use tiling::RenderTargetKind;
 use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
@@ -1953,16 +1954,19 @@ pub struct PicturePrimitive {
 
     /// The local rect of this picture. It is built
     /// dynamically during the first picture traversal.
     pub local_rect: LayoutRect,
 
     /// Local clip rect for this picture.
     pub local_clip_rect: LayoutRect,
 
+    /// A descriptor for this surface that can be used as a cache key.
+    surface_desc: Option<SurfaceDescriptor>,
+
     pub gpu_location: GpuCacheHandle,
 
     /// If Some(..) the tile cache that is associated with this picture.
     pub tile_cache: Option<TileCache>,
 }
 
 impl PicturePrimitive {
     pub fn print<T: PrintTreePrinter>(
@@ -2042,19 +2046,43 @@ impl PicturePrimitive {
         context_3d: Picture3DContext<OrderedPictureChild>,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
         prim_list: PrimitiveList,
         spatial_node_index: SpatialNodeIndex,
         local_clip_rect: LayoutRect,
+        clip_store: &ClipStore,
         tile_cache: Option<TileCache>,
     ) -> Self {
+        // For now, only create a cache descriptor for blur filters (which
+        // includes text shadows). We can incrementally expand this to
+        // handle more composite modes.
+        let create_cache_descriptor = match requested_composite_mode {
+            Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                blur_radius > 0.0
+            }
+            Some(_) | None => {
+                false
+            }
+        };
+
+        let surface_desc = if create_cache_descriptor {
+            SurfaceDescriptor::new(
+                &prim_list.prim_instances,
+                spatial_node_index,
+                clip_store,
+            )
+        } else {
+            None
+        };
+
         PicturePrimitive {
+            surface_desc,
             prim_list,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             context_3d,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
@@ -2358,16 +2386,22 @@ impl PicturePrimitive {
             mode => mode,
         };
 
         if let Some(composite_mode) = actual_composite_mode {
             // Retrieve the positioning node information for the parent surface.
             let parent_raster_spatial_node_index = state.current_surface().raster_spatial_node_index;
             let surface_spatial_node_index = self.spatial_node_index;
 
+            // TODO(gw): For now, we always raster in screen space. Soon,
+            //           we will be able to respect the requested raster
+            //           space, and/or override the requested raster root
+            //           if it makes sense to.
+            let raster_space = RasterSpace::Screen;
+
             let inflation_factor = match composite_mode {
                 PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
                     // The amount of extra space needed for primitives inside
                     // this picture to ensure the visibility check is correct.
                     BLUR_SAMPLE_SCALE * blur_radius
                 }
                 _ => {
                     0.0
@@ -2405,16 +2439,27 @@ impl PicturePrimitive {
                     surface_spatial_node_index,
                     parent_raster_spatial_node_index,
                     inflation_factor,
                     frame_context.screen_world_rect,
                     &frame_context.clip_scroll_tree,
                 );
             };
 
+            // If we have a cache key / descriptor for this surface,
+            // update any transforms it cares about.
+            if let Some(ref mut surface_desc) = self.surface_desc {
+                surface_desc.update(
+                    surface_spatial_node_index,
+                    surface.raster_spatial_node_index,
+                    frame_context.clip_scroll_tree,
+                    raster_space,
+                );
+            }
+
             self.raster_config = Some(RasterConfig {
                 composite_mode,
                 establishes_raster_root: surface_spatial_node_index == surface.raster_spatial_node_index,
                 surface_index: state.push_surface(surface),
             });
         }
 
         Some(mem::replace(&mut self.prim_list.pictures, SmallVec::new()))
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -23,16 +23,17 @@ use internal_types::{CacheTextureId, Fas
 use pathfinder_partitioner::mesh::Mesh;
 use prim_store::PictureIndex;
 use prim_store::image::ImageCacheKey;
 use prim_store::line_dec::LineDecorationCacheKey;
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
+use surface::SurfaceCacheKey;
 use std::{ops, mem, usize, f32, i32, u32};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
 #[cfg(feature = "pathfinder")]
 use webrender_api::DevicePixel;
 
 const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000;
@@ -1084,16 +1085,18 @@ impl RenderTask {
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskCacheKeyKind {
     BoxShadow(BoxShadowCacheKey),
     Image(ImageCacheKey),
     #[allow(dead_code)]
     Glyph(GpuGlyphCacheKey),
+    #[allow(dead_code)]
+    Picture(SurfaceCacheKey),
     BorderSegment(BorderSegmentCacheKey),
     LineDecoration(LineDecorationCacheKey),
 }
 
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskCacheKey {
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/src/surface.rs
@@ -0,0 +1,326 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use api::{LayoutPixel, PicturePixel, RasterSpace};
+use clip::{ClipChainId, ClipStore};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
+use euclid::TypedTransform3D;
+use intern::ItemUid;
+use internal_types::FastHashSet;
+use prim_store::{CoordinateSpaceMapping, PrimitiveInstance, PrimitiveInstanceKind};
+use std::hash;
+use util::ScaleOffset;
+
+/*
+
+    Notes for future implementation work on surface caching:
+
+    State that can affect the contents of a cached surface:
+
+        Primitives
+            These are handled by the PrimitiveUid value. The structure interning
+            code during scene building guarantees that each PrimitiveUid will
+            represent a unique identifier for the content of this primitive.
+        Clip chains
+            Similarly, the ClipUid value uniquely identifies the contents of
+            a clip node.
+        Transforms
+            Each picture contains a list of transforms that affect the content
+            of the picture itself. The value of the surface transform relative
+            to the raster root transform is only relevant if the picture is
+            being rasterized in screen-space.
+        External images
+            An external image (e.g. video) can change the contents of a picture
+            without a scene build occurring. We don't need to handle this yet,
+            but once images support interning and caching, we'll need to include
+            a list of external image dependencies in the cache key.
+        Property animation
+            Transform animations are handled by the transforms case above. We don't
+            need to handle opacity animations yet, since the interning and picture
+            caching doesn't support images and / or solid rects. Once those
+            primitives are ported, we'll need a list of property animation keys
+            that a surface depends on.
+
+*/
+
+// Matches the definition of SK_ScalarNearlyZero in Skia.
+// TODO(gw): Some testing to see what's reasonable for this value
+//           to avoid invalidating the cache for minor changes.
+const QUANTIZE_SCALE: f32 = 4096.0;
+
+fn quantize(value: f32) -> f32 {
+    (value * QUANTIZE_SCALE).round() / QUANTIZE_SCALE
+}
+
+/// A quantized, hashable version of util::ScaleOffset that
+/// can be used as a cache key.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, PartialEq, Clone)]
+pub struct ScaleOffsetKey {
+    pub scale_x: f32,
+    pub scale_y: f32,
+    pub offset_x: f32,
+    pub offset_y: f32,
+}
+
+impl ScaleOffsetKey {
+    fn new(scale_offset: &ScaleOffset) -> Self {
+        // TODO(gw): Since these are quantized, it might make sense in the future to
+        //           convert these to ints to remove the need for custom hash impl.
+        ScaleOffsetKey {
+            scale_x: quantize(scale_offset.scale.x),
+            scale_y: quantize(scale_offset.scale.y),
+            offset_x: quantize(scale_offset.offset.x),
+            offset_y: quantize(scale_offset.offset.y),
+        }
+    }
+}
+
+impl Eq for ScaleOffsetKey {}
+
+impl hash::Hash for ScaleOffsetKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.scale_x.to_bits().hash(state);
+        self.scale_y.to_bits().hash(state);
+        self.offset_x.to_bits().hash(state);
+        self.offset_y.to_bits().hash(state);
+    }
+}
+
+/// A quantized, hashable version of PictureTransform that
+/// can be used as a cache key.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, PartialEq, Clone)]
+pub struct MatrixKey {
+    values: [f32; 16],
+}
+
+impl MatrixKey {
+    fn new<Src, Dst>(transform: &TypedTransform3D<f32, Src, Dst>) -> Self {
+        let mut values = transform.to_row_major_array();
+
+        // TODO(gw): Since these are quantized, it might make sense in the future to
+        //           convert these to ints to remove the need for custom hash impl.
+        for value in &mut values {
+            *value = quantize(*value);
+        }
+
+        MatrixKey {
+            values,
+        }
+    }
+}
+
+impl Eq for MatrixKey {}
+
+impl hash::Hash for MatrixKey {
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        for value in &self.values {
+            value.to_bits().hash(state);
+        }
+    }
+}
+
+/// A quantized, hashable version of CoordinateSpaceMapping that
+/// can be used as a cache key.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, PartialEq, Clone, Hash, Eq)]
+pub enum TransformKey {
+    Local,
+    ScaleOffset(ScaleOffsetKey),
+    Transform(MatrixKey),
+}
+
+impl TransformKey {
+    pub fn local() -> Self {
+        TransformKey::Local
+    }
+}
+
+impl<F, T> From<CoordinateSpaceMapping<F, T>> for TransformKey {
+    /// Construct a transform cache key from a coordinate space mapping.
+    fn from(mapping: CoordinateSpaceMapping<F, T>) -> TransformKey {
+        match mapping {
+            CoordinateSpaceMapping::Local => {
+                TransformKey::Local
+            }
+            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
+                TransformKey::ScaleOffset(ScaleOffsetKey::new(scale_offset))
+            }
+            CoordinateSpaceMapping::Transform(ref transform) => {
+                TransformKey::Transform(MatrixKey::new(transform))
+            }
+        }
+    }
+}
+
+/// This key uniquely identifies the contents of a cached
+/// picture surface.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Eq, PartialEq, Hash, Clone)]
+pub struct SurfaceCacheKey {
+    /// The list of primitives that are part of this surface.
+    /// The uid uniquely identifies the content of the primitive.
+    pub primitive_ids: Vec<ItemUid>,
+    /// The list of clips that affect the primitives on this surface.
+    /// The uid uniquely identifies the content of the clip.
+    pub clip_ids: Vec<ItemUid>,
+    /// A list of transforms that can affect the contents of primitives
+    /// and/or clips on this picture surface.
+    pub transforms: Vec<TransformKey>,
+    /// Information about the transform of the picture surface itself. If we are
+    /// drawing in screen-space, then the value of this affects the contents
+    /// of the cached surface. If we're drawing in local space, then the transform
+    /// of the surface in its parent is not relevant to the contents.
+    pub raster_transform: TransformKey,
+}
+
+/// A descriptor for the contents of a picture surface.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SurfaceDescriptor {
+    /// The cache key identifies the contents or primitives, clips and the current
+    /// state of relevant transforms.
+    pub cache_key: SurfaceCacheKey,
+
+    /// The spatial nodes array is used to update the cache key each frame, without
+    /// relying on the value of a spatial node index (these may change, if other parts of the
+    /// display list result in a different clip-scroll tree).
+    pub spatial_nodes: Vec<SpatialNodeIndex>,
+}
+
+impl SurfaceDescriptor {
+    /// Construct a new surface descriptor for this list of primitives.
+    /// This method is fallible - it will return None if this picture
+    /// contains primitives that can't currently be cached safely.
+    pub fn new(
+        prim_instances: &[PrimitiveInstance],
+        pic_spatial_node_index: SpatialNodeIndex,
+        clip_store: &ClipStore,
+    ) -> Option<Self> {
+        let mut relevant_spatial_nodes = FastHashSet::default();
+        let mut primitive_ids = Vec::new();
+        let mut clip_ids = Vec::new();
+
+        for prim_instance in prim_instances {
+            // If the prim has the same spatial node as the surface,
+            // then the content can't move relative to it, so we don't
+            // care if the transform changes.
+            if pic_spatial_node_index != prim_instance.spatial_node_index {
+                relevant_spatial_nodes.insert(prim_instance.spatial_node_index);
+            }
+
+            // Collect clip node transforms that we care about.
+            let mut clip_chain_id = prim_instance.clip_chain_id;
+            while clip_chain_id != ClipChainId::NONE {
+                let clip_chain_node = &clip_store.clip_chain_nodes[clip_chain_id.0 as usize];
+
+                // TODO(gw): This needs to be a bit more careful once we create
+                //           descriptors for pictures that might be pass-through.
+
+                // Ignore clip chain nodes that will be handled by the clip node collector.
+                if clip_chain_node.spatial_node_index > pic_spatial_node_index {
+                    relevant_spatial_nodes.insert(prim_instance.spatial_node_index);
+
+                    clip_ids.push(clip_chain_node.handle.uid());
+                }
+
+                clip_chain_id = clip_chain_node.parent_clip_chain_id;
+            }
+
+            // For now, we only handle interned primitives. If we encounter
+            // a legacy primitive or picture, then fail to create a cache
+            // descriptor.
+            if let PrimitiveInstanceKind::Picture { .. } = prim_instance.kind {
+                return None;
+            }
+
+            // Record the unique identifier for the content represented
+            // by this primitive.
+            primitive_ids.push(prim_instance.uid());
+        }
+
+        // Get a list of spatial nodes that are relevant for the contents
+        // of this picture. Sort them to ensure that for a given clip-scroll
+        // tree, we end up with the same transform ordering.
+        let mut spatial_nodes: Vec<SpatialNodeIndex> = relevant_spatial_nodes
+            .into_iter()
+            .collect();
+        spatial_nodes.sort();
+
+        // Create the array of transform values that gets built each
+        // frame during update.
+        let transforms = vec![TransformKey::local(); spatial_nodes.len()];
+
+        let cache_key = SurfaceCacheKey {
+            primitive_ids,
+            clip_ids,
+            transforms,
+            raster_transform: TransformKey::local(),
+        };
+
+        Some(SurfaceDescriptor {
+            cache_key,
+            spatial_nodes,
+        })
+    }
+
+    /// Update the transforms for this cache key, by extracting the current
+    /// values from the clip-scroll tree state.
+    pub fn update(
+        &mut self,
+        surface_spatial_node_index: SpatialNodeIndex,
+        raster_spatial_node_index: SpatialNodeIndex,
+        clip_scroll_tree: &ClipScrollTree,
+        raster_space: RasterSpace,
+    ) {
+        // Update the state of the transform for compositing this picture.
+        self.cache_key.raster_transform = match raster_space {
+            RasterSpace::Screen => {
+                // In general cases, if we're rasterizing a picture in screen space, then the
+                // value of the surface spatial node will affect the contents of the picture
+                // itself. However, if the surface and raster spatial nodes are in the same
+                // coordinate system (which is the common case!) then we are effectively drawing
+                // in a local space anyway, so don't care about that transform for the purposes
+                // of validating the surface cache contents.
+                let raster_spatial_node = &clip_scroll_tree.spatial_nodes[raster_spatial_node_index.0 as usize];
+                let surface_spatial_node = &clip_scroll_tree.spatial_nodes[surface_spatial_node_index.0 as usize];
+
+                let mut key = CoordinateSpaceMapping::<LayoutPixel, PicturePixel>::new(
+                    raster_spatial_node_index,
+                    surface_spatial_node_index,
+                    clip_scroll_tree,
+                ).expect("bug: unable to get coord mapping").into();
+
+                if let TransformKey::ScaleOffset(ref mut key) = key {
+                    if raster_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id {
+                        key.offset_x = 0.0;
+                        key.offset_y = 0.0;
+                    }
+                }
+
+                key
+            }
+            RasterSpace::Local(..) => {
+                TransformKey::local()
+            }
+        };
+
+        // Update the state of any relevant transforms for this picture.
+        for (spatial_node_index, transform) in self.spatial_nodes
+            .iter()
+            .zip(self.cache_key.transforms.iter_mut())
+        {
+            *transform = CoordinateSpaceMapping::<LayoutPixel, PicturePixel>::new(
+                raster_spatial_node_index,
+                *spatial_node_index,
+                clip_scroll_tree,
+            ).expect("bug: unable to get coord mapping").into();
+        }
+    }
+}