Bug 1519747 - Split primitive preparation into a separate culling pass. r=kvark
authorGlenn Watson <github@intuitionlibrary.com>
Mon, 14 Jan 2019 19:03:52 +0000
changeset 510891 b7f7558083b214c8a5b8919b41e730c93affebc9
parent 510890 600fd2ac9a5ad2b5a9c1538b22bb1db010187e37
child 510892 0ee33312ad446d950b54b62cdc3cb1a4cf48cc87
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1519747
milestone66.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 1519747 - Split primitive preparation into a separate culling pass. r=kvark Differential Revision: https://phabricator.services.mozilla.com/D16429
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/clip.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -10,17 +10,17 @@ use clip_scroll_tree::{ClipScrollTree, R
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
-use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind};
+use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex};
 use prim_store::image::ImageSource;
 use render_backend::FrameResources;
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
@@ -578,17 +578,17 @@ impl AlphaBatchBuilder {
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
-        if prim_instance.bounding_rect.is_none() {
+        if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
         debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
         let transform_id = transforms
             .get_id(
@@ -596,25 +596,25 @@ impl AlphaBatchBuilder {
                 root_spatial_node_index,
                 ctx.clip_scroll_tree,
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
-        let bounding_rect = prim_instance.bounding_rect
-                                         .as_ref()
-                                         .expect("bug");
+        let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
+        let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
+
         let z_id = z_generator.next();
 
         // Get the clip task address for the global primitive, if one was set.
         let clip_task_address = get_clip_task_address(
             &ctx.scratch.clip_mask_instances,
-            prim_instance.clip_task_index,
+            prim_info.clip_task_index,
             0,
             render_tasks,
         ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
         let prim_common_data = &ctx.resources.as_common_data(&prim_instance);
         let prim_rect = LayoutRect::new(
             prim_instance.prim_origin,
             prim_common_data.prim_size,
@@ -626,17 +626,17 @@ impl AlphaBatchBuilder {
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -686,27 +686,27 @@ impl AlphaBatchBuilder {
                         SegmentInstanceData {
                             textures: BatchTextures::color(cache_item.texture_id),
                             user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
                         }
                     );
                 }
 
                 let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::instanced(
                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
@@ -732,34 +732,34 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => {
                 let run = &ctx.prim_store.text_runs[run_index];
                 let subpx_dir = run.used_font.get_subpx_dir();
 
                 // The GPU cache data is stored in the template and reused across
                 // frames and display lists.
                 let prim_data = &ctx.resources.text_run_data_store[data_handle];
                 let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
                 let alpha_batch_list = &mut self.batch_lists.last_mut().unwrap().alpha_batch_list;
                 let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range];
 
@@ -886,27 +886,27 @@ impl AlphaBatchBuilder {
                         )
                     }
                 };
 
                 // TODO(gw): We can abstract some of the common code below into
                 //           helper methods, as we port more primitives to make
                 //           use of interning.
                 let blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     BlendMode::PremultipliedAlpha
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -938,28 +938,30 @@ impl AlphaBatchBuilder {
             }
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let picture = &ctx.prim_store.pictures[pic_index.0];
                 let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
                 let prim_cache_address = gpu_cache.get_address(&picture.gpu_location);
 
                 let prim_header = PrimitiveHeader {
                     local_rect: picture.local_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 match picture.context_3d {
                     // Convert all children of the 3D hierarchy root into batches.
                     Picture3DContext::In { root_data: Some(ref list), .. } => {
                         for child in list {
                             let prim_instance = &picture.prim_list.prim_instances[child.anchor];
+                            let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize];
+
                             let pic_index = match prim_instance.kind {
                                 PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
                                 PrimitiveInstanceKind::LineDecoration { .. } |
                                 PrimitiveInstanceKind::TextRun { .. } |
                                 PrimitiveInstanceKind::NormalBorder { .. } |
                                 PrimitiveInstanceKind::ImageBorder { .. } |
                                 PrimitiveInstanceKind::Rectangle { .. } |
                                 PrimitiveInstanceKind::YuvImage { .. } |
@@ -967,27 +969,28 @@ impl AlphaBatchBuilder {
                                 PrimitiveInstanceKind::LinearGradient { .. } |
                                 PrimitiveInstanceKind::RadialGradient { .. } |
                                 PrimitiveInstanceKind::Clear { .. } => {
                                     unreachable!();
                                 }
                             };
                             let pic = &ctx.prim_store.pictures[pic_index.0];
 
+
                             // Get clip task, if set, for the picture primitive.
                             let clip_task_address = get_clip_task_address(
                                 &ctx.scratch.clip_mask_instances,
-                                prim_instance.clip_task_index,
+                                prim_info.clip_task_index,
                                 0,
                                 render_tasks,
                             ).unwrap_or(OPAQUE_TASK_ADDRESS);
 
                             let prim_header = PrimitiveHeader {
                                 local_rect: pic.local_rect,
-                                local_clip_rect: prim_instance.combined_local_clip_rect,
+                                local_clip_rect: prim_info.combined_local_clip_rect,
                                 task_address,
                                 specific_prim_address: GpuCacheAddress::invalid(),
                                 clip_task_address,
                                 transform_id: child.transform_id,
                             };
 
                             let surface_index = pic
                                 .raster_config
@@ -1020,17 +1023,17 @@ impl AlphaBatchBuilder {
                             let instance = SplitCompositeInstance::new(
                                 prim_header_index,
                                 child.gpu_address,
                                 z_id,
                             );
 
                             self.current_batch_list().push_single_instance(
                                 key,
-                                &prim_instance.bounding_rect.as_ref().expect("bug"),
+                                &prim_info.clip_chain.pic_clip_rect,
                                 z_id,
                                 PrimitiveInstanceData::from(instance),
                             );
                         }
                     }
                     // Ignore the 3D pictures that are not in the root of preserve-3D
                     // hierarchy, since we process them with the root.
                     Picture3DContext::In { root_data: None, .. } => return,
@@ -1039,17 +1042,17 @@ impl AlphaBatchBuilder {
                 }
 
                 match picture.raster_config {
                     Some(ref raster_config) => {
                         match raster_config.composite_mode {
                             PictureCompositeMode::TileCache { .. } => {
                                 // Construct a local clip rect that ensures we only draw pixels where
                                 // the local bounds of the picture extend to within the edge tiles.
-                                let local_clip_rect = prim_instance
+                                let local_clip_rect = prim_info
                                     .combined_local_clip_rect
                                     .intersection(&picture.local_rect)
                                     .and_then(|rect| {
                                         rect.intersection(&picture.local_clip_rect)
                                     });
 
                                 if let Some(local_clip_rect) = local_clip_rect {
                                     // Step through each tile in the cache, and draw it with an image
@@ -1498,27 +1501,27 @@ impl AlphaBatchBuilder {
                 if cache_item.texture_id == TextureSource::Invalid {
                     return;
                 }
 
                 let textures = BatchTextures::color(cache_item.texture_id);
                 let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle);
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let non_segmented_blend_mode = if !common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let batch_params = BrushBatchParameters::shared(
                     BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
@@ -1544,30 +1547,30 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => {
                 let prim_data = &ctx.resources.prim_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
                 let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index);
 
                 let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
                 let opacity = opacity.combine(prim_data.opacity);
 
                 let non_segmented_blend_mode = if !opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 let batch_params = BrushBatchParameters::shared(
@@ -1582,17 +1585,17 @@ impl AlphaBatchBuilder {
                 } else {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -1607,17 +1610,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => {
                 let yuv_image_data = &ctx.resources.yuv_image_data_store[data_handle].kind;
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
 
@@ -1671,17 +1674,17 @@ impl AlphaBatchBuilder {
                         uv_rect_addresses[2],
                     ],
                     0,
                 );
 
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let non_segmented_blend_mode = if !prim_common_data.opacity.is_opaque ||
-                    prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                    prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                     transform_kind == TransformedRectKind::Complex
                 {
                     specified_blend_mode
                 } else {
                     BlendMode::None
                 };
 
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
@@ -1690,17 +1693,17 @@ impl AlphaBatchBuilder {
                 } else {
                     let segment_instance = &ctx.scratch.segment_instances[segment_instance_index];
                     let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                     (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                 };
 
                 let prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: prim_cache_address,
                     clip_task_address,
                     transform_id,
                 };
 
                 let prim_header_index = prim_headers.push(
                     &prim_header,
@@ -1715,17 +1718,17 @@ impl AlphaBatchBuilder {
                     specified_blend_mode,
                     non_segmented_blend_mode,
                     prim_header_index,
                     clip_task_address,
                     bounding_rect,
                     transform_kind,
                     render_tasks,
                     z_id,
-                    prim_instance.clip_task_index,
+                    prim_info.clip_task_index,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
                 let image_data = &ctx.resources.image_data_store[data_handle].kind;
                 let common_data = &ctx.resources.image_data_store[data_handle].common;
                 let image_instance = &ctx.prim_store.images[image_instance_index];
                 let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index);
@@ -1764,17 +1767,17 @@ impl AlphaBatchBuilder {
                     }
 
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     let opacity = PrimitiveOpacity::from_alpha(opacity_binding);
                     let opacity = opacity.combine(common_data.opacity);
 
                     let non_segmented_blend_mode = if !opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1794,17 +1797,17 @@ impl AlphaBatchBuilder {
                     } else {
                         let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index];
                         let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]);
                         (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments)
                     };
 
                     let prim_header = PrimitiveHeader {
                         local_rect: prim_rect,
-                        local_clip_rect: prim_instance.combined_local_clip_rect,
+                        local_clip_rect: prim_info.combined_local_clip_rect,
                         task_address,
                         specific_prim_address: prim_cache_address,
                         clip_task_address,
                         transform_id,
                     };
 
                     let prim_header_index = prim_headers.push(
                         &prim_header,
@@ -1819,17 +1822,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     for tile in &image_instance.visible_tiles {
                         if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
@@ -1864,26 +1867,26 @@ impl AlphaBatchBuilder {
                 }
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.resources.linear_grad_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     clip_task_address,
                     transform_id,
                 };
 
                 if visible_tiles_range.is_empty() {
                     let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1918,17 +1921,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
 
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
@@ -1945,26 +1948,26 @@ impl AlphaBatchBuilder {
                 }
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
                 let prim_data = &ctx.resources.radial_grad_data_store[data_handle];
                 let specified_blend_mode = BlendMode::PremultipliedAlpha;
 
                 let mut prim_header = PrimitiveHeader {
                     local_rect: prim_rect,
-                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    local_clip_rect: prim_info.combined_local_clip_rect,
                     task_address,
                     specific_prim_address: GpuCacheAddress::invalid(),
                     clip_task_address,
                     transform_id,
                 };
 
                 if visible_tiles_range.is_empty() {
                     let non_segmented_blend_mode = if !prim_data.opacity.is_opaque ||
-                        prim_instance.clip_task_index != ClipTaskIndex::INVALID ||
+                        prim_info.clip_task_index != ClipTaskIndex::INVALID ||
                         transform_kind == TransformedRectKind::Complex
                     {
                         specified_blend_mode
                     } else {
                         BlendMode::None
                     };
 
                     let batch_params = BrushBatchParameters::shared(
@@ -1999,17 +2002,17 @@ impl AlphaBatchBuilder {
                         specified_blend_mode,
                         non_segmented_blend_mode,
                         prim_header_index,
                         clip_task_address,
                         bounding_rect,
                         transform_kind,
                         render_tasks,
                         z_id,
-                        prim_instance.clip_task_index,
+                        prim_info.clip_task_index,
                         ctx,
                     );
                 } else {
                     let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range];
 
                     add_gradient_tiles(
                         visible_tiles,
                         &prim_data.stops_handle,
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -457,16 +457,31 @@ pub struct ClipChainInstance {
     // If true, this clip chain requires allocation
     // of a clip mask.
     pub needs_mask: bool,
     // Combined clip rect in picture space (may
     // be more conservative that local_clip_rect).
     pub pic_clip_rect: PictureRect,
 }
 
+impl ClipChainInstance {
+    pub fn empty() -> Self {
+        ClipChainInstance {
+            clips_range: ClipNodeRange {
+                first: 0,
+                count: 0,
+            },
+            local_clip_rect: LayoutRect::zero(),
+            has_non_local_clips: false,
+            needs_mask: false,
+            pic_clip_rect: PictureRect::zero(),
+        }
+    }
+}
+
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_chain_nodes: Vec::new(),
             clip_node_instances: Vec::new(),
             clip_node_info: Vec::new(),
             clip_node_collectors: Vec::new(),
         }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -65,16 +65,30 @@ pub struct FrameBuilder {
     /// that can optionally be consumed by this frame builder.
     pending_retained_tiles: RetainedTiles,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 }
 
+pub struct FrameVisibilityContext<'a> {
+    pub clip_scroll_tree: &'a ClipScrollTree,
+    pub screen_world_rect: WorldRect,
+    pub device_pixel_scale: DevicePixelScale,
+    pub surfaces: &'a [SurfaceInfo],
+}
+
+pub struct FrameVisibilityState<'a> {
+    pub clip_store: &'a mut ClipStore,
+    pub resource_cache: &'a mut ResourceCache,
+    pub gpu_cache: &'a mut GpuCache,
+    pub scratch: &'a mut PrimitiveScratchBuffer,
+}
+
 pub struct FrameBuildingContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub max_local_clip: LayoutRect,
     pub debug_flags: DebugFlags,
@@ -300,16 +314,40 @@ impl FrameBuilder {
             resources,
             &self.clip_store,
             &pic_update_state.surfaces,
             gpu_cache,
             &mut retained_tiles,
             scratch,
         );
 
+        {
+            let visibility_context = FrameVisibilityContext {
+                device_pixel_scale,
+                clip_scroll_tree,
+                screen_world_rect,
+                surfaces: pic_update_state.surfaces,
+            };
+
+            let mut visibility_state = FrameVisibilityState {
+                resource_cache,
+                gpu_cache,
+                clip_store: &mut self.clip_store,
+                scratch,
+            };
+
+            self.prim_store.update_visibility(
+                self.root_pic_index,
+                ROOT_SURFACE_INDEX,
+                &visibility_context,
+                &mut visibility_state,
+                resources,
+            );
+        }
+
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             transforms: transform_palette,
             segment_builder: SegmentBuilder::new(),
@@ -341,17 +379,16 @@ impl FrameBuilder {
             scratch,
         );
 
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
             prim_list,
             pic_context,
             pic_state,
-            &mut frame_state,
         );
 
         let child_tasks = frame_state
             .surfaces[ROOT_SURFACE_INDEX.0]
             .take_render_tasks();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -4,17 +4,17 @@
 
 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};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
 #[cfg(feature = "debug_renderer")]
 use api::{DebugFlags, DeviceVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode, ClipItem};
+use clip::{ClipStore, ClipChainId, ClipChainNode, ClipItem};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 #[cfg(feature = "debug_renderer")]
 use debug_colors;
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use intern::ItemUid;
 use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
@@ -1660,17 +1660,17 @@ impl PicturePrimitive {
                 }
 
                 filter.is_visible()
             }
             _ => true,
         }
     }
 
-    fn is_visible(&self) -> bool {
+    pub fn is_visible(&self) -> bool {
         match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(ref filter)) => {
                 filter.is_visible()
             }
             _ => true,
         }
     }
 
@@ -1797,20 +1797,16 @@ impl PicturePrimitive {
 
                 (surface.raster_spatial_node_index, self.spatial_node_index, raster_config.surface_index)
             }
             None => {
                 (raster_spatial_node_index, surface_spatial_node_index, surface_index)
             }
         };
 
-        if self.raster_config.as_ref().map_or(false, |c| c.establishes_raster_root) {
-            frame_state.clip_store.push_raster_root(surface_spatial_node_index);
-        }
-
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             dirty_world_rect,
             frame_context.clip_scroll_tree,
         );
 
         let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
@@ -1886,55 +1882,49 @@ impl PicturePrimitive {
         Some((context, state, prim_list))
     }
 
     pub fn restore_context(
         &mut self,
         prim_list: PrimitiveList,
         context: PictureContext,
         state: PictureState,
-        frame_state: &mut FrameBuildingState,
-    ) -> Option<ClipNodeCollector> {
+    ) {
         self.prim_list = prim_list;
         self.state = Some((state, context));
-
-        if self.raster_config.as_ref().map_or(false, |c| c.establishes_raster_root) {
-            Some(frame_state.clip_store.pop_raster_root())
-        } else {
-            None
-        }
     }
 
     pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
         self.state.take().expect("bug: no state present!")
     }
 
     /// Add a primitive instance to the plane splitter. The function would generate
     /// an appropriate polygon, clip it against the frustum, and register with the
     /// given plane splitter.
     pub fn add_split_plane(
         splitter: &mut PlaneSplitter,
         transforms: &TransformPalette,
         prim_instance: &PrimitiveInstance,
         original_local_rect: LayoutRect,
+        combined_local_clip_rect: &LayoutRect,
         world_rect: WorldRect,
         plane_split_anchor: usize,
     ) -> bool {
         let transform = transforms
             .get_world_transform(prim_instance.spatial_node_index);
         let matrix = transform.cast();
 
         // Apply the local clip rect here, before splitting. This is
         // because the local clip rect can't be applied in the vertex
         // shader for split composites, since we are drawing polygons
         // rather that rectangles. The interpolation still works correctly
         // since we determine the UVs by doing a bilerp with a factor
         // from the original local rect.
         let local_rect = match original_local_rect
-            .intersection(&prim_instance.combined_local_clip_rect)
+            .intersection(combined_local_clip_rect)
         {
             Some(rect) => rect.cast(),
             None => return false,
         };
         let world_rect = world_rect.cast();
 
         match transform.transform_kind() {
             TransformedRectKind::AxisAligned => {
@@ -2282,17 +2272,16 @@ impl PicturePrimitive {
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
-        prim_local_rect: &LayoutRect,
         clipped_prim_bounding_rect: WorldRect,
         surface_index: SurfaceIndex,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
         let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
 
         if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
@@ -2318,17 +2307,17 @@ impl PicturePrimitive {
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             prim_instance.spatial_node_index,
             raster_spatial_node_index,
             pic_context.dirty_world_rect,
             frame_context.clip_scroll_tree,
         );
 
-        let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
+        let pic_rect = PictureRect::from_untyped(&self.local_rect.to_untyped());
 
         let (clipped, unclipped) = match get_raster_rects(
             pic_rect,
             &map_pic_to_raster,
             &map_raster_to_world,
             clipped_prim_bounding_rect,
             frame_context.device_pixel_scale,
         ) {
@@ -2548,24 +2537,24 @@ impl PicturePrimitive {
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
                     // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                     //  [brush specific data]
                     //  [segment_rect, segment data]
-                    let shadow_rect = prim_local_rect.translate(&offset);
+                    let shadow_rect = self.local_rect.translate(&offset);
 
                     // ImageBrush colors
                     request.push(color.premultiplied());
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
-                        prim_local_rect.size.width,
-                        prim_local_rect.size.height,
+                        self.local_rect.size.width,
+                        self.local_rect.size.height,
                         0.0,
                         0.0,
                     ]);
 
                     // segment rect / extra data
                     request.push(shadow_rect);
                     request.push([0.0, 0.0, 0.0, 0.0]);
                 }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -4,30 +4,30 @@
 
 use api::{BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
 use api::{DeviceIntRect, DevicePixelScale, DeviceRect};
 use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, WorldPoint, WorldSize};
 use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
 use api::{PremultipliedColorF, PropertyBinding, Shadow};
 use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, AuHelpers};
-use api::LayoutPrimitiveInfo;
+use api::{LayoutPrimitiveInfo};
 #[cfg(feature = "debug_renderer")]
 use api::DevicePoint;
 use border::{get_max_scale_for_border, build_border_instances};
 use border::BorderSegmentCacheKey;
 use clip::{ClipStore};
-use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
-use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
+use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex, ROOT_SPATIAL_NODE_INDEX};
+use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 #[cfg(feature = "debug_renderer")]
 use debug_render::DebugItem;
 use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
-use frame_builder::PrimitiveContext;
+use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
 use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState, TileCacheUpdateState};
 use picture::{ClusterIndex, PrimitiveList, SurfaceIndex, SurfaceInfo, RetainedTiles, RasterConfig};
@@ -138,17 +138,17 @@ impl ops::Not for VisibleFace {
     fn not(self) -> Self {
         match self {
             VisibleFace::Front => VisibleFace::Back,
             VisibleFace::Back => VisibleFace::Front,
         }
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum CoordinateSpaceMapping<F, T> {
     Local,
     ScaleOffset(ScaleOffset),
     Transform(TypedTransform3D<f32, F, T>),
 }
 
 impl<F, T> CoordinateSpaceMapping<F, T> {
     pub fn new(
@@ -180,17 +180,17 @@ impl<F, T> CoordinateSpaceMapping<F, T> 
                 CoordinateSpaceMapping::Transform(
                     transform.with_source::<F>().with_destination::<T>()
                 )
             })
         }
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct SpaceMapper<F, T> {
     kind: CoordinateSpaceMapping<F, T>,
     pub ref_spatial_node_index: SpatialNodeIndex,
     pub current_target_spatial_node_index: SpatialNodeIndex,
     pub bounds: TypedRect<f32, T>,
 }
 
 impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
@@ -1345,51 +1345,70 @@ pub enum PrimitiveInstanceKind {
     },
     /// Clear out a rect, used for special effects.
     Clear {
         /// Handle to the common interned data for this primitive.
         data_handle: PrimitiveDataHandle,
     },
 }
 
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct PrimitiveVisibilityIndex(pub u32);
+
+impl PrimitiveVisibilityIndex {
+    pub const INVALID: PrimitiveVisibilityIndex = PrimitiveVisibilityIndex(u32::MAX);
+}
+
+/// Information stored for a visible primitive about the visible
+/// rect and associated clip information.
+pub struct PrimitiveVisibility {
+    /// The clip chain instance that was built for this primitive.
+    pub clip_chain: ClipChainInstance,
+
+    /// The current world rect, clipped to screen / dirty rect boundaries.
+    // TODO(gw): This is only used by a small number of primitives.
+    //           It's probably faster to not store this and recalculate
+    //           on demand in those cases?
+    pub clipped_world_rect: WorldRect,
+
+    /// An index into the clip task instances array in the primitive
+    /// store. If this is ClipTaskIndex::INVALID, then the primitive
+    /// has no clip mask. Otherwise, it may store the offset of the
+    /// global clip mask task for this primitive, or the first of
+    /// a list of clip task ids (one per segment).
+    pub clip_task_index: ClipTaskIndex,
+
+    /// The current combined local clip for this primitive, from
+    /// the primitive local clip above and the current clip chain.
+    pub combined_local_clip_rect: LayoutRect,
+}
+
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Identifies the kind of primitive this
     /// instance is, and references to where
     /// the relevant information for the primitive
     /// can be found.
     pub kind: PrimitiveInstanceKind,
 
     /// Local space origin of this primitive. The size
     /// of the primitive is defined by the template.
     pub prim_origin: LayoutPoint,
 
-    /// The current combined local clip for this primitive, from
-    /// the primitive local clip above and the current clip chain.
-    pub combined_local_clip_rect: LayoutRect,
-
     #[cfg(debug_assertions)]
     pub id: PrimitiveDebugId,
 
     /// The last frame ID (of the `RenderTaskTree`) this primitive
     /// was prepared for rendering in.
     #[cfg(debug_assertions)]
     pub prepared_frame_id: FrameId,
 
-    /// The current minimal bounding rect of this primitive in picture space.
-    /// Includes the primitive rect, and any clipping rects from the same
-    /// coordinate system.
-    pub bounding_rect: Option<PictureRect>,
-
-    /// An index into the clip task instances array in the primitive
-    /// store. If this is ClipTaskIndex::INVALID, then the primitive
-    /// has no clip mask. Otherwise, it may store the offset of the
-    /// global clip mask task for this primitive, or the first of
-    /// a list of clip task ids (one per segment).
-    pub clip_task_index: ClipTaskIndex,
+    /// If this primitive is visible, an index into the instance
+    /// visibility scratch buffer. If not visible, INVALID.
+    pub visibility_info: PrimitiveVisibilityIndex,
 
     /// The cluster that this primitive belongs to. This is used
     /// for quickly culling out groups of primitives during the
     /// initial picture traversal pass.
     pub cluster_index: ClusterIndex,
 
     /// ID of the clip chain that this primitive is clipped by.
     pub clip_chain_id: ClipChainId,
@@ -1403,33 +1422,30 @@ impl PrimitiveInstance {
         prim_origin: LayoutPoint,
         kind: PrimitiveInstanceKind,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         PrimitiveInstance {
             prim_origin,
             kind,
-            combined_local_clip_rect: LayoutRect::zero(),
-            bounding_rect: None,
             #[cfg(debug_assertions)]
             prepared_frame_id: FrameId::INVALID,
             #[cfg(debug_assertions)]
             id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)),
-            clip_task_index: ClipTaskIndex::INVALID,
+            visibility_info: PrimitiveVisibilityIndex::INVALID,
             clip_chain_id,
             spatial_node_index,
             cluster_index: ClusterIndex::INVALID,
         }
     }
 
     // Reset any pre-frame state for this primitive.
     pub fn reset(&mut self) {
-        self.bounding_rect = None;
-        self.clip_task_index = ClipTaskIndex::INVALID;
+        self.visibility_info = PrimitiveVisibilityIndex::INVALID;
     }
 
     #[cfg(debug_assertions)]
     pub fn is_chased(&self) -> bool {
         PRIM_CHASE_ID.load(Ordering::SeqCst) == self.id.0
     }
 
     #[cfg(not(debug_assertions))]
@@ -1519,36 +1535,41 @@ pub struct PrimitiveScratchBuffer {
     /// that have opted into segment building. In future, this should be
     /// removed in favor of segment building during primitive interning.
     pub segment_instances: SegmentInstanceStorage,
 
     /// A list of visible tiles that tiled gradients use to store
     /// per-tile information.
     pub gradient_tiles: GradientTileStorage,
 
+    /// List of the visibility information for currently visible primitives.
+    pub prim_info: Vec<PrimitiveVisibility>,
+
     #[cfg(feature = "debug_renderer")]
     pub debug_items: Vec<DebugItem>,
 }
 
 impl PrimitiveScratchBuffer {
     pub fn new() -> Self {
         PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
             glyph_keys: GlyphKeyStorage::new(0),
             border_cache_handles: BorderHandleStorage::new(0),
             segments: SegmentStorage::new(0),
             segment_instances: SegmentInstanceStorage::new(0),
             gradient_tiles: GradientTileStorage::new(0),
             #[cfg(feature = "debug_renderer")]
             debug_items: Vec::new(),
+            prim_info: Vec::new(),
         }
     }
 
     pub fn recycle(&mut self) {
         recycle_vec(&mut self.clip_mask_instances);
+        recycle_vec(&mut self.prim_info);
         self.glyph_keys.recycle();
         self.border_cache_handles.recycle();
         self.segments.recycle();
         self.segment_instances.recycle();
         self.gradient_tiles.recycle();
         #[cfg(feature = "debug_renderer")]
         recycle_vec(&mut self.debug_items);
     }
@@ -1563,16 +1584,18 @@ impl PrimitiveScratchBuffer {
         self.border_cache_handles.clear();
 
         // TODO(gw): As in the previous code, the gradient tiles store GPU cache
         //           handles that are cleared (and thus invalidated + re-uploaded)
         //           every frame. This maintains the existing behavior, but we
         //           should fix this in the future to retain handles.
         self.gradient_tiles.clear();
 
+        self.prim_info.clear();
+
         #[cfg(feature = "debug_renderer")]
         self.debug_items.clear();
     }
 
     #[allow(dead_code)]
     #[cfg(feature = "debug_renderer")]
     pub fn push_debug_rect(
         &mut self,
@@ -1713,16 +1736,248 @@ impl PrimitiveStore {
                 children,
                 state,
                 frame_context,
                 gpu_cache,
             );
         }
     }
 
+    /// Update visibility pass - update each primitive visibility struct, and
+    /// build the clip chain instance if appropriate.
+    pub fn update_visibility(
+        &mut self,
+        pic_index: PictureIndex,
+        parent_surface_index: SurfaceIndex,
+        frame_context: &FrameVisibilityContext,
+        frame_state: &mut FrameVisibilityState,
+        resources: &mut FrameResources,
+    ) {
+        let (mut prim_list, surface_index, apply_local_clip_rect) = {
+            let pic = &mut self.pictures[pic_index.0];
+
+            let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty());
+            let surface_index = match pic.raster_config {
+                Some(ref raster_config) => raster_config.surface_index,
+                None => parent_surface_index,
+            };
+
+            (prim_list, surface_index, pic.apply_local_clip_rect)
+        };
+
+        let surface = &frame_context.surfaces[surface_index.0 as usize];
+
+        let mut map_local_to_surface = surface
+            .map_local_to_surface
+            .clone();
+
+        let map_surface_to_world = SpaceMapper::new_with_target(
+            ROOT_SPATIAL_NODE_INDEX,
+            surface.surface_spatial_node_index,
+            frame_context.screen_world_rect,
+            frame_context.clip_scroll_tree,
+        );
+
+        for prim_instance in &mut prim_list.prim_instances {
+            prim_instance.reset();
+
+            if prim_instance.is_chased() {
+                #[cfg(debug_assertions)]
+                println!("\tpreparing {:?} in {:?}",
+                    prim_instance.id, pic_index);
+            }
+
+            // Get the cluster and see if is visible
+            if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
+                continue;
+            }
+
+            let spatial_node = &frame_context
+                .clip_scroll_tree
+                .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
+
+            // TODO(gw): Although constructing these is cheap, they are often
+            //           the same for many consecutive primitives, so it may
+            //           be worth caching the most recent context.
+            let prim_context = PrimitiveContext::new(
+                spatial_node,
+                prim_instance.spatial_node_index,
+            );
+
+            map_local_to_surface.set_target_spatial_node(
+                prim_instance.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+
+            let (is_passthrough, prim_local_rect, prim_local_clip_rect, clip_node_collector) = match prim_instance.kind {
+                PrimitiveInstanceKind::Picture { pic_index, .. } => {
+                    if !self.pictures[pic_index.0].is_visible() {
+                        continue;
+                    }
+
+                    if let Some(ref raster_config) = self.pictures[pic_index.0].raster_config {
+                        if raster_config.establishes_raster_root {
+                            let surface = &frame_context.surfaces[raster_config.surface_index.0 as usize];
+                            frame_state.clip_store.push_raster_root(surface.surface_spatial_node_index);
+                        }
+                    }
+
+                    self.update_visibility(
+                        pic_index,
+                        surface_index,
+                        frame_context,
+                        frame_state,
+                        resources,
+                    );
+
+                    let pic = &self.pictures[pic_index.0];
+
+                    let clip_node_collector = pic.raster_config.as_ref().and_then(|rc| {
+                        if rc.establishes_raster_root {
+                            Some(frame_state.clip_store.pop_raster_root())
+                        } else {
+                            None
+                        }
+                    });
+
+                    (pic.raster_config.is_none(), pic.local_rect, LayoutRect::max_rect(), clip_node_collector)
+                }
+                _ => {
+                    let prim_data = &resources.as_common_data(&prim_instance);
+
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.prim_size,
+                    );
+                    let clip_rect = prim_data
+                        .prim_relative_clip_rect
+                        .translate(&LayoutVector2D::new(prim_instance.prim_origin.x, prim_instance.prim_origin.y));
+
+                    (false, prim_rect, clip_rect, None)
+                }
+            };
+
+            if is_passthrough {
+                let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
+
+                frame_state.scratch.prim_info.push(
+                    PrimitiveVisibility {
+                        clipped_world_rect: WorldRect::zero(),
+                        clip_chain: ClipChainInstance::empty(),
+                        clip_task_index: ClipTaskIndex::INVALID,
+                        combined_local_clip_rect: LayoutRect::zero(),
+                    }
+                );
+
+                prim_instance.visibility_info = vis_index;
+            } else {
+                if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
+                    if prim_instance.is_chased() {
+                        println!("\tculled for zero local rectangle");
+                    }
+                    continue;
+                }
+
+                // Inflate the local rect for this primitive by the inflation factor of
+                // the picture context. This ensures that even if the primitive itself
+                // is not visible, any effects from the blur radius will be correctly
+                // taken into account.
+                let inflation_factor = surface.inflation_factor;
+                let local_rect = prim_local_rect
+                    .inflate(inflation_factor, inflation_factor)
+                    .intersection(&prim_local_clip_rect);
+                let local_rect = match local_rect {
+                    Some(local_rect) => local_rect,
+                    None => {
+                        if prim_instance.is_chased() {
+                            println!("\tculled for being out of the local clip rectangle: {:?}",
+                                prim_local_clip_rect);
+                        }
+                        continue;
+                    }
+                };
+
+                let clip_chain = frame_state
+                    .clip_store
+                    .build_clip_chain_instance(
+                        prim_instance,
+                        local_rect,
+                        prim_local_clip_rect,
+                        prim_context.spatial_node_index,
+                        &map_local_to_surface,
+                        &map_surface_to_world,
+                        &frame_context.clip_scroll_tree,
+                        frame_state.gpu_cache,
+                        frame_state.resource_cache,
+                        frame_context.device_pixel_scale,
+                        &frame_context.screen_world_rect,
+                        clip_node_collector.as_ref(),
+                        &mut resources.clip_data_store,
+                    );
+
+                let clip_chain = match clip_chain {
+                    Some(clip_chain) => clip_chain,
+                    None => {
+                        if prim_instance.is_chased() {
+                            println!("\tunable to build the clip chain, skipping");
+                        }
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
+                        continue;
+                    }
+                };
+
+                if prim_instance.is_chased() {
+                    println!("\teffective clip chain from {:?} {}",
+                        clip_chain.clips_range,
+                        if apply_local_clip_rect { "(applied)" } else { "" },
+                    );
+                }
+
+                let combined_local_clip_rect = if apply_local_clip_rect {
+                    clip_chain.local_clip_rect
+                } else {
+                    prim_local_clip_rect
+                };
+
+                // Check if the clip bounding rect (in pic space) is visible on screen
+                // This includes both the prim bounding rect + local prim clip rect!
+                let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
+                    Some(world_rect) => world_rect,
+                    None => {
+                        continue;
+                    }
+                };
+
+                let clipped_world_rect = match world_rect.intersection(&frame_context.screen_world_rect) {
+                    Some(rect) => rect,
+                    None => {
+                        continue;
+                    }
+                };
+
+                let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
+
+                frame_state.scratch.prim_info.push(
+                    PrimitiveVisibility {
+                        clipped_world_rect,
+                        clip_chain,
+                        clip_task_index: ClipTaskIndex::INVALID,
+                        combined_local_clip_rect,
+                    }
+                );
+
+                prim_instance.visibility_info = vis_index;
+            }
+
+        }
+
+        // frame_state.pop_picture();
+        self.pictures[pic_index.0].prim_list = prim_list;
+    }
+
     /// Update any picture tile caches for a subset of the picture tree.
     /// This is often a no-op that exits very quickly, unless a new scene
     /// has arrived, or the relative transforms have changed.
     pub fn update_tile_cache(
         &mut self,
         pic_index: PictureIndex,
         state: &mut TileCacheUpdateState,
         frame_context: &FrameBuildingContext,
@@ -1983,17 +2238,17 @@ impl PrimitiveStore {
                 PrimitiveInstanceKind::LinearGradient { .. } |
                 PrimitiveInstanceKind::RadialGradient { .. } |
                 PrimitiveInstanceKind::Clear { .. } => {
                     None
                 }
             }
         };
 
-        let (is_passthrough, clip_node_collector) = match pic_info {
+        let is_passthrough = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
                 // Mark whether this picture has a complex coordinate system.
                 let is_passthrough = pic_context_for_children.is_passthrough;
 
                 self.prepare_primitives(
                     &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
@@ -2003,218 +2258,83 @@ impl PrimitiveStore {
                     scratch,
                 );
 
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
-                let clip_node_collector = self
-                    .pictures[pic_context_for_children.pic_index.0]
+                self.pictures[pic_context_for_children.pic_index.0]
                     .restore_context(
                         prim_list,
                         pic_context_for_children,
                         pic_state_for_children,
-                        frame_state,
                     );
 
-                (is_passthrough, clip_node_collector)
+                is_passthrough
             }
             None => {
-                (false, None)
-            }
-        };
-
-        let (prim_local_rect, prim_local_clip_rect) = match prim_instance.kind {
-            PrimitiveInstanceKind::Picture { pic_index, .. } => {
-                let pic = &self.pictures[pic_index.0];
-                (pic.local_rect, LayoutRect::max_rect())
-            }
-            _ => {
-                let prim_data = &resources.as_common_data(&prim_instance);
-
-                let prim_rect = LayoutRect::new(
-                    prim_instance.prim_origin,
-                    prim_data.prim_size,
-                );
-                let clip_rect = prim_data
-                    .prim_relative_clip_rect
-                    .translate(&LayoutVector2D::new(prim_instance.prim_origin.x, prim_instance.prim_origin.y));
-
-                (prim_rect, clip_rect)
+                false
             }
         };
 
-        // TODO(gw): Eventually we can move all the code handling below for
-        //           visibility and clip chain building to be done during the
-        //           update_prim_dependencies pass. That will mean that:
-        //           (a) We only do the work if the relative transforms change.
-        //           (b) Local clip rects can reduce the # of tile dependencies.
-
-        // TODO(gw): Having this declared outside is a hack / workaround. We
-        //           need it in pic.prepare_for_render below, but that code
-        //           path will only read it in the !is_passthrough case
-        //           below. This should be much tidier once we port this
-        //           traversal to work with a state stack like the initial
-        //           picture traversal now works.
-        let mut clipped_world_rect = WorldRect::zero();
-
-        if is_passthrough {
-            prim_instance.bounding_rect = Some(pic_state.map_local_to_pic.bounds);
-        } else {
-            if prim_local_rect.size.width <= 0.0 ||
-               prim_local_rect.size.height <= 0.0
-            {
-                if prim_instance.is_chased() {
-                    println!("\tculled for zero local rectangle");
-                }
-                return false;
-            }
-
-            // Inflate the local rect for this primitive by the inflation factor of
-            // the picture context. This ensures that even if the primitive itself
-            // is not visible, any effects from the blur radius will be correctly
-            // taken into account.
-            let inflation_factor = frame_state
-                .surfaces[pic_context.surface_index.0]
-                .inflation_factor;
-            let local_rect = prim_local_rect
-                .inflate(inflation_factor, inflation_factor)
-                .intersection(&prim_local_clip_rect);
-            let local_rect = match local_rect {
-                Some(local_rect) => local_rect,
-                None => {
-                    if prim_instance.is_chased() {
-                        println!("\tculled for being out of the local clip rectangle: {:?}",
-                            prim_local_clip_rect);
-                    }
-                    return false;
-                }
-            };
-
-            let clip_chain = frame_state
-                .clip_store
-                .build_clip_chain_instance(
-                    prim_instance,
-                    local_rect,
-                    prim_local_clip_rect,
-                    prim_context.spatial_node_index,
-                    &pic_state.map_local_to_pic,
-                    &pic_state.map_pic_to_world,
-                    &frame_context.clip_scroll_tree,
-                    frame_state.gpu_cache,
-                    frame_state.resource_cache,
-                    frame_context.device_pixel_scale,
-                    &pic_context.dirty_world_rect,
-                    clip_node_collector.as_ref(),
-                    &mut resources.clip_data_store,
-                );
-
-            let clip_chain = match clip_chain {
-                Some(clip_chain) => clip_chain,
-                None => {
-                    if prim_instance.is_chased() {
-                        println!("\tunable to build the clip chain, skipping");
-                    }
-                    prim_instance.bounding_rect = None;
-                    return false;
-                }
-            };
-
-            if prim_instance.is_chased() {
-                println!("\teffective clip chain from {:?} {}",
-                    clip_chain.clips_range,
-                    if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
-                );
-            }
-
-            prim_instance.combined_local_clip_rect = if pic_context.apply_local_clip_rect {
-                clip_chain.local_clip_rect
-            } else {
-                prim_local_clip_rect
-            };
-
-            // Check if the clip bounding rect (in pic space) is visible on screen
-            // This includes both the prim bounding rect + local prim clip rect!
-            let world_rect = match pic_state
-                .map_pic_to_world
-                .map(&clip_chain.pic_clip_rect)
-            {
-                Some(world_rect) => world_rect,
-                None => {
-                    return false;
-                }
-            };
-
-            clipped_world_rect = match world_rect.intersection(&pic_context.dirty_world_rect) {
-                Some(rect) => rect,
-                None => {
-                    return false;
-                }
-            };
-
-            prim_instance.bounding_rect = Some(clip_chain.pic_clip_rect);
-
+        if !is_passthrough {
             prim_instance.update_clip_task(
-                prim_local_rect,
-                prim_local_clip_rect,
                 prim_context,
-                clipped_world_rect,
                 pic_context.raster_spatial_node_index,
-                &clip_chain,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
-                clip_node_collector.as_ref(),
                 self,
                 resources,
                 scratch,
             );
 
             if prim_instance.is_chased() {
-                println!("\tconsidered visible and ready with local rect {:?}", local_rect);
+                println!("\tconsidered visible and ready with local pos {:?}", prim_instance.prim_origin);
             }
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         pic_state.is_cacheable &= prim_instance.is_cacheable(
             &resources,
             frame_state.resource_cache,
         );
 
         match prim_instance.kind {
             PrimitiveInstanceKind::Picture { pic_index, .. } => {
                 let pic = &mut self.pictures[pic_index.0];
+                let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
                 if pic.prepare_for_render(
                     pic_index,
                     prim_instance,
-                    &prim_local_rect,
-                    clipped_world_rect,
+                    prim_info.clipped_world_rect,
                     pic_context.surface_index,
                     frame_context,
                     frame_state,
                 ) {
                     if let Some(ref mut splitter) = pic_state.plane_splitter {
                         PicturePrimitive::add_split_plane(
                             splitter,
                             frame_state.transforms,
                             prim_instance,
-                            prim_local_rect,
+                            pic.local_rect,
+                            &prim_info.combined_local_clip_rect,
                             pic_context.dirty_world_rect,
                             plane_split_anchor,
                         );
                     }
                 } else {
-                    prim_instance.bounding_rect = None;
+                    prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                 }
 
                 if let Some(mut request) = frame_state.gpu_cache.request(&mut pic.gpu_location) {
                     request.push(PremultipliedColorF::WHITE);
                     request.push(PremultipliedColorF::WHITE);
                     request.push([
                         -1.0,       // -ve means use prim rect for stretch size
                         0.0,
@@ -2230,17 +2350,16 @@ impl PrimitiveStore {
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
-                    prim_local_rect,
                     prim_context,
                     pic_context,
                     frame_context,
                     frame_state,
                     resources,
                     scratch,
                 );
             }
@@ -2255,26 +2374,17 @@ impl PrimitiveStore {
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         for (plane_split_anchor, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
-            prim_instance.reset();
-
-            if prim_instance.is_chased() {
-                #[cfg(debug_assertions)]
-                println!("\tpreparing {:?} in {:?}",
-                    prim_instance.id, pic_context.pipeline_id);
-            }
-
-            // Get the cluster and see if is visible
-            if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
+            if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
                 continue;
             }
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
 
             // TODO(gw): Although constructing these is cheap, they are often
@@ -2307,17 +2417,16 @@ impl PrimitiveStore {
     }
 
     /// Prepare an interned primitive for rendering, by requesting
     /// resources, render tasks etc. This is equivalent to the
     /// prepare_prim_for_render_inner call for old style primitives.
     fn prepare_interned_prim_for_render(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
-        prim_local_rect: LayoutRect,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let is_chased = prim_instance.is_chased();
@@ -2527,32 +2636,37 @@ impl PrimitiveStore {
 
                 if let Some(image_properties) = image_properties {
                     if let Some(tile_size) = image_properties.tiling {
                         let device_image_size = image_properties.descriptor.size;
 
                         // Tighten the clip rect because decomposing the repeated image can
                         // produce primitives that are partially covering the original image
                         // rect and we want to clip these extra parts out.
-                        let tight_clip_rect = prim_instance
+                        let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                        let prim_rect = LayoutRect::new(
+                            prim_instance.prim_origin,
+                            common_data.prim_size,
+                        );
+                        let tight_clip_rect = prim_info
                             .combined_local_clip_rect
-                            .intersection(&prim_local_rect).unwrap();
+                            .intersection(&prim_rect).unwrap();
 
                         let visible_rect = compute_conservative_visible_rect(
                             prim_context,
                             &pic_context.dirty_world_rect,
                             &tight_clip_rect
                         );
 
                         let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing);
 
                         let stride = image_data.stretch_size + image_data.tile_spacing;
 
                         let repetitions = ::image::repetitions(
-                            &prim_local_rect,
+                            &prim_rect,
                             &visible_rect,
                             stride,
                         );
 
                         let request = ImageRequest {
                             key: image_data.key,
                             rendering: image_data.image_rendering,
                             tile: None,
@@ -2597,36 +2711,42 @@ impl PrimitiveStore {
                         }
 
                         if image_instance.visible_tiles.is_empty() {
                             // At this point if we don't have tiles to show it means we could probably
                             // have done a better a job at culling during an earlier stage.
                             // Clearing the screen rect has the effect of "culling out" the primitive
                             // from the point of view of the batch builder, and ensures we don't hit
                             // assertions later on because we didn't request any image.
-                            prim_instance.bounding_rect = None;
+                            prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         }
                     }
                 }
 
                 write_segment(image_instance.segment_instance_index, frame_state, scratch, |request| {
                     image_data.write_prim_gpu_blocks(request);
                 });
             }
             PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, .. } => {
                 let prim_data = &mut resources.linear_grad_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
                 if prim_data.tile_spacing != LayoutSize::zero() {
+                    let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.common.prim_size,
+                    );
+
                     *visible_tiles_range = decompose_repeated_primitive(
-                        &prim_instance.combined_local_clip_rect,
-                        &prim_local_rect,
+                        &prim_info.combined_local_clip_rect,
+                        &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
                         prim_context,
                         frame_state,
                         &pic_context.dirty_world_rect,
                         &mut scratch.gradient_tiles,
                         &mut |_, mut request| {
                             request.push([
@@ -2640,34 +2760,40 @@ impl PrimitiveStore {
                                 prim_data.stretch_size.width,
                                 prim_data.stretch_size.height,
                                 0.0,
                             ]);
                         }
                     );
 
                     if visible_tiles_range.is_empty() {
-                        prim_instance.bounding_rect = None;
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 // TODO(gw): Consider whether it's worth doing segment building
                 //           for gradient primitives.
             }
             PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => {
                 let prim_data = &mut resources.radial_grad_data_store[*data_handle];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
                 if prim_data.tile_spacing != LayoutSize::zero() {
+                    let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize];
+                    let prim_rect = LayoutRect::new(
+                        prim_instance.prim_origin,
+                        prim_data.common.prim_size,
+                    );
+
                     *visible_tiles_range = decompose_repeated_primitive(
-                        &prim_instance.combined_local_clip_rect,
-                        &prim_local_rect,
+                        &prim_info.combined_local_clip_rect,
+                        &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
                         prim_context,
                         frame_state,
                         &pic_context.dirty_world_rect,
                         &mut scratch.gradient_tiles,
                         &mut |_, mut request| {
                             request.push([
@@ -2681,17 +2807,17 @@ impl PrimitiveStore {
                                 pack_as_float(prim_data.extend_mode as u32),
                                 prim_data.stretch_size.width,
                                 prim_data.stretch_size.height,
                             ]);
                         },
                     );
 
                     if visible_tiles_range.is_empty() {
-                        prim_instance.bounding_rect = None;
+                        prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                     }
                 }
 
                 // TODO(gw): Consider whether it's worth doing segment building
                 //           for gradient primitives.
             }
             _ => {
                 unreachable!();
@@ -2952,24 +3078,30 @@ impl<'a> GpuDataRequest<'a> {
         }
 
         false
     }
 
 impl PrimitiveInstance {
     fn build_segments_if_needed(
         &mut self,
-        prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
         prim_clip_chain: &ClipChainInstance,
         frame_state: &mut FrameBuildingState,
         prim_store: &mut PrimitiveStore,
         resources: &FrameResources,
-        scratch: &mut PrimitiveScratchBuffer,
+        segments_store: &mut SegmentStorage,
+        segment_instances_store: &mut SegmentInstanceStorage,
     ) {
+        let prim_data = &resources.as_common_data(self);
+        let prim_local_rect = LayoutRect::new(
+            self.prim_origin,
+            prim_data.prim_size,
+        );
+
         let segment_instance_index = match self.kind {
             PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } |
             PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => {
                 segment_instance_index
             }
             PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
                 let image_data = &resources.image_data_store[data_handle].kind;
                 let image_instance = &mut prim_store.images[image_instance_index];
@@ -3019,48 +3151,43 @@ impl PrimitiveInstance {
                         ),
                     );
                 });
             }
 
             if segments.is_empty() {
                 *segment_instance_index = SegmentInstanceIndex::UNUSED;
             } else {
-                let segments_range = scratch
-                    .segments
-                    .extend(segments);
+                let segments_range = segments_store.extend(segments);
 
                 let instance = SegmentedInstance {
                     segments_range,
                     gpu_cache_handle: GpuCacheHandle::new(),
                 };
 
-                *segment_instance_index = scratch
-                    .segment_instances
-                    .push(instance);
+                *segment_instance_index = segment_instances_store.push(instance);
             };
         }
     }
 
     fn update_clip_task_for_brush(
-        &mut self,
-        prim_origin: LayoutPoint,
+        &self,
+        prim_info: &mut PrimitiveVisibility,
         prim_local_clip_rect: LayoutRect,
         root_spatial_node_index: SpatialNodeIndex,
-        prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
-        prim_clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        clip_node_collector: Option<&ClipNodeCollector>,
         prim_store: &PrimitiveStore,
         resources: &mut FrameResources,
-        scratch: &mut PrimitiveScratchBuffer,
+        segments_store: &mut SegmentStorage,
+        segment_instances_store: &mut SegmentInstanceStorage,
+        clip_mask_instances: &mut Vec<ClipMaskKind>,
     ) -> bool {
         let segments = match self.kind {
             PrimitiveInstanceKind::Picture { .. } |
             PrimitiveInstanceKind::TextRun { .. } |
             PrimitiveInstanceKind::Clear { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 return false;
             }
@@ -3068,31 +3195,31 @@ impl PrimitiveInstance {
                 let segment_instance_index = prim_store
                     .images[image_instance_index]
                     .segment_instance_index;
 
                 if segment_instance_index == SegmentInstanceIndex::UNUSED {
                     return false;
                 }
 
-                let segment_instance = &scratch.segment_instances[segment_instance_index];
-
-                &scratch.segments[segment_instance.segments_range]
+                let segment_instance = &segment_instances_store[segment_instance_index];
+
+                &segments_store[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::YuvImage { segment_instance_index, .. } |
             PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => {
                 debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID);
 
                 if segment_instance_index == SegmentInstanceIndex::UNUSED {
                     return false;
                 }
 
-                let segment_instance = &scratch.segment_instances[segment_instance_index];
-
-                &scratch.segments[segment_instance.segments_range]
+                let segment_instance = &segment_instances_store[segment_instance_index];
+
+                &segments_store[segment_instance.segments_range]
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
                 let border_data = &resources.image_border_data_store[data_handle].kind;
 
                 // TODO: This is quite messy - once we remove legacy primitives we
                 //       can change this to be a tuple match on (instance, template)
                 border_data.brush_segments.as_slice()
             }
@@ -3131,156 +3258,160 @@ impl PrimitiveInstance {
         // clip task instance location below.
         if segments.is_empty() {
             return true;
         }
 
         // Set where in the clip mask instances array the clip mask info
         // can be found for this primitive. Each segment will push the
         // clip mask information for itself in update_clip_task below.
-        self.clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
+        prim_info.clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
 
         // If we only built 1 segment, there is no point in re-running
         // the clip chain builder. Instead, just use the clip chain
         // instance that was built for the main primitive. This is a
         // significant optimization for the common case.
         if segments.len() == 1 {
             let clip_mask_kind = segments[0].update_clip_task(
-                Some(prim_clip_chain),
-                prim_bounding_rect,
+                Some(&prim_info.clip_chain),
+                prim_info.clipped_world_rect,
                 root_spatial_node_index,
                 pic_context.surface_index,
                 pic_state,
                 frame_context,
                 frame_state,
                 &mut resources.clip_data_store,
             );
-            scratch.clip_mask_instances.push(clip_mask_kind);
+            clip_mask_instances.push(clip_mask_kind);
         } else {
             for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         self,
-                        segment.local_rect.translate(&LayoutVector2D::new(prim_origin.x, prim_origin.y)),
+                        segment.local_rect.translate(&LayoutVector2D::new(
+                            self.prim_origin.x,
+                            self.prim_origin.y,
+                        )),
                         prim_local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         frame_context.device_pixel_scale,
                         &pic_context.dirty_world_rect,
-                        clip_node_collector,
+                        None,
                         &mut resources.clip_data_store,
                     );
 
                 let clip_mask_kind = segment.update_clip_task(
                     segment_clip_chain.as_ref(),
-                    prim_bounding_rect,
+                    prim_info.clipped_world_rect,
                     root_spatial_node_index,
                     pic_context.surface_index,
                     pic_state,
                     frame_context,
                     frame_state,
                     &mut resources.clip_data_store,
                 );
-                scratch.clip_mask_instances.push(clip_mask_kind);
+                clip_mask_instances.push(clip_mask_kind);
             }
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
-        prim_local_rect: LayoutRect,
-        prim_local_clip_rect: LayoutRect,
         prim_context: &PrimitiveContext,
-        prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
-        clip_chain: &ClipChainInstance,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        clip_node_collector: Option<&ClipNodeCollector>,
         prim_store: &mut PrimitiveStore,
         resources: &mut FrameResources,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
+        let prim_info = &mut scratch.prim_info[self.visibility_info.0 as usize];
+
         if self.is_chased() {
-            println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
+            println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect);
         }
 
+        let prim_clip_rect = resources
+            .as_common_data(self)
+            .prim_relative_clip_rect
+            .translate(&self.prim_origin.to_vector());
+
         self.build_segments_if_needed(
-            prim_local_rect,
-            prim_local_clip_rect,
-            clip_chain,
+            prim_clip_rect,
+            &prim_info.clip_chain,
             frame_state,
             prim_store,
             resources,
-            scratch,
+            &mut scratch.segments,
+            &mut scratch.segment_instances,
         );
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
-            prim_local_rect.origin,
-            prim_local_clip_rect,
+            prim_info,
+            prim_clip_rect,
             root_spatial_node_index,
-            prim_bounding_rect,
             prim_context,
-            &clip_chain,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
-            clip_node_collector,
             prim_store,
             resources,
-            scratch,
+            &mut scratch.segments,
+            &mut scratch.segment_instances,
+            &mut scratch.clip_mask_instances,
         ) {
             if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
-        if clip_chain.needs_mask {
+        if prim_info.clip_chain.needs_mask {
             if let Some((device_rect, _)) = get_raster_rects(
-                clip_chain.pic_clip_rect,
+                prim_info.clip_chain.pic_clip_rect,
                 &pic_state.map_pic_to_raster,
                 &pic_state.map_raster_to_world,
-                prim_bounding_rect,
+                prim_info.clipped_world_rect,
                 frame_context.device_pixel_scale,
             ) {
                 let clip_task = RenderTask::new_mask(
                     device_rect,
-                    clip_chain.clips_range,
+                    prim_info.clip_chain.clips_range,
                     root_spatial_node_index,
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if self.is_chased() {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 // Set the global clip mask instance for this primitive.
                 let clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _);
                 scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id));
-                self.clip_task_index = clip_task_index;
+                prim_info.clip_task_index = clip_task_index;
                 frame_state.surfaces[pic_context.surface_index.0].tasks.push(clip_task_id);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
@@ -3381,15 +3512,15 @@ fn update_opacity_binding(
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<PrimitiveInstance>(), 112, "PrimitiveInstance size changed");
+    assert_eq!(mem::size_of::<PrimitiveInstance>(), 80, "PrimitiveInstance size changed");
     assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 40, "PrimitiveInstanceKind size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplate>(), 56, "PrimitiveTemplate size changed");
     assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 20, "PrimitiveTemplateKind size changed");
     assert_eq!(mem::size_of::<PrimitiveKey>(), 36, "PrimitiveKey size changed");
     assert_eq!(mem::size_of::<PrimitiveKeyKind>(), 5, "PrimitiveKeyKind size changed");
 }