Bug 1503442 - Update webrender to commit 62af01cdf4f09f8f403e4f66f067e8db7ae42329 (WR PR 3244). r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Wed, 31 Oct 2018 11:29:11 +0000
changeset 443710 50ce569555048d0d8dc29a6aa2d9a296acada5fb
parent 443709 5a1beccacebf371da00b6622586396df368bb245
child 443711 6fff6e1293898d93a9c6f923e2871fb5623eead4
push id34967
push useraiakab@mozilla.com
push dateWed, 31 Oct 2018 16:33:06 +0000
treeherdermozilla-central@1e15b59301a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1503442
milestone65.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 1503442 - Update webrender to commit 62af01cdf4f09f8f403e4f66f067e8db7ae42329 (WR PR 3244). r=kats Differential Revision: https://phabricator.services.mozilla.com/D10341
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -11,17 +11,17 @@ 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::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
-use prim_store::{EdgeAaSegmentMask, ImageSource};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind};
 use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BrushSegment, BorderSource, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
@@ -475,17 +475,17 @@ impl AlphaBatchBuilder {
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
         // Add each run in this picture to the batch.
-        for prim_instance in &pic.prim_instances {
+        for prim_instance in &pic.prim_list.prim_instances {
             self.add_prim_to_batch(
                 prim_instance,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
@@ -510,18 +510,16 @@ 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,
     ) {
-        let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
-
         if prim_instance.clipped_world_rect.is_none() {
             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
@@ -535,393 +533,314 @@ impl AlphaBatchBuilder {
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
         let bounding_rect = prim_instance.clipped_world_rect
                                          .as_ref()
                                          .expect("bug");
         let z_id = z_generator.next();
 
-        // If the primitive is internally decomposed into multiple sub-primitives we may not
-        // use some of the per-primitive data and get it from each sub-primitive instead.
-        let is_multiple_primitives = match prim.details {
-            PrimitiveDetails::Brush(ref brush) => {
-                match brush.kind {
-                    BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
-                    BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
-                    BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
-                    _ => false,
-                }
-            }
-            _ => false,
-        };
-
-        let prim_cache_address = if is_multiple_primitives {
-            GpuCacheAddress::invalid()
-        } else {
-            gpu_cache.get_address(&prim_instance.gpu_location)
-        };
-
         let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
-        let specified_blend_mode = prim_instance.get_blend_mode(&prim.details);
+        match prim_instance.kind {
+            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(&prim_instance.gpu_location);
+
+                let prim_header = PrimitiveHeader {
+                    local_rect: picture.local_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: prim_cache_address,
+                    clip_task_address,
+                    transform_id,
+                };
 
-        let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
-            prim_instance.clip_task_id.is_some() ||
-            transform_kind == TransformedRectKind::Complex
-        {
-            specified_blend_mode
-        } else {
-            BlendMode::None
-        };
+                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 pic_index = match prim_instance.kind {
+                                PrimitiveInstanceKind::Picture { pic_index } => pic_index,
+                                PrimitiveInstanceKind::Primitive { .. } => {
+                                    unreachable!();
+                                }
+                            };
+                            let pic = &ctx.prim_store.pictures[pic_index.0];
+
+                            let clip_task_address = prim_instance
+                                .clip_task_id
+                                .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
-        let prim_header = PrimitiveHeader {
-            local_rect: prim.local_rect,
-            local_clip_rect: prim_instance.combined_local_clip_rect,
-            task_address,
-            specific_prim_address: prim_cache_address,
-            clip_task_address,
-            transform_id,
-        };
+                            let prim_header = PrimitiveHeader {
+                                local_rect: pic.local_rect,
+                                local_clip_rect: prim_instance.combined_local_clip_rect,
+                                task_address,
+                                specific_prim_address: GpuCacheAddress::invalid(),
+                                clip_task_address,
+                                transform_id: child.transform_id,
+                            };
 
-        if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
-            println!("\ttask target {:?}", self.target_rect);
-            println!("\t{:?}", prim_header);
-        }
+                            let surface_index = pic
+                                .raster_config
+                                .as_ref()
+                                .expect("BUG: 3d primitive was not assigned a surface")
+                                .surface_index;
+                            let (uv_rect_address, _) = ctx
+                                .surfaces[surface_index.0]
+                                .surface
+                                .as_ref()
+                                .expect("BUG: no surface")
+                                .resolve(
+                                    render_tasks,
+                                    ctx.resource_cache,
+                                    gpu_cache,
+                                );
 
-        match prim.details {
-            PrimitiveDetails::Brush(ref brush) => {
-                match brush.kind {
-                    BrushKind::Picture { pic_index } => {
-                        let picture = &ctx.prim_store.pictures[pic_index.0];
-                        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_instances[child.anchor];
-                                    let pic_primitive = &ctx.prim_store.primitives[prim_instance.prim_index.0];
+                            let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                uv_rect_address.as_int(),
+                                0,
+                                0,
+                            ]);
+
+                            let key = BatchKey::new(
+                                BatchKind::SplitComposite,
+                                BlendMode::PremultipliedAlpha,
+                                BatchTextures::no_texture(),
+                            );
 
-                                    let clip_task_address = prim_instance
-                                        .clip_task_id
-                                        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
+                            let instance = SplitCompositeInstance::new(
+                                prim_header_index,
+                                child.gpu_address,
+                                z_id,
+                            );
 
-                                    let prim_header = PrimitiveHeader {
-                                        local_rect: pic_primitive.local_rect,
-                                        local_clip_rect: prim_instance.combined_local_clip_rect,
-                                        task_address,
-                                        specific_prim_address: GpuCacheAddress::invalid(),
-                                        clip_task_address,
-                                        transform_id: child.transform_id,
-                                    };
+                            self.batch_list.push_single_instance(
+                                key,
+                                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
+                                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,
+                    // Proceed for non-3D pictures.
+                    Picture3DContext::Out => ()
+                }
 
-						            let pic_index = match pic_primitive.details {
-						            	PrimitiveDetails::Brush(ref brush) => {
-						                    match brush.kind {
-						                        BrushKind::Picture { pic_index, .. } => pic_index,
-						                        _ => unreachable!(),
-						                    }
-						                }
-						                PrimitiveDetails::TextRun(..) => {
-						                    unreachable!();
-						                }
-						            };
-						            let pic = &ctx.prim_store.pictures[pic_index.0];
+                match picture.raster_config {
+                    Some(ref raster_config) => {
+                        let surface = ctx.surfaces[raster_config.surface_index.0]
+                            .surface
+                            .as_ref()
+                            .expect("bug: surface must be allocated by now");
+                        match raster_config.composite_mode {
+                            PictureCompositeMode::Filter(filter) => {
+                                assert!(filter.is_visible());
+                                match filter {
+                                    FilterOp::Blur(..) => {
+                                        let kind = BatchKind::Brush(
+                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
+                                        );
+                                        let (uv_rect_address, textures) = surface
+                                            .resolve(
+                                                render_tasks,
+                                                ctx.resource_cache,
+                                                gpu_cache,
+                                            );
+                                        let key = BatchKey::new(
+                                            kind,
+                                            non_segmented_blend_mode,
+                                            textures,
+                                        );
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                            ShaderColorMode::Image as i32,
+                                            RasterizationSpace::Screen as i32,
+                                            0,
+                                        ]);
 
-                                    let (uv_rect_address, _) = pic
-                                        .raster_config
-                                        .as_ref()
-                                        .expect("BUG: no raster config")
-                                        .surface
-                                        .as_ref()
-                                        .expect("BUG: no surface")
-                                        .resolve(
-                                            render_tasks,
-                                            ctx.resource_cache,
-                                            gpu_cache,
+                                        let instance = BrushInstance {
+                                            prim_header_index,
+                                            segment_index: 0,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
+                                            clip_task_address,
+                                            user_data: uv_rect_address.as_int(),
+                                        };
+
+                                        self.batch_list.push_single_instance(
+                                            key,
+                                            bounding_rect,
+                                            z_id,
+                                            PrimitiveInstanceData::from(instance),
+                                        );
+                                    }
+                                    FilterOp::DropShadow(offset, ..) => {
+                                        // Draw an instance of the shadow first, following by the content.
+
+                                        // Both the shadow and the content get drawn as a brush image.
+                                        let kind = BatchKind::Brush(
+                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                         );
 
-                                    let prim_header_index = prim_headers.push(&prim_header, z_id, [
-                                        uv_rect_address.as_int(),
-                                        0,
-                                        0,
-                                    ]);
+                                        // Gets the saved render task ID of the content, which is
+                                        // deeper in the render task tree than the direct child.
+                                        let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
+                                        let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                        debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
 
-                                    let key = BatchKey::new(
-                                        BatchKind::SplitComposite,
-                                        BlendMode::PremultipliedAlpha,
-                                        BatchTextures::no_texture(),
-                                    );
-
-                                    let instance = SplitCompositeInstance::new(
-                                        prim_header_index,
-                                        child.gpu_address,
-                                        z_id,
-                                    );
+                                        // Build BatchTextures for shadow/content
+                                        let shadow_textures = BatchTextures::render_target_cache();
+                                        let content_textures = BatchTextures {
+                                            colors: [
+                                                TextureSource::RenderTaskCache(saved_index),
+                                                TextureSource::Invalid,
+                                                TextureSource::Invalid,
+                                            ],
+                                        };
 
-                                    self.batch_list.push_single_instance(
-                                        key,
-                                        &prim_instance.clipped_world_rect.as_ref().expect("bug"),
-                                        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,
-                            // Proceed for non-3D pictures.
-                            Picture3DContext::Out => ()
-                        }
+                                        // Build batch keys for shadow/content
+                                        let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures);
+                                        let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures);
 
-                        match picture.raster_config {
-                            Some(ref raster_config) => {
-                                let surface = raster_config.surface
-                                                           .as_ref()
-                                                           .expect("bug: surface must be allocated by now");
-                                match raster_config.composite_mode {
-                                    PictureCompositeMode::Filter(filter) => {
-                                        assert!(filter.is_visible());
-                                        match filter {
-                                            FilterOp::Blur(..) => {
-                                                let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                                );
-                                                let (uv_rect_address, textures) = surface
-                                                    .resolve(
-                                                        render_tasks,
-                                                        ctx.resource_cache,
-                                                        gpu_cache,
-                                                    );
-                                                let key = BatchKey::new(
-                                                    kind,
-                                                    non_segmented_blend_mode,
-                                                    textures,
-                                                );
-                                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
-                                                    ShaderColorMode::Image as i32,
-                                                    RasterizationSpace::Screen as i32,
-                                                    0,
-                                                ]);
+                                        // Retrieve the UV rect addresses for shadow/content.
+                                        let cache_task_id = surface.resolve_render_task_id();
+                                        let shadow_uv_rect_address = render_tasks[cache_task_id]
+                                            .get_texture_address(gpu_cache)
+                                            .as_int();
+                                        let content_uv_rect_address = render_tasks[secondary_id]
+                                            .get_texture_address(gpu_cache)
+                                            .as_int();
+
+                                        // Get the GPU cache address of the extra data handle.
+                                        let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
 
-                                                let instance = BrushInstance {
-                                                    prim_header_index,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    clip_task_address,
-                                                    user_data: uv_rect_address.as_int(),
-                                                };
+                                        let z_id_shadow = z_id;
+                                        let z_id_content = z_generator.next();
 
-                                                self.batch_list.push_single_instance(
-                                                    key,
-                                                    bounding_rect,
-                                                    z_id,
-                                                    PrimitiveInstanceData::from(instance),
-                                                );
-                                            }
-                                            FilterOp::DropShadow(offset, ..) => {
-                                                // Draw an instance of the shadow first, following by the content.
+                                        let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
+                                            ShaderColorMode::Image as i32,
+                                            RasterizationSpace::Screen as i32,
+                                            0,
+                                        ]);
 
-                                                // Both the shadow and the content get drawn as a brush image.
-                                                let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
-                                                );
-
-                                                // Gets the saved render task ID of the content, which is
-                                                // deeper in the render task tree than the direct child.
-                                                let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
-                                                let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
-                                                debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
+                                        let shadow_rect = picture.local_rect.translate(&offset);
+                                        let shadow_clip_rect = picture.local_clip_rect.translate(&offset);
 
-                                                // Build BatchTextures for shadow/content
-                                                let shadow_textures = BatchTextures::render_target_cache();
-                                                let content_textures = BatchTextures {
-                                                    colors: [
-                                                        TextureSource::RenderTaskCache(saved_index),
-                                                        TextureSource::Invalid,
-                                                        TextureSource::Invalid,
-                                                    ],
-                                                };
-
-                                                // Build batch keys for shadow/content
-                                                let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures);
-                                                let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures);
+                                        let shadow_prim_header = PrimitiveHeader {
+                                            local_rect: shadow_rect,
+                                            local_clip_rect: shadow_clip_rect,
+                                            specific_prim_address: shadow_prim_address,
+                                            ..prim_header
+                                        };
 
-                                                // Retrieve the UV rect addresses for shadow/content.
-                                                let cache_task_id = surface.resolve_render_task_id();
-                                                let shadow_uv_rect_address = render_tasks[cache_task_id]
-                                                    .get_texture_address(gpu_cache)
-                                                    .as_int();
-                                                let content_uv_rect_address = render_tasks[secondary_id]
-                                                    .get_texture_address(gpu_cache)
-                                                    .as_int();
+                                        let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id_shadow, [
+                                            ShaderColorMode::Alpha as i32,
+                                            RasterizationSpace::Screen as i32,
+                                            0,
+                                        ]);
 
-                                                // Get the GPU cache address of the extra data handle.
-                                                let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handle);
-
-                                                let z_id_shadow = z_id;
-                                                let z_id_content = z_generator.next();
-
-                                                let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
-                                                    ShaderColorMode::Image as i32,
-                                                    RasterizationSpace::Screen as i32,
-                                                    0,
-                                                ]);
+                                        let shadow_instance = BrushInstance {
+                                            prim_header_index: shadow_prim_header_index,
+                                            clip_task_address,
+                                            segment_index: 0,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
+                                            user_data: shadow_uv_rect_address,
+                                        };
 
-                                                let shadow_rect = prim.local_rect.translate(&offset);
-                                                let shadow_clip_rect = prim.local_clip_rect.translate(&offset);
-
-                                                let shadow_prim_header = PrimitiveHeader {
-                                                    local_rect: shadow_rect,
-                                                    local_clip_rect: shadow_clip_rect,
-                                                    specific_prim_address: shadow_prim_address,
-                                                    ..prim_header
-                                                };
-
-                                                let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id_shadow, [
-                                                    ShaderColorMode::Alpha as i32,
-                                                    RasterizationSpace::Screen as i32,
-                                                    0,
-                                                ]);
-
-                                                let shadow_instance = BrushInstance {
-                                                    prim_header_index: shadow_prim_header_index,
-                                                    clip_task_address,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    user_data: shadow_uv_rect_address,
-                                                };
+                                        let content_instance = BrushInstance {
+                                            prim_header_index: content_prim_header_index,
+                                            clip_task_address,
+                                            segment_index: 0,
+                                            edge_flags: EdgeAaSegmentMask::empty(),
+                                            brush_flags: BrushFlags::empty(),
+                                            user_data: content_uv_rect_address,
+                                        };
 
-                                                let content_instance = BrushInstance {
-                                                    prim_header_index: content_prim_header_index,
-                                                    clip_task_address,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    user_data: content_uv_rect_address,
-                                                };
-
-                                                self.batch_list.push_single_instance(
-                                                    shadow_key,
-                                                    bounding_rect,
-                                                    z_id_shadow,
-                                                    PrimitiveInstanceData::from(shadow_instance),
-                                                );
-
-                                                self.batch_list.push_single_instance(
-                                                    content_key,
-                                                    bounding_rect,
-                                                    z_id_content,
-                                                    PrimitiveInstanceData::from(content_instance),
-                                                );
-                                            }
-                                            _ => {
-                                                let filter_mode = match filter {
-                                                    FilterOp::Identity => 1, // matches `Contrast(1)`
-                                                    FilterOp::Blur(..) => 0,
-                                                    FilterOp::Contrast(..) => 1,
-                                                    FilterOp::Grayscale(..) => 2,
-                                                    FilterOp::HueRotate(..) => 3,
-                                                    FilterOp::Invert(..) => 4,
-                                                    FilterOp::Saturate(..) => 5,
-                                                    FilterOp::Sepia(..) => 6,
-                                                    FilterOp::Brightness(..) => 7,
-                                                    FilterOp::Opacity(..) => 8,
-                                                    FilterOp::DropShadow(..) => 9,
-                                                    FilterOp::ColorMatrix(..) => 10,
-                                                    FilterOp::SrgbToLinear => 11,
-                                                    FilterOp::LinearToSrgb => 12,
-                                                };
+                                        self.batch_list.push_single_instance(
+                                            shadow_key,
+                                            bounding_rect,
+                                            z_id_shadow,
+                                            PrimitiveInstanceData::from(shadow_instance),
+                                        );
 
-                                                let user_data = match filter {
-                                                    FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
-                                                    FilterOp::Contrast(amount) |
-                                                    FilterOp::Grayscale(amount) |
-                                                    FilterOp::Invert(amount) |
-                                                    FilterOp::Saturate(amount) |
-                                                    FilterOp::Sepia(amount) |
-                                                    FilterOp::Brightness(amount) |
-                                                    FilterOp::Opacity(_, amount) => {
-                                                        (amount * 65536.0) as i32
-                                                    }
-                                                    FilterOp::SrgbToLinear | FilterOp::LinearToSrgb => 0,
-                                                    FilterOp::HueRotate(angle) => {
-                                                        (0.01745329251 * angle * 65536.0) as i32
-                                                    }
-                                                    // Go through different paths
-                                                    FilterOp::Blur(..) |
-                                                    FilterOp::DropShadow(..) => {
-                                                        unreachable!();
-                                                    }
-                                                    FilterOp::ColorMatrix(_) => {
-                                                        picture.extra_gpu_data_handle.as_int(gpu_cache)
-                                                    }
-                                                };
-
-                                                let (uv_rect_address, textures) = surface
-                                                    .resolve(
-                                                        render_tasks,
-                                                        ctx.resource_cache,
-                                                        gpu_cache,
-                                                    );
+                                        self.batch_list.push_single_instance(
+                                            content_key,
+                                            bounding_rect,
+                                            z_id_content,
+                                            PrimitiveInstanceData::from(content_instance),
+                                        );
+                                    }
+                                    _ => {
+                                        let filter_mode = match filter {
+                                            FilterOp::Identity => 1, // matches `Contrast(1)`
+                                            FilterOp::Blur(..) => 0,
+                                            FilterOp::Contrast(..) => 1,
+                                            FilterOp::Grayscale(..) => 2,
+                                            FilterOp::HueRotate(..) => 3,
+                                            FilterOp::Invert(..) => 4,
+                                            FilterOp::Saturate(..) => 5,
+                                            FilterOp::Sepia(..) => 6,
+                                            FilterOp::Brightness(..) => 7,
+                                            FilterOp::Opacity(..) => 8,
+                                            FilterOp::DropShadow(..) => 9,
+                                            FilterOp::ColorMatrix(..) => 10,
+                                            FilterOp::SrgbToLinear => 11,
+                                            FilterOp::LinearToSrgb => 12,
+                                        };
 
-                                                let key = BatchKey::new(
-                                                    BatchKind::Brush(BrushBatchKind::Blend),
-                                                    BlendMode::PremultipliedAlpha,
-                                                    textures,
-                                                );
-
-                                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
-                                                    uv_rect_address.as_int(),
-                                                    filter_mode,
-                                                    user_data,
-                                                ]);
+                                        let user_data = match filter {
+                                            FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
+                                            FilterOp::Contrast(amount) |
+                                            FilterOp::Grayscale(amount) |
+                                            FilterOp::Invert(amount) |
+                                            FilterOp::Saturate(amount) |
+                                            FilterOp::Sepia(amount) |
+                                            FilterOp::Brightness(amount) |
+                                            FilterOp::Opacity(_, amount) => {
+                                                (amount * 65536.0) as i32
+                                            }
+                                            FilterOp::SrgbToLinear | FilterOp::LinearToSrgb => 0,
+                                            FilterOp::HueRotate(angle) => {
+                                                (0.01745329251 * angle * 65536.0) as i32
+                                            }
+                                            // Go through different paths
+                                            FilterOp::Blur(..) |
+                                            FilterOp::DropShadow(..) => {
+                                                unreachable!();
+                                            }
+                                            FilterOp::ColorMatrix(_) => {
+                                                picture.extra_gpu_data_handle.as_int(gpu_cache)
+                                            }
+                                        };
 
-                                                let instance = BrushInstance {
-                                                    prim_header_index,
-                                                    clip_task_address,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::empty(),
-                                                    user_data: 0,
-                                                };
-
-                                                self.batch_list.push_single_instance(
-                                                    key,
-                                                    bounding_rect,
-                                                    z_id,
-                                                    PrimitiveInstanceData::from(instance),
-                                                );
-                                            }
-                                        }
-                                    }
-                                    PictureCompositeMode::MixBlend(mode) => {
-                                        let cache_task_id = surface.resolve_render_task_id();
-                                        let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
+                                        let (uv_rect_address, textures) = surface
+                                            .resolve(
+                                                render_tasks,
+                                                ctx.resource_cache,
+                                                gpu_cache,
+                                            );
 
                                         let key = BatchKey::new(
-                                            BatchKind::Brush(
-                                                BrushBatchKind::MixBlend {
-                                                    task_id,
-                                                    source_id: cache_task_id,
-                                                    backdrop_id,
-                                                },
-                                            ),
+                                            BatchKind::Brush(BrushBatchKind::Blend),
                                             BlendMode::PremultipliedAlpha,
-                                            BatchTextures::no_texture(),
+                                            textures,
                                         );
-                                        let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
-                                        let source_task_address = render_tasks.get_task_address(cache_task_id);
+
                                         let prim_header_index = prim_headers.push(&prim_header, z_id, [
-                                            mode as u32 as i32,
-                                            backdrop_task_address.0 as i32,
-                                            source_task_address.0 as i32,
+                                            uv_rect_address.as_int(),
+                                            filter_mode,
+                                            user_data,
                                         ]);
 
                                         let instance = BrushInstance {
                                             prim_header_index,
                                             clip_task_address,
                                             segment_index: 0,
                                             edge_flags: EdgeAaSegmentMask::empty(),
                                             brush_flags: BrushFlags::empty(),
@@ -930,256 +849,351 @@ impl AlphaBatchBuilder {
 
                                         self.batch_list.push_single_instance(
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
-                                    PictureCompositeMode::Blit => {
-                                        let cache_task_id = surface.resolve_render_task_id();
-                                        let kind = BatchKind::Brush(
-                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                        );
-                                        let key = BatchKey::new(
-                                            kind,
-                                            non_segmented_blend_mode,
-                                            BatchTextures::render_target_cache(),
-                                        );
-
-                                        let uv_rect_address = render_tasks[cache_task_id]
-                                            .get_texture_address(gpu_cache)
-                                            .as_int();
-                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
-                                            ShaderColorMode::Image as i32,
-                                            RasterizationSpace::Screen as i32,
-                                            0,
-                                        ]);
-
-                                        let instance = BrushInstance {
-                                            prim_header_index,
-                                            clip_task_address,
-                                            segment_index: 0,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags: BrushFlags::empty(),
-                                            user_data: uv_rect_address,
-                                        };
-
-                                        self.batch_list.push_single_instance(
-                                            key,
-                                            bounding_rect,
-                                            z_id,
-                                            PrimitiveInstanceData::from(instance),
-                                        );
-                                    }
                                 }
                             }
-                            None => {
-                                // If this picture is being drawn into an existing target (i.e. with
-                                // no composition operation), recurse and add to the current batch list.
-                                self.add_pic_to_batch(
-                                    picture,
-                                    task_id,
-                                    ctx,
-                                    gpu_cache,
-                                    render_tasks,
-                                    deferred_resolves,
-                                    prim_headers,
-                                    transforms,
-                                    root_spatial_node_index,
-                                    z_generator,
+                            PictureCompositeMode::MixBlend(mode) => {
+                                let cache_task_id = surface.resolve_render_task_id();
+                                let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
+
+                                let key = BatchKey::new(
+                                    BatchKind::Brush(
+                                        BrushBatchKind::MixBlend {
+                                            task_id,
+                                            source_id: cache_task_id,
+                                            backdrop_id,
+                                        },
+                                    ),
+                                    BlendMode::PremultipliedAlpha,
+                                    BatchTextures::no_texture(),
+                                );
+                                let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
+                                let source_task_address = render_tasks.get_task_address(cache_task_id);
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                    mode as u32 as i32,
+                                    backdrop_task_address.0 as i32,
+                                    source_task_address.0 as i32,
+                                ]);
+
+                                let instance = BrushInstance {
+                                    prim_header_index,
+                                    clip_task_address,
+                                    segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags: BrushFlags::empty(),
+                                    user_data: 0,
+                                };
+
+                                self.batch_list.push_single_instance(
+                                    key,
+                                    bounding_rect,
+                                    z_id,
+                                    PrimitiveInstanceData::from(instance),
                                 );
                             }
-                        }
-                    }
-                    BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
-                        for tile in visible_tiles {
-                            if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
-                                    ctx.resource_cache,
-                                    gpu_cache,
-                                    deferred_resolves,
-                                    request.with_tile(tile.tile_offset),
-                            ) {
-                                let prim_cache_address = gpu_cache.get_address(&tile.handle);
-                                let prim_header = PrimitiveHeader {
-                                    specific_prim_address: prim_cache_address,
-                                    local_rect: tile.local_rect,
-                                    local_clip_rect: tile.local_clip_rect,
-                                    ..prim_header
-                                };
-                                let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
+                            PictureCompositeMode::Blit => {
+                                let cache_task_id = surface.resolve_render_task_id();
+                                let kind = BatchKind::Brush(
+                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
+                                );
+                                let key = BatchKey::new(
+                                    kind,
+                                    non_segmented_blend_mode,
+                                    BatchTextures::render_target_cache(),
+                                );
 
-                                self.add_image_tile_to_batch(
-                                    batch_kind,
-                                    specified_blend_mode,
-                                    textures,
+                                let uv_rect_address = render_tasks[cache_task_id]
+                                    .get_texture_address(gpu_cache)
+                                    .as_int();
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                    ShaderColorMode::Image as i32,
+                                    RasterizationSpace::Screen as i32,
+                                    0,
+                                ]);
+
+                                let instance = BrushInstance {
                                     prim_header_index,
                                     clip_task_address,
+                                    segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags: BrushFlags::empty(),
+                                    user_data: uv_rect_address,
+                                };
+
+                                self.batch_list.push_single_instance(
+                                    key,
                                     bounding_rect,
-                                    tile.edge_flags,
-                                    uv_rect_address,
                                     z_id,
+                                    PrimitiveInstanceData::from(instance),
                                 );
                             }
                         }
                     }
-                    BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
-                        add_gradient_tiles(
-                            visible_tiles,
-                            stops_handle,
-                            BrushBatchKind::LinearGradient,
-                            specified_blend_mode,
-                            bounding_rect,
-                            clip_task_address,
+                    None => {
+                        // If this picture is being drawn into an existing target (i.e. with
+                        // no composition operation), recurse and add to the current batch list.
+                        self.add_pic_to_batch(
+                            picture,
+                            task_id,
+                            ctx,
                             gpu_cache,
-                            &mut self.batch_list,
-                            &prim_header,
+                            render_tasks,
+                            deferred_resolves,
                             prim_headers,
-                            z_id,
-                        );
-                    }
-                    BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
-                        add_gradient_tiles(
-                            visible_tiles,
-                            stops_handle,
-                            BrushBatchKind::RadialGradient,
-                            specified_blend_mode,
-                            bounding_rect,
-                            clip_task_address,
-                            gpu_cache,
-                            &mut self.batch_list,
-                            &prim_header,
-                            prim_headers,
-                            z_id,
+                            transforms,
+                            root_spatial_node_index,
+                            z_generator,
                         );
                     }
-                    _ => {
-                        if let Some(params) = brush.get_batch_params(
-                            ctx.resource_cache,
-                            gpu_cache,
-                            deferred_resolves,
-                            ctx.prim_store.chase_id == Some(prim_instance.prim_index),
-                        ) {
-                            let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data);
-                            if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
-                                println!("\t{:?} {:?}, task relative bounds {:?}",
-                                    params.batch_kind, prim_header_index, bounding_rect);
-                            }
-
-                            self.add_brush_to_batch(
-                                brush,
-                                &params,
-                                prim_instance,
-                                specified_blend_mode,
-                                non_segmented_blend_mode,
-                                prim_header_index,
-                                clip_task_address,
-                                bounding_rect,
-                                transform_kind,
-                                render_tasks,
-                                z_id,
-                            );
-                        }
-                    }
                 }
             }
-            PrimitiveDetails::TextRun(ref text_cpu) => {
-                let subpx_dir = text_cpu.used_font.get_subpx_dir();
+            PrimitiveInstanceKind::Primitive { prim_index } => {
+                let prim = &ctx.prim_store.primitives[prim_index.0];
 
-                let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
-                let alpha_batch_list = &mut self.batch_list.alpha_batch_list;
+                // If the primitive is internally decomposed into multiple sub-primitives we may not
+                // use some of the per-primitive data and get it from each sub-primitive instead.
+                let is_multiple_primitives = match prim.details {
+                    PrimitiveDetails::Brush(ref brush) => {
+                        match brush.kind {
+                            BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                            BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                            BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(),
+                            _ => false,
+                        }
+                    }
+                    _ => false,
+                };
 
-                ctx.resource_cache.fetch_glyphs(
-                    text_cpu.used_font.clone(),
-                    &text_cpu.glyph_keys,
-                    glyph_fetch_buffer,
-                    gpu_cache,
-                    |texture_id, mut glyph_format, glyphs| {
-                        debug_assert_ne!(texture_id, TextureSource::Invalid);
+                let prim_cache_address = if is_multiple_primitives {
+                    GpuCacheAddress::invalid()
+                } else {
+                    gpu_cache.get_address(&prim_instance.gpu_location)
+                };
+
+                let specified_blend_mode = prim_instance.get_blend_mode(&prim.details);
 
-                        // Ignore color and only sample alpha when shadowing.
-                        if text_cpu.shadow {
-                            glyph_format = glyph_format.ignore_color();
-                        }
-
-                        let subpx_dir = subpx_dir.limit_by(glyph_format);
+                let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
+                    prim_instance.clip_task_id.is_some() ||
+                    transform_kind == TransformedRectKind::Complex
+                {
+                    specified_blend_mode
+                } else {
+                    BlendMode::None
+                };
 
-                        let textures = BatchTextures {
-                            colors: [
-                                texture_id,
-                                TextureSource::Invalid,
-                                TextureSource::Invalid,
-                            ],
-                        };
+                let prim_header = PrimitiveHeader {
+                    local_rect: prim.local_rect,
+                    local_clip_rect: prim_instance.combined_local_clip_rect,
+                    task_address,
+                    specific_prim_address: prim_cache_address,
+                    clip_task_address,
+                    transform_id,
+                };
 
-                        let kind = BatchKind::TextRun(glyph_format);
+                if prim_instance.is_chased() {
+                    println!("\ttask target {:?}", self.target_rect);
+                    println!("\t{:?}", prim_header);
+                }
 
-                        let (blend_mode, color_mode) = match glyph_format {
-                            GlyphFormat::Subpixel |
-                            GlyphFormat::TransformedSubpixel => {
-                                if text_cpu.used_font.bg_color.a != 0 {
-                                    (
-                                        BlendMode::SubpixelWithBgColor,
-                                        ShaderColorMode::FromRenderPassMode,
-                                    )
-                                } else if ctx.use_dual_source_blending {
-                                    (
-                                        BlendMode::SubpixelDualSource,
-                                        ShaderColorMode::SubpixelDualSource,
-                                    )
-                                } else {
-                                    (
-                                        BlendMode::SubpixelConstantTextColor(text_cpu.used_font.color.into()),
-                                        ShaderColorMode::SubpixelConstantTextColor,
-                                    )
+                match prim.details {
+                    PrimitiveDetails::Brush(ref brush) => {
+                        match brush.kind {
+                            BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                                for tile in visible_tiles {
+                                    if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params(
+                                            ctx.resource_cache,
+                                            gpu_cache,
+                                            deferred_resolves,
+                                            request.with_tile(tile.tile_offset),
+                                    ) {
+                                        let prim_cache_address = gpu_cache.get_address(&tile.handle);
+                                        let prim_header = PrimitiveHeader {
+                                            specific_prim_address: prim_cache_address,
+                                            local_rect: tile.local_rect,
+                                            local_clip_rect: tile.local_clip_rect,
+                                            ..prim_header
+                                        };
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, user_data);
+
+                                        self.add_image_tile_to_batch(
+                                            batch_kind,
+                                            specified_blend_mode,
+                                            textures,
+                                            prim_header_index,
+                                            clip_task_address,
+                                            bounding_rect,
+                                            tile.edge_flags,
+                                            uv_rect_address,
+                                            z_id,
+                                        );
+                                    }
                                 }
                             }
-                            GlyphFormat::Alpha |
-                            GlyphFormat::TransformedAlpha => {
-                                (
-                                    BlendMode::PremultipliedAlpha,
-                                    ShaderColorMode::Alpha,
-                                )
+                            BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                                add_gradient_tiles(
+                                    visible_tiles,
+                                    stops_handle,
+                                    BrushBatchKind::LinearGradient,
+                                    specified_blend_mode,
+                                    bounding_rect,
+                                    clip_task_address,
+                                    gpu_cache,
+                                    &mut self.batch_list,
+                                    &prim_header,
+                                    prim_headers,
+                                    z_id,
+                                );
+                            }
+                            BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                                add_gradient_tiles(
+                                    visible_tiles,
+                                    stops_handle,
+                                    BrushBatchKind::RadialGradient,
+                                    specified_blend_mode,
+                                    bounding_rect,
+                                    clip_task_address,
+                                    gpu_cache,
+                                    &mut self.batch_list,
+                                    &prim_header,
+                                    prim_headers,
+                                    z_id,
+                                );
                             }
-                            GlyphFormat::Bitmap => {
-                                (
-                                    BlendMode::PremultipliedAlpha,
-                                    ShaderColorMode::Bitmap,
-                                )
+                            _ => {
+                                if let Some(params) = brush.get_batch_params(
+                                    ctx.resource_cache,
+                                    gpu_cache,
+                                    deferred_resolves,
+                                    prim_instance,
+                                ) {
+                                    let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data);
+                                    if prim_instance.is_chased() {
+                                        println!("\t{:?} {:?}, task relative bounds {:?}",
+                                            params.batch_kind, prim_header_index, bounding_rect);
+                                    }
+
+                                    self.add_brush_to_batch(
+                                        brush,
+                                        &params,
+                                        prim_instance,
+                                        specified_blend_mode,
+                                        non_segmented_blend_mode,
+                                        prim_header_index,
+                                        clip_task_address,
+                                        bounding_rect,
+                                        transform_kind,
+                                        render_tasks,
+                                        z_id,
+                                    );
+                                }
                             }
-                            GlyphFormat::ColorBitmap => {
-                                (
-                                    BlendMode::PremultipliedAlpha,
-                                    ShaderColorMode::ColorBitmap,
-                                )
-                            }
-                        };
+                        }
+                    }
+                    PrimitiveDetails::TextRun(ref text_cpu) => {
+                        let subpx_dir = text_cpu.used_font.get_subpx_dir();
+
+                        let glyph_fetch_buffer = &mut self.glyph_fetch_buffer;
+                        let alpha_batch_list = &mut self.batch_list.alpha_batch_list;
+
+                        ctx.resource_cache.fetch_glyphs(
+                            text_cpu.used_font.clone(),
+                            &text_cpu.glyph_keys,
+                            glyph_fetch_buffer,
+                            gpu_cache,
+                            |texture_id, mut glyph_format, glyphs| {
+                                debug_assert_ne!(texture_id, TextureSource::Invalid);
 
-                        let prim_header_index = prim_headers.push(&prim_header, z_id, [0; 3]);
-                        let key = BatchKey::new(kind, blend_mode, textures);
-                        let base_instance = GlyphInstance::new(
-                            prim_header_index,
-                        );
-                        let batch = alpha_batch_list.set_params_and_get_batch(
-                            key,
-                            bounding_rect,
-                            z_id,
-                        );
+                                // Ignore color and only sample alpha when shadowing.
+                                if text_cpu.shadow {
+                                    glyph_format = glyph_format.ignore_color();
+                                }
+
+                                let subpx_dir = subpx_dir.limit_by(glyph_format);
+
+                                let textures = BatchTextures {
+                                    colors: [
+                                        texture_id,
+                                        TextureSource::Invalid,
+                                        TextureSource::Invalid,
+                                    ],
+                                };
+
+                                let kind = BatchKind::TextRun(glyph_format);
 
-                        for glyph in glyphs {
-                            batch.push(base_instance.build(
-                                glyph.index_in_text_run,
-                                glyph.uv_rect_address.as_int(),
-                                (subpx_dir as u32 as i32) << 16 |
-                                (color_mode as u32 as i32),
-                            ));
-                        }
-                    },
-                );
+                                let (blend_mode, color_mode) = match glyph_format {
+                                    GlyphFormat::Subpixel |
+                                    GlyphFormat::TransformedSubpixel => {
+                                        if text_cpu.used_font.bg_color.a != 0 {
+                                            (
+                                                BlendMode::SubpixelWithBgColor,
+                                                ShaderColorMode::FromRenderPassMode,
+                                            )
+                                        } else if ctx.use_dual_source_blending {
+                                            (
+                                                BlendMode::SubpixelDualSource,
+                                                ShaderColorMode::SubpixelDualSource,
+                                            )
+                                        } else {
+                                            (
+                                                BlendMode::SubpixelConstantTextColor(text_cpu.used_font.color.into()),
+                                                ShaderColorMode::SubpixelConstantTextColor,
+                                            )
+                                        }
+                                    }
+                                    GlyphFormat::Alpha |
+                                    GlyphFormat::TransformedAlpha => {
+                                        (
+                                            BlendMode::PremultipliedAlpha,
+                                            ShaderColorMode::Alpha,
+                                        )
+                                    }
+                                    GlyphFormat::Bitmap => {
+                                        (
+                                            BlendMode::PremultipliedAlpha,
+                                            ShaderColorMode::Bitmap,
+                                        )
+                                    }
+                                    GlyphFormat::ColorBitmap => {
+                                        (
+                                            BlendMode::PremultipliedAlpha,
+                                            ShaderColorMode::ColorBitmap,
+                                        )
+                                    }
+                                };
+
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [0; 3]);
+                                let key = BatchKey::new(kind, blend_mode, textures);
+                                let base_instance = GlyphInstance::new(
+                                    prim_header_index,
+                                );
+                                let batch = alpha_batch_list.set_params_and_get_batch(
+                                    key,
+                                    bounding_rect,
+                                    z_id,
+                                );
+
+                                for glyph in glyphs {
+                                    batch.push(base_instance.build(
+                                        glyph.index_in_text_run,
+                                        glyph.uv_rect_address.as_int(),
+                                        (subpx_dir as u32 as i32) << 16 |
+                                        (color_mode as u32 as i32),
+                                    ));
+                                }
+                            },
+                        );
+                    }
+                }
+
             }
         }
     }
 
     fn add_image_tile_to_batch(
         &mut self,
         batch_kind: BrushBatchKind,
         blend_mode: BlendMode,
@@ -1496,17 +1510,17 @@ impl BrushBatchParameters {
 }
 
 impl BrushPrimitive {
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        is_chased: bool,
+        prim_instance: &PrimitiveInstance,
     ) -> Option<BrushBatchParameters> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
@@ -1518,17 +1532,17 @@ impl BrushPrimitive {
                         let rt_handle = handle
                             .as_ref()
                             .expect("bug: render task handle not allocated");
                         let rt_cache_entry = resource_cache
                             .get_cached_render_task(rt_handle);
                         resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
                     }
                 };
-                if cfg!(debug_assertions) && is_chased {
+                if prim_instance.is_chased() {
                     println!("\tsource {:?}", cache_item);
                 }
 
                 if cache_item.texture_id == TextureSource::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
@@ -1628,19 +1642,16 @@ impl BrushPrimitive {
                                 RasterizationSpace::Local as i32,
                                 0,
                             ],
                             segment_data,
                         ))
                     }
                 }
             }
-            BrushKind::Picture { .. } => {
-                panic!("bug: get_batch_key is handled at higher level for pictures");
-            }
             BrushKind::Solid { .. } => {
                 Some(BrushBatchParameters::shared(
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                     0,
                 ))
             }
@@ -1758,18 +1769,17 @@ impl PrimitiveInstance {
                             AlphaType::Alpha => BlendMode::Alpha,
                         }
                     }
                     BrushKind::LineDecoration { .. } |
                     BrushKind::Solid { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } |
-                    BrushKind::Border { .. } |
-                    BrushKind::Picture { .. } => {
+                    BrushKind::Border { .. } => {
                         BlendMode::PremultipliedAlpha
                     }
                 }
             }
         }
     }
 
     pub fn is_cacheable(
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -673,16 +673,62 @@ impl ClipStore {
             has_non_root_coord_system,
             has_non_local_clips,
             local_clip_rect,
             pic_clip_rect,
             needs_mask,
         })
     }
 
+    /// Walk the clip chain of a primitive, and calculate a minimal
+    /// local clip rect for the primitive.
+    #[allow(dead_code)]
+    pub fn build_local_clip_rect(
+        &self,
+        prim_clip_rect: LayoutRect,
+        spatial_node_index: SpatialNodeIndex,
+        clip_chain_id: ClipChainId,
+        clip_interner: &ClipDataInterner,
+    ) -> LayoutRect {
+        let mut clip_rect = prim_clip_rect;
+        let mut current_clip_chain_id = clip_chain_id;
+
+        // for each clip chain node
+        while current_clip_chain_id != ClipChainId::NONE {
+            let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
+
+            // If the clip chain node and the primitive share a spatial node,
+            // then by definition the clip can't move relative to the primitive,
+            // due to scrolling or transform animation. When that constraint
+            // holds, it's fine to include the local clip rect of this
+            // clip node in the local clip rect of the primitive itself. This
+            // is used to minimize the size of any render target allocations
+            // if this primitive ends up being part of an off-screen surface.
+
+            if clip_chain_node.spatial_node_index == spatial_node_index {
+                let clip_data = &clip_interner[clip_chain_node.handle];
+
+                // TODO(gw): For now, if a clip results in the local
+                //           rect of this primitive becoming zero, just
+                //           ignore and continue (it will be culled later
+                //           on). Technically, we could add a code path
+                //           here to drop the primitive immediately, but
+                //           that complicates some of the existing callers
+                //           of build_local_clip_rect.
+                clip_rect = clip_rect
+                    .intersection(&clip_data.clip_rect)
+                    .unwrap_or(LayoutRect::zero());
+            }
+
+            current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
+        }
+
+        clip_rect
+    }
+
     /// Reports the heap usage of this clip store.
     pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
         let mut size = 0;
         unsafe {
             size += op(self.clip_chain_nodes.as_ptr() as *const c_void);
             size += op(self.clip_node_instances.as_ptr() as *const c_void);
             size += op(self.clip_node_info.as_ptr() as *const c_void);
         }
@@ -753,22 +799,17 @@ impl ClipRegion<Option<ComplexClipRegion
 }
 
 /// The information about an interned clip item that
 /// is stored and available in the scene builder
 /// thread.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipItemSceneData {
-    // TODO(gw): We will store the local clip rect of
-    //           the clip item here. This will allow
-    //           calculation of the local clip rect
-    //           for a primitive and its clip chain
-    //           during scene building, rather than
-    //           frame building.
+    pub clip_rect: LayoutRect,
 }
 
 // The ClipItemKey is a hashable representation of the contents
 // of a clip item. It is used during interning to de-duplicate
 // clip nodes between frames and display lists. This allows quick
 // comparison of clip node equality by handle, and also allows
 // the uploaded GPU cache handle to be retained between display lists.
 // TODO(gw): Maybe we should consider constructing these directly
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -19,20 +19,20 @@ use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{Picture3DContext, PictureCompositeMode, PictureIdGenerator, PicturePrimitive, PrimitiveList};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance, PrimitiveDataInterner};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore};
-use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive, PictureIndex};
+use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive, PictureIndex, register_prim_chase_id};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
@@ -798,16 +798,22 @@ impl<'a> DisplayListFlattener<'a> {
 
             for item in clip_items {
                 // Intern this clip item, and store the handle
                 // in the clip chain node.
                 let handle = self.resources
                     .clip_interner
                     .intern(&item, || {
                         ClipItemSceneData {
+                            // The only type of clip items that exist in the per-primitive
+                            // clip items are box shadows, and they don't contribute a
+                            // local clip rect, so just provide max_rect here. In the future,
+                            // we intend to make box shadows a primitive effect, in which
+                            // case the entire clip_items API on primitives can be removed.
+                            clip_rect: LayoutRect::max_rect(),
                         }
                     });
 
                 clip_chain_id = self.clip_store
                                     .add_clip_chain_node(
                                         handle,
                                         spatial_node_index,
                                         clip_chain_id,
@@ -823,33 +829,48 @@ impl<'a> DisplayListFlattener<'a> {
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
     ) -> PrimitiveInstance {
-        let prim_key = PrimitiveKey::new(info.is_backface_visible);
+        let prim_key = PrimitiveKey::new(
+            info.is_backface_visible,
+            info.rect,
+            info.clip_rect,
+        );
+
+        // Get a tight bounding / culling rect for this primitive
+        // from its local rect intersection with minimal local
+        // clip rect.
+        let culling_rect = info.clip_rect
+            .intersection(&info.rect)
+            .unwrap_or(LayoutRect::zero());
 
         let prim_data_handle = self.resources
             .prim_interner
             .intern(&prim_key, || {
                 PrimitiveSceneData {
+                    culling_rect,
+                    is_backface_visible: info.is_backface_visible,
                 }
             });
 
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
             container,
         );
 
         PrimitiveInstance::new(
-            prim_index,
+            PrimitiveInstanceKind::Primitive {
+                prim_index,
+            },
             prim_data_handle,
             clip_chain_id,
             spatial_node_index,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
@@ -875,17 +896,17 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_instance: PrimitiveInstance,
     ) {
         // Add primitive to the top-most stacking context on the stack.
-        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
+        if prim_instance.is_chased() {
             println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
         let stacking_context = self.sc_stack.last_mut().unwrap();
         stacking_context.primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
@@ -906,20 +927,20 @@ impl<'a> DisplayListFlattener<'a> {
                     clip_and_scroll.clip_chain_id,
                 );
                 let prim_instance = self.create_primitive(
                     info,
                     clip_chain_id,
                     clip_and_scroll.spatial_node_index,
                     container,
                 );
-                if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                    println!("Chasing {:?} by local rect", prim_instance.prim_index);
-                    self.prim_store.chase_id = Some(prim_instance.prim_index);
-                }
+                self.register_chase_primitive_by_rect(
+                    &info.rect,
+                    &prim_instance,
+                );
                 self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
                 self.add_primitive_to_draw_list(prim_instance);
             }
         } else {
             debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives");
 
             // There is an active shadow context. Store as a pending primitive
             // for processing during pop_all_shadows.
@@ -961,16 +982,17 @@ impl<'a> DisplayListFlattener<'a> {
         // an intermediate surface for plane splitting purposes.
         let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
             Some(sc) => {
                 // Cut the sequence of flat children before starting a child stacking context,
                 // so that the relative order between them and our current SC is preserved.
                 let extra_instance = sc.cut_flat_item_sequence(
                     &mut self.picture_id_generator,
                     &mut self.prim_store,
+                    &self.resources.prim_interner,
                 );
                 (sc.is_3d(), extra_instance)
             },
             None => (false, None),
         };
 
         if let Some(instance) = extra_3d_instance {
             self.add_primitive_instance_to_3d_root(instance);
@@ -1007,21 +1029,28 @@ impl<'a> DisplayListFlattener<'a> {
         };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
         let should_isolate = clipping_node.is_some();
 
-        let prim_key = PrimitiveKey::new(is_backface_visible);
+        let prim_key = PrimitiveKey::new(
+            is_backface_visible,
+            LayoutRect::zero(),
+            LayoutRect::max_rect(),
+        );
+
         let primitive_data_handle = self.resources
             .prim_interner
             .intern(&prim_key, || {
                 PrimitiveSceneData {
+                    culling_rect: LayoutRect::zero(),
+                    is_backface_visible,
                 }
             }
         );
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
             primitives: Vec::new(),
@@ -1074,162 +1103,139 @@ impl<'a> DisplayListFlattener<'a> {
                 },
                 stacking_context.frame_output_pipeline_id
             ),
         };
 
         // Add picture for this actual stacking context contents to render into.
         let prim_list = PrimitiveList::new(
             stacking_context.primitives,
-            &self.prim_store.primitives,
+            &self.resources.prim_interner,
         );
         let leaf_picture = PicturePrimitive::new_image(
             self.picture_id_generator.next(),
             leaf_composite_mode,
             leaf_context_3d,
             stacking_context.pipeline_id,
             leaf_output_pipeline_id,
             true,
             stacking_context.requested_raster_space,
             prim_list,
             stacking_context.spatial_node_index,
+            max_clip,
         );
         let leaf_pic_index = self.prim_store.create_picture(leaf_picture);
 
-        // Create a brush primitive that draws this picture.
-        let leaf_prim = BrushPrimitive::new_picture(leaf_pic_index);
-
-        // Add the brush to the parent picture.
-        let leaf_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            PrimitiveContainer::Brush(leaf_prim),
-        );
-
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
 
-        if cfg!(debug_assertions) && Some(leaf_prim_index) == self.prim_store.chase_id {
-            println!("\tis a leaf primitive for a stacking context");
-        }
-
         let mut current_pic_index = leaf_pic_index;
         let mut cur_instance = PrimitiveInstance::new(
-            leaf_prim_index,
+            PrimitiveInstanceKind::Picture { pic_index: leaf_pic_index },
             stacking_context.primitive_data_handle,
             stacking_context.clip_chain_id,
             stacking_context.spatial_node_index,
         );
 
+        if cur_instance.is_chased() {
+            println!("\tis a leaf primitive for a stacking context");
+        }
+
         // If establishing a 3d context, the `cur_instance` represents
         // a picture with all the *trailing* immediate children elements.
         // We append this to the preserve-3D picture set and make a container picture of them.
         if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
             prims.push(cur_instance.clone());
 
             let prim_list = PrimitiveList::new(
                 prims,
-                &self.prim_store.primitives,
+                &self.resources.prim_interner,
             );
 
             // This is the acttual picture representing our 3D hierarchy root.
             let container_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 None,
                 Picture3DContext::In {
                     root_data: Some(Vec::new()),
                     ancestor_index,
                 },
                 stacking_context.pipeline_id,
                 stacking_context.frame_output_pipeline_id,
                 true,
                 stacking_context.requested_raster_space,
                 prim_list,
                 stacking_context.spatial_node_index,
+                max_clip,
             );
 
             current_pic_index = self.prim_store.create_picture(container_picture);
-            let container_prim = BrushPrimitive::new_picture(current_pic_index);
 
-            cur_instance.prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                PrimitiveContainer::Brush(container_prim),
-            );
+            cur_instance.kind = PrimitiveInstanceKind::Picture { pic_index: current_pic_index };
         }
 
         // For each filter, create a new image with that composite mode.
         for filter in &stacking_context.composite_ops.filters {
             let filter = filter.sanitize();
             let prim_list = PrimitiveList::new(
                 vec![cur_instance.clone()],
-                &self.prim_store.primitives,
+                &self.resources.prim_interner,
             );
 
             let filter_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::Filter(filter)),
                 Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
                 prim_list,
                 stacking_context.spatial_node_index,
+                max_clip,
             );
             let filter_pic_index = self.prim_store.create_picture(filter_picture);
             current_pic_index = filter_pic_index;
 
-            let filter_prim = BrushPrimitive::new_picture(filter_pic_index);
+            cur_instance.kind = PrimitiveInstanceKind::Picture { pic_index: current_pic_index };
 
-            cur_instance.prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                PrimitiveContainer::Brush(filter_prim),
-            );
-
-            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+            if cur_instance.is_chased() {
                 println!("\tis a composite picture for a stacking context with {:?}", filter);
             }
 
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
             let prim_list = PrimitiveList::new(
                 vec![cur_instance.clone()],
-                &self.prim_store.primitives,
+                &self.resources.prim_interner,
             );
 
             let blend_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
                 prim_list,
                 stacking_context.spatial_node_index,
+                max_clip,
             );
             let blend_pic_index = self.prim_store.create_picture(blend_picture);
             current_pic_index = blend_pic_index;
 
-            let blend_prim = BrushPrimitive::new_picture(blend_pic_index);
+            cur_instance.kind = PrimitiveInstanceKind::Picture { pic_index: blend_pic_index };
 
-            cur_instance.prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                PrimitiveContainer::Brush(blend_prim),
-            );
-
-            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+            if cur_instance.is_chased() {
                 println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode);
             }
         }
 
         let has_mix_blend_on_secondary_framebuffer =
             stacking_context.composite_ops.mix_blend_mode.is_some() &&
             self.sc_stack.len() > 2;
 
@@ -1300,19 +1306,19 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayoutSize,
         content_size: &LayoutSize,
     ) {
-        if let ChasePrimitive::Index(prim_index) = self.config.chase_primitive {
-            println!("Chasing {:?} by index", prim_index);
-            self.prim_store.chase_id = Some(prim_index);
+        if let ChasePrimitive::Id(id) = self.config.chase_primitive {
+            println!("Chasing {:?} by index", id);
+            register_prim_chase_id(id);
         }
 
         self.push_reference_frame(
             ClipId::root_reference_frame(pipeline_id),
             None,
             pipeline_id,
             None,
             None,
@@ -1361,16 +1367,17 @@ impl<'a> DisplayListFlattener<'a> {
         //           interning and chaining code.
 
         // Build the clip sources from the supplied region.
         let handle = self
             .resources
             .clip_interner
             .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip), || {
                 ClipItemSceneData {
+                    clip_rect: clip_region.main,
                 }
             });
 
         parent_clip_chain_index = self
             .clip_store
             .add_clip_chain_node(
                 handle,
                 spatial_node,
@@ -1379,16 +1386,17 @@ impl<'a> DisplayListFlattener<'a> {
         clip_count += 1;
 
         if let Some(ref image_mask) = clip_region.image_mask {
             let handle = self
                 .resources
                 .clip_interner
                 .intern(&ClipItemKey::image_mask(image_mask), || {
                     ClipItemSceneData {
+                        clip_rect: image_mask.get_local_clip_rect().unwrap_or(LayoutRect::max_rect()),
                     }
                 });
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
@@ -1398,16 +1406,17 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         for region in clip_region.complex_clips {
             let handle = self
                 .resources
                 .clip_interner
                 .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode), || {
                     ClipItemSceneData {
+                        clip_rect: region.get_local_clip_rect().unwrap_or(LayoutRect::max_rect()),
                     }
                 });
 
             parent_clip_chain_index = self
                 .clip_store
                 .add_clip_chain_node(
                     handle,
                     spatial_node,
@@ -1532,17 +1541,17 @@ impl<'a> DisplayListFlattener<'a> {
                         }
                     }
 
                     // No point in adding a shadow here if there were no primitives
                     // added to the shadow.
                     if !prims.is_empty() {
                         let prim_list = PrimitiveList::new(
                             prims,
-                            &self.prim_store.primitives,
+                            &self.resources.prim_interner,
                         );
 
                         // Create a picture that the shadow primitives will be added to. If the
                         // blur radius is 0, the code in Picture::prepare_for_render will
                         // detect this and mark the picture to be drawn directly into the
                         // parent picture, which avoids an intermediate surface and blur.
                         let blur_filter = FilterOp::Blur(std_deviation).sanitize();
                         let mut shadow_pic = PicturePrimitive::new_image(
@@ -1550,38 +1559,40 @@ impl<'a> DisplayListFlattener<'a> {
                             Some(PictureCompositeMode::Filter(blur_filter)),
                             Picture3DContext::Out,
                             pipeline_id,
                             None,
                             is_passthrough,
                             raster_space,
                             prim_list,
                             pending_shadow.clip_and_scroll.spatial_node_index,
+                            max_clip,
                         );
 
                         // Create the primitive to draw the shadow picture into the scene.
                         let shadow_pic_index = self.prim_store.create_picture(shadow_pic);
-                        let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
-                        let shadow_prim_index = self.prim_store.add_primitive(
-                            &LayoutRect::zero(),
-                            &max_clip,
-                            PrimitiveContainer::Brush(shadow_prim),
+
+                        let shadow_prim_key = PrimitiveKey::new(
+                            true,
+                            LayoutRect::zero(),
+                            LayoutRect::max_rect(),
                         );
 
-                        let shadow_prim_key = PrimitiveKey::new(true);
                         let shadow_prim_data_handle = self.resources
                             .prim_interner
                             .intern(&shadow_prim_key, || {
                                 PrimitiveSceneData {
+                                    culling_rect: LayoutRect::zero(),
+                                    is_backface_visible: true,
                                 }
                             }
                         );
 
                         let shadow_prim_instance = PrimitiveInstance::new(
-                            shadow_prim_index,
+                            PrimitiveInstanceKind::Picture { pic_index: shadow_pic_index },
                             shadow_prim_data_handle,
                             pending_shadow.clip_and_scroll.clip_chain_id,
                             pending_shadow.clip_and_scroll.spatial_node_index,
                         );
 
                         // Add the shadow primitive. This must be done before pushing this
                         // picture on to the shadow stack, to avoid infinite recursion!
                         self.add_primitive_to_draw_list(shadow_prim_instance);
@@ -1592,31 +1603,51 @@ impl<'a> DisplayListFlattener<'a> {
                     // as a normal primitive to the parent picture.
                     if pending_primitive.container.is_visible() {
                         let prim_instance = self.create_primitive(
                             &pending_primitive.info,
                             pending_primitive.clip_and_scroll.clip_chain_id,
                             pending_primitive.clip_and_scroll.spatial_node_index,
                             pending_primitive.container,
                         );
-                        if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
-                            println!("Chasing {:?} by local rect", prim_instance.prim_index);
-                            self.prim_store.chase_id = Some(prim_instance.prim_index);
-                        }
+                        self.register_chase_primitive_by_rect(
+                            &pending_primitive.info.rect,
+                            &prim_instance,
+                        );
                         self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
                         self.add_primitive_to_draw_list(prim_instance);
                     }
                 }
             }
         }
 
         debug_assert!(items.is_empty());
         self.pending_shadow_items = items;
     }
 
+    #[cfg(debug_assertions)]
+    fn register_chase_primitive_by_rect(
+        &mut self,
+        rect: &LayoutRect,
+        prim_instance: &PrimitiveInstance,
+    ) {
+        if ChasePrimitive::LocalRect(*rect) == self.config.chase_primitive {
+            println!("Chasing {:?} by local rect", prim_instance.id);
+            register_prim_chase_id(prim_instance.id);
+        }
+    }
+
+    #[cfg(not(debug_assertions))]
+    fn register_chase_primitive_by_rect(
+        &mut self,
+        _rect: &LayoutRect,
+        _prim_instance: &PrimitiveInstance,
+    ) {
+    }
+
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
     ) {
         if color.a == 0.0 {
             // Don't add transparent rectangles to the draw list, but do consider them for hit
@@ -2244,55 +2275,51 @@ impl FlattenedStackingContext {
     }
 
     /// For a Preserve3D context, cut the sequence of the immediate flat children
     /// recorded so far and generate a picture from them.
     pub fn cut_flat_item_sequence(
         &mut self,
         picture_id_generator: &mut PictureIdGenerator,
         prim_store: &mut PrimitiveStore,
+        prim_interner: &PrimitiveDataInterner,
     ) -> Option<PrimitiveInstance> {
         if !self.is_3d() || self.primitives.is_empty() {
             return None
         }
         let flat_items_context_3d = match self.context_3d {
             Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
                 root_data: None,
                 ancestor_index,
             },
             Picture3DContext::Out => panic!("Unexpected out of 3D context"),
         };
 
         let prim_list = PrimitiveList::new(
             mem::replace(&mut self.primitives, Vec::new()),
-            &prim_store.primitives,
+            prim_interner,
         );
 
         let container_picture = PicturePrimitive::new_image(
             picture_id_generator.next(),
             Some(PictureCompositeMode::Blit),
             flat_items_context_3d,
             self.pipeline_id,
             None,
             true,
             self.requested_raster_space,
             prim_list,
             self.spatial_node_index,
+            LayoutRect::max_rect(),
         );
 
         let pic_index = prim_store.create_picture(container_picture);
-        let container_prim = BrushPrimitive::new_picture(pic_index);
-        let cut_prim_index = prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &LayoutRect::max_rect(),
-            PrimitiveContainer::Brush(container_prim),
-        );
 
         Some(PrimitiveInstance::new(
-            cut_prim_index,
+            PrimitiveInstanceKind::Picture { pic_index },
             self.primitive_data_handle,
             self.clip_chain_id,
             self.spatial_node_index,
         ))
     }
 }
 
 /// A primitive that is added while a shadow context is
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
-use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode, PictureRect};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
 use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
-use picture::{PictureCompositeMode, PictureSurface, PictureUpdateContext, RasterConfig};
-use prim_store::{PrimitiveIndex, PrimitiveStore, SpaceMapper, PictureIndex};
+use picture::{PictureSurface, PictureUpdateContext, SurfaceInfo, ROOT_SURFACE_INDEX};
+use prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameResources, FrameId};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
 use spatial_node::SpatialNode;
 use std::f32;
@@ -27,17 +27,17 @@ use tiling::{Frame, RenderPass, RenderPa
 use tiling::{SpecialRenderPasses};
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ChasePrimitive {
     Nothing,
-    Index(PrimitiveIndex),
+    Id(PrimitiveDebugId),
     LocalRect(LayoutRect),
 }
 
 impl Default for ChasePrimitive {
     fn default() -> Self {
         ChasePrimitive::Nothing
     }
 }
@@ -80,49 +80,44 @@ pub struct FrameBuildingState<'a> {
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
     pub resources: &'a mut FrameResources,
     pub segment_builder: SegmentBuilder,
+    pub surfaces: &'a mut Vec<SurfaceInfo>,
 }
 
 /// Immutable context of a picture when processing children.
 #[derive(Debug)]
 pub struct PictureContext {
     pub pic_index: PictureIndex,
     pub pipeline_id: PipelineId,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
     pub is_passthrough: bool,
     pub raster_space: RasterSpace,
-    pub local_spatial_node_index: SpatialNodeIndex,
     pub surface_spatial_node_index: SpatialNodeIndex,
     pub raster_spatial_node_index: SpatialNodeIndex,
 }
 
 /// Mutable state of a picture that gets modified when
 /// the children are processed.
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
     pub is_cacheable: bool,
     pub local_rect_changed: bool,
-    /// Union rectangle of all the items in this picture.
-    pub rect: PictureRect,
     pub map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel>,
     pub map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel>,
     pub map_pic_to_raster: SpaceMapper<PicturePixel, RasterPixel>,
     pub map_raster_to_world: SpaceMapper<RasterPixel, WorldPixel>,
-    /// Mapping from local space to the containing block, which is the root for
-    /// plane splitting and affects backface visibility.
-    pub map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel>,
     /// If the plane splitter, the primitives get added to it insted of
     /// batching into their parent pictures.
     pub plane_splitter: Option<PlaneSplitter>,
 }
 
 pub struct PrimitiveContext<'a> {
     pub spatial_node: &'a SpatialNode,
     pub spatial_node_index: SpatialNodeIndex,
@@ -191,20 +186,21 @@ impl FrameBuilder {
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &mut TransformPalette,
         resources: &mut FrameResources,
+        surfaces: &mut Vec<SurfaceInfo>,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
-        if self.prim_store.primitives.is_empty() {
+        if self.prim_store.pictures.is_empty() {
             return None
         }
 
         let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let world_rect = (self.screen_rect.to_f32() / device_pixel_scale).round_out();
@@ -217,103 +213,105 @@ impl FrameBuilder {
             world_rect,
             clip_scroll_tree,
             max_local_clip: LayoutRect::new(
                 LayoutPoint::new(-MAX_CLIP_COORD, -MAX_CLIP_COORD),
                 LayoutSize::new(2.0 * MAX_CLIP_COORD, 2.0 * MAX_CLIP_COORD),
             ),
         };
 
-        let mut frame_state = FrameBuildingState {
-            render_tasks,
-            profile_counters,
-            clip_store: &mut self.clip_store,
-            resource_cache,
-            gpu_cache,
-            special_render_passes,
-            transforms: transform_palette,
-            resources,
-            segment_builder: SegmentBuilder::new(),
-        };
+        // Construct a dummy root surface, that represents the
+        // main framebuffer surface.
+        let root_surface = SurfaceInfo::new(
+            ROOT_SPATIAL_NODE_INDEX,
+            ROOT_SPATIAL_NODE_INDEX,
+            world_rect,
+            clip_scroll_tree,
+        );
+        surfaces.push(root_surface);
 
-        let pic_update_context = PictureUpdateContext {
-            surface_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
-            raster_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
-        };
+        let pic_update_context = PictureUpdateContext::new(
+            ROOT_SURFACE_INDEX,
+            ROOT_SPATIAL_NODE_INDEX,
+        );
 
         // The first major pass of building a frame is to walk the picture
         // tree. This pass must be quick (it should never touch individual
         // primitives). For now, all we do here is determine which pictures
         // will create surfaces. In the future, this will be expanded to
         // set up render tasks, determine scaling of surfaces, and detect
         // which surfaces have valid cached surfaces that don't need to
         // be rendered this frame.
         self.prim_store.update_picture(
             self.root_pic_index,
             &pic_update_context,
             &frame_context,
+            surfaces,
         );
 
-        let prim_context = PrimitiveContext::new(
-            &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
-            root_spatial_node_index,
-        );
+        let mut frame_state = FrameBuildingState {
+            render_tasks,
+            profile_counters,
+            clip_store: &mut self.clip_store,
+            resource_cache,
+            gpu_cache,
+            special_render_passes,
+            transforms: transform_palette,
+            resources,
+            segment_builder: SegmentBuilder::new(),
+            surfaces,
+        };
 
-        let (pic_context, mut pic_state, mut instances) = self
+        let (pic_context, mut pic_state, mut prim_list) = self
             .prim_store
             .pictures[self.root_pic_index.0]
             .take_context(
                 self.root_pic_index,
-                &prim_context,
-                root_spatial_node_index,
                 root_spatial_node_index,
                 root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
-                false,
             )
             .unwrap();
 
         self.prim_store.prepare_primitives(
-            &mut instances,
+            &mut prim_list,
             &pic_context,
             &mut pic_state,
             &frame_context,
             &mut frame_state,
         );
 
-        let pic_rect = Some(pic_state.rect);
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
-            instances,
+            prim_list,
             pic_context,
             pic_state,
-            pic_rect,
             &mut frame_state,
         );
 
         let (pic_state, _) = pic.take_state_and_context();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
-        pic.raster_config = Some(RasterConfig {
-            composite_mode: PictureCompositeMode::Blit,
-            surface: Some(PictureSurface::RenderTask(render_task_id)),
-            raster_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
-        });
+        frame_state
+            .surfaces
+            .first_mut()
+            .unwrap()
+            .surface = Some(PictureSurface::RenderTask(render_task_id));
         Some(render_task_id)
     }
 
     pub fn build(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         frame_id: FrameId,
@@ -344,32 +342,34 @@ impl FrameBuilder {
         let mut transform_palette = TransformPalette::new();
         clip_scroll_tree.update_tree(
             pan,
             scene_properties,
             Some(&mut transform_palette),
         );
 
         let mut render_tasks = RenderTaskTree::new(frame_id);
+        let mut surfaces = Vec::new();
 
         let screen_size = self.screen_rect.size.to_i32();
         let mut special_render_passes = SpecialRenderPasses::new(&screen_size);
 
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
             &mut transform_palette,
             resources,
+            &mut surfaces,
         );
 
         resource_cache.block_until_all_resources_added(gpu_cache,
                                                        &mut render_tasks,
                                                        texture_cache_profile);
 
         let mut passes = vec![
             special_render_passes.alpha_glyph_pass,
@@ -406,16 +406,17 @@ impl FrameBuilder {
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 use_dual_source_blending,
                 clip_scroll_tree,
                 resources,
+                surfaces: &surfaces,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,33 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint};
-use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutRect, PictureToRasterTransform};
+use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel};
 use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize, RasterRect, RasterSpace};
-use api::{PicturePixel, RasterPixel, WorldPixel};
+use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
-use euclid::{TypedScale, vec3};
-use internal_types::PlaneSplitter;
-use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
-use frame_builder::{PictureContext, PrimitiveContext};
+use euclid::{TypedScale, vec3, TypedRect};
+use internal_types::{FastHashMap, PlaneSplitter};
+use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
 use plane_split::{Clipper, Polygon, Splitter};
-use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, PrimitiveDetails};
-use prim_store::{Primitive, get_raster_rects, BrushKind, BrushPrimitive};
+use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
+use prim_store::{get_raster_rects, PrimitiveDataInterner};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use smallvec::SmallVec;
-use std::mem;
+use std::{mem, ops};
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
@@ -35,31 +34,105 @@ use util::{TransformedRectKind, MatrixHe
  * A configuration describing how to draw the primitives on
    this picture (e.g. in screen space or local space).
  */
 
 /// A context structure passed from parent to child
 /// during the first frame building pass, when just
 /// pictures are traversed.
 pub struct PictureUpdateContext {
+    /// Index of the current surface as the picture
+    /// tree is traversed.
+    pub surface_index: SurfaceIndex,
+    /// Used for backface visibility calculations.
+    pub parent_spatial_node_index: SpatialNodeIndex,
+}
+
+impl PictureUpdateContext {
+    pub fn new(
+        surface_index: SurfaceIndex,
+        parent_spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        PictureUpdateContext {
+            surface_index,
+            parent_spatial_node_index,
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct SurfaceIndex(pub usize);
+
+pub const ROOT_SURFACE_INDEX: SurfaceIndex = SurfaceIndex(0);
+
+/// Information about an offscreen surface. For now,
+/// it contains information about the size and coordinate
+/// system of the surface. In the future, it will contain
+/// information about the contents of the surface, which
+/// will allow surfaces to be cached / retained between
+/// frames and display lists.
+#[derive(Debug)]
+pub struct SurfaceInfo {
+    /// A local rect defining the size of this surface, in the
+    /// coordinate system of the surface itself.
+    pub rect: PictureRect,
+    /// Helper structs for mapping local rects in different
+    /// coordinate systems into the surface coordinates.
+    pub map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
+    pub map_surface_to_world: SpaceMapper<PicturePixel, WorldPixel>,
+    /// Defines the positioning node for the surface itself,
+    /// and the rasterization root for this surface.
+    pub raster_spatial_node_index: SpatialNodeIndex,
     pub surface_spatial_node_index: SpatialNodeIndex,
-    pub raster_spatial_node_index: SpatialNodeIndex,
+    /// This is set when the render task is created.
+    pub surface: Option<PictureSurface>,
+}
+
+impl SurfaceInfo {
+    pub fn new(
+        surface_spatial_node_index: SpatialNodeIndex,
+        raster_spatial_node_index: SpatialNodeIndex,
+        world_rect: WorldRect,
+        clip_scroll_tree: &ClipScrollTree,
+    ) -> Self {
+        let map_surface_to_world = SpaceMapper::new_with_target(
+            ROOT_SPATIAL_NODE_INDEX,
+            surface_spatial_node_index,
+            world_rect,
+            clip_scroll_tree,
+        );
+
+        let pic_bounds = map_surface_to_world
+            .unmap(&map_surface_to_world.bounds)
+            .unwrap_or(PictureRect::max_rect());
+
+        let map_local_to_surface = SpaceMapper::new(
+            surface_spatial_node_index,
+            pic_bounds,
+        );
+
+        SurfaceInfo {
+            rect: PictureRect::zero(),
+            map_surface_to_world,
+            map_local_to_surface,
+            surface: None,
+            raster_spatial_node_index,
+            surface_spatial_node_index,
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct RasterConfig {
+    /// How this picture should be composited into
+    /// the parent surface.
     pub composite_mode: PictureCompositeMode,
-
-    // If this picture is drawn to an intermediate surface,
-    // the associated target information.
-    pub surface: Option<PictureSurface>,
-
-    // The spatial node of the rasterization root
-    // for this picture.
-    pub raster_spatial_node_index: SpatialNodeIndex,
+    /// Index to the surface descriptor for this
+    /// picture.
+    pub surface_index: SurfaceIndex,
 }
 
 /// Specifies how this Picture should be composited
 /// onto the target it belongs to.
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum PictureCompositeMode {
     /// Apply CSS mix-blend-mode effect.
     MixBlend(MixBlendMode),
@@ -187,59 +260,185 @@ pub enum Picture3DContext<C> {
 /// and ordered according to the view direction.
 #[derive(Clone, Debug)]
 pub struct OrderedPictureChild {
     pub anchor: usize,
     pub transform_id: TransformPaletteId,
     pub gpu_address: GpuCacheAddress,
 }
 
+/// Defines the grouping key for a cluster of primitives in a picture.
+/// In future this will also contain spatial grouping details.
+#[derive(Hash, Eq, PartialEq, Copy, Clone)]
+struct PrimitiveClusterKey {
+    /// Grouping primitives by spatial node ensures that we can calculate a local
+    /// bounding volume for the cluster, and then transform that by the spatial
+    /// node transform once to get an updated bounding volume for the entire cluster.
+    spatial_node_index: SpatialNodeIndex,
+    /// We want to separate clusters that have different backface visibility properties
+    /// so that we can accept / reject an entire cluster at once if the backface is not
+    /// visible.
+    is_backface_visible: bool,
+}
+
+/// Descriptor for a cluster of primitives. For now, this is quite basic but will be
+/// extended to handle more spatial clustering of primitives.
+pub struct PrimitiveCluster {
+    /// The positioning node for this cluster.
+    spatial_node_index: SpatialNodeIndex,
+    /// Whether this cluster is visible when the position node is a backface.
+    is_backface_visible: bool,
+    /// The bounding rect of the cluster, in the local space of the spatial node.
+    /// This is used to quickly determine the overall bounding rect for a picture
+    /// during the first picture traversal, which is needed for local scale
+    /// determination, and render task size calculations.
+    bounding_rect: LayoutRect,
+    /// This flag is set during the first pass picture traversal, depending on whether
+    /// the cluster is visible or not. It's read during the second pass when primitives
+    /// consult their owning clusters to see if the primitive itself is visible.
+    pub is_visible: bool,
+}
+
+impl PrimitiveCluster {
+    fn new(
+        spatial_node_index: SpatialNodeIndex,
+        is_backface_visible: bool,
+    ) -> Self {
+        PrimitiveCluster {
+            bounding_rect: LayoutRect::zero(),
+            spatial_node_index,
+            is_backface_visible,
+            is_visible: false,
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct PrimitiveClusterIndex(pub u32);
+
+pub type ClusterRange = ops::Range<u32>;
+
 /// A list of primitive instances that are added to a picture
 /// This ensures we can keep a list of primitives that
 /// are pictures, for a fast initial traversal of the picture
 /// tree without walking the instance list.
 pub struct PrimitiveList {
+    /// The primitive instances, in render order.
     pub prim_instances: Vec<PrimitiveInstance>,
+    /// List of pictures that are part of this list.
+    /// Used to implement the picture traversal pass.
     pub pictures: SmallVec<[PictureIndex; 4]>,
+    /// List of primitives grouped into clusters.
+    pub clusters: SmallVec<[PrimitiveCluster; 4]>,
+    /// This maps from the cluster_range in a primitive
+    /// instance to a set of cluster(s) that the
+    /// primitive instance belongs to.
+    pub prim_cluster_map: Vec<PrimitiveClusterIndex>,
 }
 
 impl PrimitiveList {
+    /// Construct an empty primitive list. This is
+    /// just used during the take_context / restore_context
+    /// borrow check dance, which will be removed as the
+    /// picture traversal pass is completed.
+    pub fn empty() -> Self {
+        PrimitiveList {
+            prim_instances: Vec::new(),
+            pictures: SmallVec::new(),
+            clusters: SmallVec::new(),
+            prim_cluster_map: Vec::new(),
+        }
+    }
+
+    /// Construct a new prim list from a list of instances
+    /// in render order. This does some work during scene
+    /// building which makes the frame building traversals
+    /// significantly faster.
     pub fn new(
-        prim_instances: Vec<PrimitiveInstance>,
-        primitives: &[Primitive],
+        mut prim_instances: Vec<PrimitiveInstance>,
+        prim_interner: &PrimitiveDataInterner,
     ) -> Self {
         let mut pictures = SmallVec::new();
+        let mut clusters_map = FastHashMap::default();
+        let mut clusters: SmallVec<[PrimitiveCluster; 4]> = SmallVec::new();
+        let mut prim_cluster_map = Vec::new();
 
         // Walk the list of primitive instances and extract any that
         // are pictures.
-        for prim_instance in &prim_instances {
-            let prim = &primitives[prim_instance.prim_index.0];
-            match prim.details {
-                PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture { pic_index }, .. }) => {
+        for prim_instance in &mut prim_instances {
+            // Check if this primitive is a picture. In future we should
+            // remove this match and embed this info directly in the primitive instance.
+            let is_pic = match prim_instance.kind {
+                PrimitiveInstanceKind::Picture { pic_index } => {
                     pictures.push(pic_index);
+                    true
                 }
                 _ => {
-                    // TODO(gw): For now, we don't do anything with non-picture primitives.
-                    //           However, in the near future we'll accumulate the static
-                    //           bounding rects for primitive clusters here, so that we can
-                    //           quickly determine the size of a picture during frame building.
+                    false
+                }
+            };
+
+            // Get the key for the cluster that this primitive should
+            // belong to.
+            let prim_data = &prim_interner[prim_instance.prim_data_handle];
+            let key = PrimitiveClusterKey {
+                spatial_node_index: prim_instance.spatial_node_index,
+                is_backface_visible: prim_data.is_backface_visible,
+            };
+
+            // Find the cluster, or create a new one.
+            let cluster_index = *clusters_map
+                .entry(key)
+                .or_insert_with(|| {
+                    let index = clusters.len();
+                    clusters.push(PrimitiveCluster::new(
+                        prim_instance.spatial_node_index,
+                        prim_data.is_backface_visible,
+                    ));
+                    index
                 }
+            );
+
+            // Pictures don't have a known static local bounding rect (they are
+            // calculated during the picture traversal dynamically). If not
+            // a picture, include a minimal bounding rect in the cluster bounds.
+            let cluster = &mut clusters[cluster_index];
+            if !is_pic {
+                cluster.bounding_rect = cluster.bounding_rect.union(&prim_data.culling_rect);
             }
+
+            // Define a range of clusters that this primitive belongs to. For now, this
+            // seems like overkill, since a primitive only ever belongs to one cluster.
+            // However, in the future the clusters will include spatial information. It
+            // will often be the case that a primitive may overlap more than one cluster,
+            // and belong to several.
+            let start = prim_cluster_map.len() as u32;
+            let cluster_range = ClusterRange {
+                start,
+                end: start + 1,
+            };
+
+            // Store the cluster index in the map, and the range in the instance.
+            prim_cluster_map.push(PrimitiveClusterIndex(cluster_index as u32));
+            prim_instance.cluster_range = cluster_range;
         }
 
         PrimitiveList {
             prim_instances,
             pictures,
+            clusters,
+            prim_cluster_map,
         }
     }
 }
 
 pub struct PicturePrimitive {
-    // List of primitive runs that make up this picture.
-    pub prim_instances: Vec<PrimitiveInstance>,
+    /// List of primitives, and associated info for this picture.
+    pub prim_list: PrimitiveList,
+
     pub state: Option<(PictureState, PictureContext)>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
     // If true, apply the local clip rect to primitive drawn
     // in this picture.
     pub apply_local_clip_rect: bool,
@@ -268,22 +467,26 @@ pub struct PicturePrimitive {
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
 
     // Unique identifier for this picture.
     pub id: PictureId,
 
-    /// List of the children that are picture primitives.
-    child_pictures: SmallVec<[PictureIndex; 4]>,
-
     /// The spatial node index of this picture when it is
     /// composited into the parent picture.
     spatial_node_index: SpatialNodeIndex,
+
+    /// The local rect of this picture. It is built
+    /// dynamically during the first picture traversal.
+    pub local_rect: LayoutRect,
+
+    /// Local clip rect for this picture.
+    pub local_clip_rect: LayoutRect,
 }
 
 impl PicturePrimitive {
     fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
         match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(ref mut filter)) => {
                 match *filter {
                     FilterOp::Opacity(ref binding, ref mut value) => {
@@ -312,61 +515,58 @@ impl PicturePrimitive {
         requested_composite_mode: Option<PictureCompositeMode>,
         context_3d: Picture3DContext<OrderedPictureChild>,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
         prim_list: PrimitiveList,
         spatial_node_index: SpatialNodeIndex,
+        local_clip_rect: LayoutRect,
     ) -> Self {
         PicturePrimitive {
-            prim_instances: prim_list.prim_instances,
-            child_pictures: prim_list.pictures,
+            prim_list,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             context_3d,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             id,
             requested_raster_space,
             spatial_node_index,
+            local_rect: LayoutRect::zero(),
+            local_clip_rect,
         }
     }
 
     pub fn take_context(
         &mut self,
         pic_index: PictureIndex,
-        prim_context: &PrimitiveContext,
-        parent_spatial_node_index: SpatialNodeIndex,
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         parent_allows_subpixel_aa: bool,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
-        is_chased: bool,
-    ) -> Option<(PictureContext, PictureState, Vec<PrimitiveInstance>)> {
+    ) -> Option<(PictureContext, PictureState, PrimitiveList)> {
         if !self.is_visible() {
-            if cfg!(debug_assertions) && is_chased {
-                println!("\tculled for carrying an invisible composite filter");
-            }
-
             return None;
         }
 
         // Extract the raster and surface spatial nodes from the raster
         // config, if this picture establishes a surface. Otherwise just
         // pass in the spatial node indices from the parent context.
         let (raster_spatial_node_index, surface_spatial_node_index) = match self.raster_config {
             Some(ref raster_config) => {
-                (raster_config.raster_spatial_node_index, self.spatial_node_index)
+                let surface = &frame_state.surfaces[raster_config.surface_index.0];
+
+                (surface.raster_spatial_node_index, self.spatial_node_index)
             }
             None => {
                 (raster_spatial_node_index, surface_spatial_node_index)
             }
         };
 
         if self.raster_config.is_some() {
             frame_state.clip_store
@@ -389,44 +589,37 @@ impl PicturePrimitive {
         );
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             surface_spatial_node_index,
             raster_spatial_node_index,
             frame_context,
         );
 
-        let (containing_block_index, plane_splitter) = match self.context_3d {
+        let plane_splitter = match self.context_3d {
             Picture3DContext::Out => {
-                (parent_spatial_node_index, None)
+                None
             }
-            Picture3DContext::In { root_data: Some(_), ancestor_index } => {
-                (ancestor_index, Some(PlaneSplitter::new()))
+            Picture3DContext::In { root_data: Some(_), .. } => {
+                Some(PlaneSplitter::new())
             }
-            Picture3DContext::In { root_data: None, ancestor_index } => {
-                (ancestor_index, None)
+            Picture3DContext::In { root_data: None, .. } => {
+                None
             }
         };
 
-        let map_local_to_containing_block = SpaceMapper::new(
-            containing_block_index,
-            LayoutRect::zero(), // bounds aren't going to be used for this mapping
-        );
-
         let state = PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
             is_cacheable: true,
             local_rect_changed: false,
-            rect: PictureRect::zero(),
             map_local_to_pic,
             map_pic_to_world,
             map_pic_to_raster,
             map_raster_to_world,
-            map_local_to_containing_block,
             plane_splitter,
         };
 
         // Disallow subpixel AA if an intermediate surface is needed.
         // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
         let allow_subpixel_aa = parent_allows_subpixel_aa &&
             self.raster_config.is_none();
 
@@ -444,78 +637,48 @@ impl PicturePrimitive {
         let context = PictureContext {
             pic_index,
             pipeline_id: self.pipeline_id,
             apply_local_clip_rect: self.apply_local_clip_rect,
             inflation_factor,
             allow_subpixel_aa,
             is_passthrough: self.raster_config.is_none(),
             raster_space: self.requested_raster_space,
-            local_spatial_node_index: prim_context.spatial_node_index,
             raster_spatial_node_index,
             surface_spatial_node_index,
         };
 
-        let instances = mem::replace(&mut self.prim_instances, Vec::new());
+        let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
 
-        Some((context, state, instances))
+        Some((context, state, prim_list))
     }
 
     pub fn restore_context(
         &mut self,
-        prim_instances: Vec<PrimitiveInstance>,
+        prim_list: PrimitiveList,
         context: PictureContext,
         state: PictureState,
-        local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
-    ) -> (LayoutRect, Option<ClipNodeCollector>) {
-        let local_rect = match local_rect {
-            Some(local_rect) => {
-                let local_content_rect = LayoutRect::from_untyped(&local_rect.to_untyped());
+    ) -> (bool, Option<ClipNodeCollector>) {
+        self.prim_list = prim_list;
+        self.state = Some((state, context));
 
-                match self.raster_config {
-                    Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)), .. }) => {
-                        let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
-                        local_content_rect.inflate(inflate_size, inflate_size)
-                    }
-                    Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)), .. }) => {
-                        let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
-                        local_content_rect.inflate(inflate_size, inflate_size)
+        match self.raster_config {
+            Some(ref raster_config) => {
+                let local_rect = frame_state.surfaces[raster_config.surface_index.0].rect;
+                let local_rect = LayoutRect::from_untyped(&local_rect.to_untyped());
+                let local_rect_changed = local_rect != self.local_rect;
+                self.local_rect = local_rect;
 
-                        // TODO(gw): When we support culling rect being separate from
-                        //           the task/screen rect, we should include both the
-                        //           content and shadow rect here, which will prevent
-                        //           drop-shadows from disappearing if the main content
-                        //           rect is not visible. Something like:
-                        // let shadow_rect = local_content_rect
-                        //     .inflate(inflate_size, inflate_size)
-                        //     .translate(&offset);
-                        // shadow_rect.union(&local_content_rect)
-                    }
-                    _ => {
-                        local_content_rect
-                    }
-                }
+                (local_rect_changed, Some(frame_state.clip_store.pop_surface()))
             }
             None => {
-                assert!(self.raster_config.is_none());
-                LayoutRect::zero()
+                (false, None)
             }
-        };
-
-        let clip_node_collector = if context.is_passthrough {
-            None
-        } else {
-            Some(frame_state.clip_store.pop_surface())
-        };
-
-        self.prim_instances = prim_instances;
-        self.state = Some((state, context));
-
-        (local_rect, clip_node_collector)
+        }
     }
 
     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
@@ -587,17 +750,17 @@ impl PicturePrimitive {
             Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
             _ => panic!("Expected to find 3D context root"),
         };
         ordered.clear();
 
         // Process the accumulated split planes and order them for rendering.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
-            let spatial_node_index = self.prim_instances[poly.anchor].spatial_node_index;
+            let spatial_node_index = self.prim_list.prim_instances[poly.anchor].spatial_node_index;
 
             let transform = frame_state.transforms.get_world_inv_transform(spatial_node_index);
             let transform_id = frame_state.transforms.get_id(
                 spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 clip_scroll_tree,
             );
 
@@ -624,86 +787,202 @@ impl PicturePrimitive {
 
     /// Called during initial picture traversal, before we know the
     /// bounding rect of children. It is possible to determine the
     /// surface / raster config now though.
     pub fn pre_update(
         &mut self,
         context: &PictureUpdateContext,
         frame_context: &FrameBuildingContext,
+        surfaces: &mut Vec<SurfaceInfo>,
     ) -> Option<(PictureUpdateContext, SmallVec<[PictureIndex; 4]>)> {
         // Reset raster config in case we early out below.
         self.raster_config = None;
 
+        // Resolve animation properties, and early out if the filter
+        // properties make this picture invisible.
         if !self.resolve_scene_properties(frame_context.scene_properties) {
             return None;
         }
 
+        // See if this picture actually needs a surface for compositing.
         let actual_composite_mode = match self.requested_composite_mode {
             Some(PictureCompositeMode::Filter(filter)) if filter.is_noop() => None,
             mode => mode,
         };
 
-        let has_surface = actual_composite_mode.is_some();
+        let surface_index = match actual_composite_mode {
+            Some(composite_mode) => {
+                // Retrieve the positioning node information for the parent surface.
+                let parent_raster_spatial_node_index = surfaces[context.surface_index.0].raster_spatial_node_index;
+                let surface_spatial_node_index = self.spatial_node_index;
+
+                // Check if there is perspective, and thus whether a new
+                // rasterization root should be established.
+                let xf = frame_context.clip_scroll_tree.get_relative_transform(
+                    parent_raster_spatial_node_index,
+                    surface_spatial_node_index,
+                ).expect("BUG: unable to get relative transform");
+
+                // TODO(gw): A temporary hack here to revert behavior to
+                //           always raster in screen-space. This is not
+                //           a problem yet, since we're not taking advantage
+                //           of this for caching yet. This is a workaround
+                //           for some existing issues with handling scale
+                //           when rasterizing in local space mode. Once
+                //           the fixes for those are in-place, we can
+                //           remove this hack!
+                //let local_scale = raster_space.local_scale();
+                // let wants_raster_root = xf.has_perspective_component() ||
+                //                         local_scale.is_some();
+                let establishes_raster_root = xf.has_perspective_component();
 
-        let surface_spatial_node_index = if has_surface {
-            self.spatial_node_index
-        } else {
-            context.surface_spatial_node_index
+                let raster_spatial_node_index = if establishes_raster_root {
+                    surface_spatial_node_index
+                } else {
+                    parent_raster_spatial_node_index
+                };
+
+                let surface_index = SurfaceIndex(surfaces.len());
+                let surface = SurfaceInfo::new(
+                    surface_spatial_node_index,
+                    raster_spatial_node_index,
+                    frame_context.world_rect,
+                    &frame_context.clip_scroll_tree,
+                );
+                surfaces.push(surface);
+
+                self.raster_config = Some(RasterConfig {
+                    composite_mode,
+                    surface_index,
+                });
+
+                surface_index
+            }
+            None => {
+                context.surface_index
+            }
         };
 
-        let xf = frame_context.clip_scroll_tree.get_relative_transform(
-            context.raster_spatial_node_index,
-            surface_spatial_node_index,
-        ).expect("BUG: unable to get relative transform");
-
-        // TODO(gw): A temporary hack here to revert behavior to
-        //           always raster in screen-space. This is not
-        //           a problem yet, since we're not taking advantage
-        //           of this for caching yet. This is a workaround
-        //           for some existing issues with handling scale
-        //           when rasterizing in local space mode. Once
-        //           the fixes for those are in-place, we can
-        //           remove this hack!
-        //let local_scale = raster_space.local_scale();
-        // let wants_raster_root = xf.has_perspective_component() ||
-        //                         local_scale.is_some();
-        let wants_raster_root = xf.has_perspective_component();
-
-        let establishes_raster_root = has_surface && wants_raster_root;
+        let child_context = PictureUpdateContext::new(
+            surface_index,
+            self.spatial_node_index,
+        );
 
-        let raster_spatial_node_index = if establishes_raster_root {
-            surface_spatial_node_index
-        } else {
-            context.raster_spatial_node_index
-        };
-
-        self.raster_config = actual_composite_mode.map(|composite_mode| {
-            RasterConfig {
-                composite_mode,
-                surface: None,
-                raster_spatial_node_index,
-            }
-        });
-
-        let child_context = PictureUpdateContext {
-            raster_spatial_node_index,
-            surface_spatial_node_index,
-        };
-
-        Some((child_context, mem::replace(&mut self.child_pictures, SmallVec::new())))
+        Some((child_context, mem::replace(&mut self.prim_list.pictures, SmallVec::new())))
     }
 
     /// Called after updating child pictures during the initial
     /// picture traversal.
     pub fn post_update(
         &mut self,
+        parent_context: &PictureUpdateContext,
+        this_context: &PictureUpdateContext,
         child_pictures: SmallVec<[PictureIndex; 4]>,
+        surfaces: &mut [SurfaceInfo],
+        frame_context: &FrameBuildingContext,
     ) {
-        self.child_pictures = child_pictures;
+        // Check visibility of each cluster, and then use
+        // the cluster bounding rect to calculate the
+        // surface bounding rect.
+
+        for cluster in &mut self.prim_list.clusters {
+            // Reset visibility
+            cluster.is_visible = false;
+
+            // Skip the cluster if backface culled.
+            if !cluster.is_backface_visible {
+                let containing_block_index = match self.context_3d {
+                    Picture3DContext::Out => {
+                        parent_context.parent_spatial_node_index
+                    }
+                    Picture3DContext::In { root_data: Some(_), ancestor_index } => {
+                        ancestor_index
+                    }
+                    Picture3DContext::In { root_data: None, ancestor_index } => {
+                        ancestor_index
+                    }
+                };
+
+                let map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel> = SpaceMapper::new_with_target(
+                    containing_block_index,
+                    cluster.spatial_node_index,
+                    LayoutRect::zero(),     // bounds aren't going to be used for this mapping
+                    &frame_context.clip_scroll_tree,
+                );
+
+                match map_local_to_containing_block.visible_face() {
+                    VisibleFace::Back => continue,
+                    VisibleFace::Front => {}
+                }
+            }
+
+            // No point including this cluster if it can't be transformed
+            let spatial_node = &frame_context
+                .clip_scroll_tree
+                .spatial_nodes[cluster.spatial_node_index.0];
+            if !spatial_node.invertible {
+                continue;
+            }
+
+            // Map the cluster bounding rect into the space of the surface, and
+            // include it in the surface bounding rect.
+            let surface = &mut surfaces[this_context.surface_index.0];
+            surface.map_local_to_surface.set_target_spatial_node(
+                cluster.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+
+            // Mark the cluster visible, since it passed the invertible and
+            // backface checks. In future, this will include spatial clustering
+            // which will allow the frame building code to skip most of the
+            // current per-primitive culling code.
+            cluster.is_visible = true;
+            if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) {
+                surface.rect = surface.rect.union(&cluster_rect);
+            }
+        }
+
+        // Inflate the local bounding rect if required by the filter effect.
+        let inflation_size = match self.raster_config {
+            Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)), .. }) |
+            Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)), .. }) => {
+                Some((blur_radius * BLUR_SAMPLE_SCALE).ceil())
+            }
+            _ => {
+                None
+            }
+        };
+        if let Some(inflation_size) = inflation_size {
+            let surface = &mut surfaces[this_context.surface_index.0];
+            surface.rect = surface.rect.inflate(inflation_size, inflation_size);
+        }
+
+        // If this picture establishes a surface, then map the surface bounding
+        // rect into the parent surface coordinate space, and propagate that up
+        // to the parent.
+        if let Some(ref raster_config) = self.raster_config {
+            let surface_rect = surfaces[raster_config.surface_index.0].rect;
+            let surface_rect = TypedRect::from_untyped(&surface_rect.to_untyped());
+
+            // Propagate up to parent surface, now that we know this surface's static rect
+            let parent_surface = &mut surfaces[parent_context.surface_index.0];
+            parent_surface.map_local_to_surface.set_target_spatial_node(
+                self.spatial_node_index,
+                frame_context.clip_scroll_tree,
+            );
+            if let Some(parent_surface_rect) = parent_surface
+                .map_local_to_surface
+                .map(&surface_rect) {
+                parent_surface.rect = parent_surface.rect.union(&parent_surface_rect);
+            }
+        }
+
+        // Restore the pictures list used during recursion.
+        self.prim_list.pictures = child_pictures;
     }
 
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
         prim_local_rect: &LayoutRect,
         pic_state: &mut PictureState,
@@ -723,19 +1002,21 @@ impl PicturePrimitive {
         let raster_config = match self.raster_config {
             Some(ref mut raster_config) => raster_config,
             None => {
                 pic_state.tasks.extend(pic_state_for_children.tasks);
                 return true
             }
         };
 
+        let surface_info = &mut frame_state.surfaces[raster_config.surface_index.0];
+
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             prim_instance.spatial_node_index,
-            raster_config.raster_spatial_node_index,
+            surface_info.raster_spatial_node_index,
             frame_context,
         );
 
         let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
 
         let (clipped, unclipped, transform) = match get_raster_rects(
             pic_rect,
             &map_pic_to_raster,
@@ -871,17 +1152,17 @@ impl PicturePrimitive {
 
                             render_task_id
                         }
                     );
 
                     PictureSurface::TextureCache(cache_item)
                 };
 
-                raster_config.surface = Some(surface);
+                surface_info.surface = Some(surface);
             }
             PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
                 let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
                 // The clipped field is the part of the picture that is visible
                 // on screen. The unclipped field is the screen-space rect of
                 // the complete picture, if no screen / clip-chain was applied
@@ -922,17 +1203,17 @@ impl PicturePrimitive {
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
                 self.secondary_render_task_id = Some(picture_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
-                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+                surface_info.surface = Some(PictureSurface::RenderTask(render_task_id));
 
                 // If the local rect of the contents changed, force the cache handle
                 // to be invalidated so that the primitive data below will get
                 // uploaded to the GPU this frame. This can occur during property
                 // animation.
                 if pic_state.local_rect_changed {
                     frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
                 }
@@ -987,17 +1268,17 @@ impl PicturePrimitive {
                     RenderTask::new_readback(clipped)
                 );
 
                 self.secondary_render_task_id = Some(readback_task_id);
                 pic_state.tasks.push(readback_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
-                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+                surface_info.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
             PictureCompositeMode::Filter(filter) => {
                 if let FilterOp::ColorMatrix(m) = filter {
                     if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                         for i in 0..5 {
                             request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
@@ -1017,17 +1298,17 @@ impl PicturePrimitive {
                     clipped.origin,
                     pic_state_for_children.tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
-                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+                surface_info.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
             PictureCompositeMode::Blit => {
                 let uv_rect_kind = calculate_uv_rect_kind(
                     &pic_rect,
                     &transform,
                     &clipped,
                     frame_context.device_pixel_scale,
                 );
@@ -1039,17 +1320,17 @@ impl PicturePrimitive {
                     clipped.origin,
                     pic_state_for_children.tasks,
                     uv_rect_kind,
                     pic_context.raster_spatial_node_index,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
-                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+                surface_info.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
         }
 
         true
     }
 }
 
 // Calculate a single screen-space UV for a picture.
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,45 +1,63 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
-use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
+use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, LayoutRectAu};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
 use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers};
 use app_units::Au;
 use border::{get_max_scale_for_border, build_border_instances, create_normal_border_prim};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect, TypedScale};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{self, Repetition};
 use intern;
-use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateContext};
+use picture::{ClusterRange, PictureCompositeMode, PicturePrimitive, PictureUpdateContext};
+use picture::{PrimitiveList, SurfaceInfo};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, to_cache_size};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use std::{cmp, fmt, mem, ops, usize};
-use util::{ScaleOffset, MatrixHelpers};
+#[cfg(debug_assertions)]
+use std::sync::atomic::{AtomicUsize, Ordering};
+use util::{ScaleOffset, MatrixHelpers, MaxRect};
 use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
+/// Counter for unique primitive IDs for debug tracing.
+#[cfg(debug_assertions)]
+static NEXT_PRIM_ID: AtomicUsize = AtomicUsize::new(0);
+
+#[cfg(debug_assertions)]
+static PRIM_CHASE_ID: AtomicUsize = AtomicUsize::new(usize::MAX);
+
+#[cfg(debug_assertions)]
+pub fn register_prim_chase_id(id: PrimitiveDebugId) {
+    PRIM_CHASE_ID.store(id.0, Ordering::SeqCst);
+}
+
+#[cfg(not(debug_assertions))]
+pub fn register_prim_chase_id(_: PrimitiveDebugId) {
+}
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
     pub spatial_node_index: SpatialNodeIndex,
     pub clip_chain_id: ClipChainId,
@@ -276,50 +294,57 @@ impl GpuCacheAddress {
 }
 
 /// The information about an interned primitive that
 /// is stored and available in the scene builder
 /// thread.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveSceneData {
-    // TODO(gw): We will store the local clip rect of
-    //           the primitive here. This will allow
-    //           fast calculation of the tight local
-    //           bounding rect of a primitive during
-    //           picture traversal.
+    pub culling_rect: LayoutRect,
+    pub is_backface_visible: bool,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct PrimitiveKey {
     pub is_backface_visible: bool,
+    pub prim_rect: LayoutRectAu,
+    pub clip_rect: LayoutRectAu,
 }
 
 impl PrimitiveKey {
     pub fn new(
         is_backface_visible: bool,
+        prim_rect: LayoutRect,
+        clip_rect: LayoutRect,
     ) -> Self {
         PrimitiveKey {
             is_backface_visible,
+            prim_rect: prim_rect.to_au(),
+            clip_rect: clip_rect.to_au(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveTemplate {
     pub is_backface_visible: bool,
+    pub prim_rect: LayoutRect,
+    pub clip_rect: LayoutRect,
 }
 
 impl From<PrimitiveKey> for PrimitiveTemplate {
     fn from(item: PrimitiveKey) -> Self {
         PrimitiveTemplate {
             is_backface_visible: item.is_backface_visible,
+            prim_rect: LayoutRect::from_au(item.prim_rect),
+            clip_rect: LayoutRect::from_au(item.clip_rect),
         }
     }
 }
 
 // Type definitions for interning primitives.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone, Copy, Debug)]
@@ -418,19 +443,16 @@ pub enum BorderSource {
 }
 
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
-    Picture {
-        pic_index: PictureIndex,
-    },
     Image {
         request: ImageRequest,
         alpha_type: AlphaType,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         color: ColorF,
         source: ImageSource,
         sub_rect: Option<DeviceIntRect>,
@@ -492,20 +514,16 @@ impl BrushKind {
             }
 
             BrushKind::Solid { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
             BrushKind::Border { .. } |
             BrushKind::LinearGradient { .. } => true,
 
-            // TODO(gw): Allow batch.rs to add segment instances
-            //           for Picture primitives.
-            BrushKind::Picture { .. } => false,
-
             BrushKind::Clear => false,
 
             BrushKind::LineDecoration { .. } => false,
         }
     }
 
     // Construct a brush that is a solid color rectangle.
     pub fn new_solid(color: ColorF) -> BrushKind {
@@ -687,25 +705,16 @@ impl BrushPrimitive {
         segment_desc: Option<BrushSegmentDescriptor>,
     ) -> Self {
         BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
-    pub fn new_picture(pic_index: PictureIndex) -> Self {
-        BrushPrimitive {
-            kind: BrushKind::Picture {
-                pic_index,
-            },
-            segment_desc: None,
-        }
-    }
-
     pub fn new_line_decoration(
         color: ColorF,
         style: LineStyle,
         orientation: LineOrientation,
         wavy_line_thickness: f32,
     ) -> Self {
         BrushPrimitive::new(
             BrushKind::LineDecoration {
@@ -742,26 +751,16 @@ impl BrushPrimitive {
             BrushKind::YuvImage { color_depth, .. } => {
                 request.push([
                     color_depth.rescaling_factor(),
                     0.0,
                     0.0,
                     0.0
                 ]);
             }
-            BrushKind::Picture { .. } => {
-                request.push(PremultipliedColorF::WHITE);
-                request.push(PremultipliedColorF::WHITE);
-                request.push([
-                    local_rect.size.width,
-                    local_rect.size.height,
-                    0.0,
-                    0.0,
-                ]);
-            }
             // Images are drawn as a white color, modulated by the total
             // opacity coming from any collapsed property bindings.
             BrushKind::Image { stretch_size, tile_spacing, color, ref opacity_binding, .. } => {
                 request.push(color.scale_alpha(opacity_binding.current).premultiplied());
                 request.push(PremultipliedColorF::WHITE);
                 request.push([
                     stretch_size.width + tile_spacing.width,
                     stretch_size.height + tile_spacing.height,
@@ -1448,17 +1447,16 @@ impl PrimitiveContainer {
                 match brush.kind {
                     BrushKind::Solid { ref color, .. } => {
                         color.a > 0.0
                     }
                     BrushKind::LineDecoration { ref color, .. } => {
                         color.a > 0.0
                     }
                     BrushKind::Clear |
-                    BrushKind::Picture { .. } |
                     BrushKind::Image { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
@@ -1533,17 +1531,16 @@ impl PrimitiveContainer {
                         PrimitiveContainer::Brush(BrushPrimitive::new(
                             BrushKind::new_image(request.clone(),
                                                  stretch_size.clone(),
                                                  shadow.color),
                             None,
                         ))
                     }
                     BrushKind::Clear |
-                    BrushKind::Picture { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
         }
@@ -1556,30 +1553,49 @@ pub enum PrimitiveDetails {
 }
 
 pub struct Primitive {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
     pub details: PrimitiveDetails,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveDebugId(pub usize);
+
+#[derive(Clone, Debug)]
+pub enum PrimitiveInstanceKind {
+    Picture {
+        pic_index: PictureIndex,
+    },
+    Primitive {
+        prim_index: PrimitiveIndex,
+    },
+}
+
 #[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
-    /// Index into the prim store containing information about
-    /// the specific primitive. This will be removed once all
-    /// primitive data is interned.
-    pub prim_index: PrimitiveIndex,
+    /// Identifies the kind of primitive this
+    /// instance is, and references to where
+    /// the relevant information for the primitive
+    /// can be found.
+    pub kind: PrimitiveInstanceKind,
 
     /// Handle to the common interned data for this primitive.
     pub prim_data_handle: PrimitiveDataHandle,
 
     /// 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 world rect of this primitive, clipped to the
     /// world rect of the screen. None means the primitive is
     /// completely off-screen.
@@ -1599,55 +1615,67 @@ pub struct PrimitiveInstance {
     /// The current opacity of the primitive contents.
     pub opacity: PrimitiveOpacity,
 
     /// ID of the clip chain that this primitive is clipped by.
     pub clip_chain_id: ClipChainId,
 
     /// ID of the spatial node that this primitive is positioned by.
     pub spatial_node_index: SpatialNodeIndex,
+
+    /// A range of clusters that this primitive instance belongs to.
+    pub cluster_range: ClusterRange,
 }
 
 impl PrimitiveInstance {
     pub fn new(
-        prim_index: PrimitiveIndex,
+        kind: PrimitiveInstanceKind,
         prim_data_handle: PrimitiveDataHandle,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
     ) -> Self {
         PrimitiveInstance {
-            prim_index,
+            kind,
             prim_data_handle,
             combined_local_clip_rect: LayoutRect::zero(),
             clipped_world_rect: None,
             #[cfg(debug_assertions)]
             prepared_frame_id: FrameId(0),
+            #[cfg(debug_assertions)]
+            id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)),
             clip_task_id: None,
             gpu_location: GpuCacheHandle::new(),
             opacity: PrimitiveOpacity::translucent(),
             clip_chain_id,
             spatial_node_index,
+            cluster_range: ClusterRange { start: 0, end: 0 },
         }
     }
+
+    #[cfg(debug_assertions)]
+    pub fn is_chased(&self) -> bool {
+        PRIM_CHASE_ID.load(Ordering::SeqCst) == self.id.0
+    }
+
+    #[cfg(not(debug_assertions))]
+    pub fn is_chased(&self) -> bool {
+        false
+    }
 }
 
 pub struct PrimitiveStore {
     pub primitives: Vec<Primitive>,
     pub pictures: Vec<PicturePrimitive>,
-
-    /// A primitive index to chase through debugging.
-    pub chase_id: Option<PrimitiveIndex>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             primitives: Vec::new(),
             pictures: Vec::new(),
-            chase_id: None,
         }
     }
 
     pub fn create_picture(
         &mut self,
         prim: PicturePrimitive,
     ) -> PictureIndex {
         let index = PictureIndex(self.pictures.len());
@@ -1658,30 +1686,39 @@ impl PrimitiveStore {
     /// Update a picture, determining surface configuration,
     /// rasterization roots, and (in future) whether there
     /// are cached surfaces that can be used by this picture.
     pub fn update_picture(
         &mut self,
         pic_index: PictureIndex,
         context: &PictureUpdateContext,
         frame_context: &FrameBuildingContext,
+        surfaces: &mut Vec<SurfaceInfo>,
     ) {
         if let Some((child_context, children)) = self.pictures[pic_index.0].pre_update(
             context,
             frame_context,
+            surfaces,
         ) {
             for child_pic_index in &children {
                 self.update_picture(
                     *child_pic_index,
                     &child_context,
                     frame_context,
+                    surfaces,
                 );
             }
 
-            self.pictures[pic_index.0].post_update(children);
+            self.pictures[pic_index.0].post_update(
+                context,
+                &child_context,
+                children,
+                surfaces,
+                frame_context,
+            );
         }
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayoutRect,
         local_clip_rect: &LayoutRect,
         container: PrimitiveContainer,
@@ -1713,54 +1750,58 @@ impl PrimitiveStore {
     fn get_opacity_collapse_prim(
         &self,
         pic_index: PictureIndex,
     ) -> Option<PrimitiveIndex> {
         let pic = &self.pictures[pic_index.0];
 
         // We can only collapse opacity if there is a single primitive, otherwise
         // the opacity needs to be applied to the primitives as a group.
-        if pic.prim_instances.len() != 1 {
+        if pic.prim_list.prim_instances.len() != 1 {
             return None;
         }
 
-        let prim_instance = &pic.prim_instances[0];
-        let prim = &self.primitives[prim_instance.prim_index.0];
+        let prim_instance = &pic.prim_list.prim_instances[0];
 
         // For now, we only support opacity collapse on solid rects and images.
         // This covers the most common types of opacity filters that can be
         // handled by this optimization. In the future, we can easily extend
         // this to other primitives, such as text runs and gradients.
-        match prim.details {
-            PrimitiveDetails::Brush(ref brush) => {
-                match brush.kind {
-                    BrushKind::Picture { pic_index, .. } => {
-                        let pic = &self.pictures[pic_index.0];
-
-                        // If we encounter a picture that is a pass-through
-                        // (i.e. no composite mode), then we can recurse into
-                        // that to try and find a primitive to collapse to.
-                        if pic.requested_composite_mode.is_none() {
-                            return self.get_opacity_collapse_prim(pic_index);
+        match prim_instance.kind {
+            PrimitiveInstanceKind::Picture { pic_index } => {
+                let pic = &self.pictures[pic_index.0];
+
+                // If we encounter a picture that is a pass-through
+                // (i.e. no composite mode), then we can recurse into
+                // that to try and find a primitive to collapse to.
+                if pic.requested_composite_mode.is_none() {
+                    return self.get_opacity_collapse_prim(pic_index);
+                }
+            }
+            PrimitiveInstanceKind::Primitive { prim_index } => {
+                let prim = &self.primitives[prim_index.0];
+                match prim.details {
+                    PrimitiveDetails::Brush(ref brush) => {
+                        match brush.kind {
+                            // If we find a single rect or image, we can use that
+                            // as the primitive to collapse the opacity into.
+                            BrushKind::Solid { .. } | BrushKind::Image { .. } => {
+                                return Some(prim_index)
+                            }
+                            BrushKind::Border { .. } |
+                            BrushKind::YuvImage { .. } |
+                            BrushKind::LinearGradient { .. } |
+                            BrushKind::RadialGradient { .. } |
+                            BrushKind::LineDecoration { .. } |
+                            BrushKind::Clear => {}
                         }
                     }
-                    // If we find a single rect or image, we can use that
-                    // as the primitive to collapse the opacity into.
-                    BrushKind::Solid { .. } | BrushKind::Image { .. } => {
-                        return Some(prim_instance.prim_index)
-                    }
-                    BrushKind::Border { .. } |
-                    BrushKind::YuvImage { .. } |
-                    BrushKind::LinearGradient { .. } |
-                    BrushKind::RadialGradient { .. } |
-                    BrushKind::LineDecoration { .. } |
-                    BrushKind::Clear => {}
+                    PrimitiveDetails::TextRun(..) => {}
                 }
             }
-            PrimitiveDetails::TextRun(..) => {}
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
     // by pushing that opacity value into the color of a primitive
@@ -1789,17 +1830,16 @@ impl PrimitiveStore {
                         // By this point, we know we should only have found a primitive
                         // that supports opacity collapse.
                         match brush.kind {
                             BrushKind::Solid { ref mut opacity_binding, .. } |
                             BrushKind::Image { ref mut opacity_binding, .. } => {
                                 opacity_binding.push(binding);
                             }
                             BrushKind::Clear { .. } |
-                            BrushKind::Picture { .. } |
                             BrushKind::YuvImage { .. } |
                             BrushKind::Border { .. } |
                             BrushKind::LinearGradient { .. } |
                             BrushKind::LineDecoration { .. } |
                             BrushKind::RadialGradient { .. } => {
                                 unreachable!("bug: invalid prim type for opacity collapse");
                             }
                         }
@@ -1831,187 +1871,175 @@ impl PrimitiveStore {
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
         plane_split_anchor: usize,
-        is_chased: bool,
     ) -> bool {
         // If we have dependencies, we need to prepare them first, in order
         // to know the actual rect of this primitive.
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
         let pic_info = {
-            match self.primitives[prim_instance.prim_index.0].details {
-                PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture { pic_index, .. }, .. }) => {
+            match prim_instance.kind {
+                PrimitiveInstanceKind::Picture { pic_index } => {
                     let pic = &mut self.pictures[pic_index.0];
 
                     match pic.take_context(
                         pic_index,
-                        prim_context,
-                        pic_context.local_spatial_node_index,
                         pic_context.surface_spatial_node_index,
                         pic_context.raster_spatial_node_index,
                         pic_context.allow_subpixel_aa,
                         frame_state,
                         frame_context,
-                        is_chased,
                     ) {
                         Some(info) => Some(info),
-                        None => return false,
+                        None => {
+                            if prim_instance.is_chased() {
+                                println!("\tculled for carrying an invisible composite filter");
+                            }
+
+                            return false;
+                        }
                     }
                 }
-                PrimitiveDetails::Brush(_) |
-                PrimitiveDetails::TextRun(..) => {
+                PrimitiveInstanceKind::Primitive { .. } => {
                     None
                 }
             }
         };
 
         let (is_passthrough, clip_node_collector) = match pic_info {
-            Some((pic_context_for_children, mut pic_state_for_children, mut prim_instances)) => {
+            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;
                 pic_state_for_children.has_non_root_coord_system |=
                     prim_context.spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
                 self.prepare_primitives(
-                    &mut prim_instances,
+                    &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
                 );
 
-                let pic_rect = if is_passthrough {
-                    pic_state.rect = pic_state.rect.union(&pic_state_for_children.rect);
-                    None
-                } else {
-                    Some(pic_state_for_children.rect)
-                };
-
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
-                let (new_local_rect, clip_node_collector) = self
+                let (local_rect_changed, clip_node_collector) = self
                     .pictures[pic_context_for_children.pic_index.0]
                     .restore_context(
-                        prim_instances,
+                        prim_list,
                         pic_context_for_children,
                         pic_state_for_children,
-                        pic_rect,
                         frame_state,
                     );
 
-                let prim = &mut self.primitives[prim_instance.prim_index.0];
-                if new_local_rect != prim.local_rect {
-                    prim.local_rect = new_local_rect;
+                if local_rect_changed {
                     frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                     pic_state.local_rect_changed = true;
                 }
 
                 (is_passthrough, clip_node_collector)
             }
             None => {
                 (false, None)
             }
         };
 
-        let prim = &mut self.primitives[prim_instance.prim_index.0];
-
-        pic_state.is_cacheable &= prim_instance.is_cacheable(
-            &prim.details,
-            frame_state.resource_cache,
-        );
+        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())
+            }
+            PrimitiveInstanceKind::Primitive { prim_index } => {
+                let prim = &self.primitives[prim_index.0];
+                (prim.local_rect, prim.local_clip_rect)
+            }
+        };
 
         if is_passthrough {
             prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
         } else {
-            if prim.local_rect.size.width <= 0.0 ||
-               prim.local_rect.size.height <= 0.0
+            if prim_local_rect.size.width <= 0.0 ||
+               prim_local_rect.size.height <= 0.0
             {
-                if cfg!(debug_assertions) && is_chased {
+                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 local_rect = prim
-                .local_rect
+            let local_rect = prim_local_rect
                 .inflate(pic_context.inflation_factor, pic_context.inflation_factor)
-                .intersection(&prim.local_clip_rect);
+                .intersection(&prim_local_clip_rect);
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
                 None => {
-                    if cfg!(debug_assertions) && is_chased {
+                    if prim_instance.is_chased() {
                         println!("\tculled for being out of the local clip rectangle: {:?}",
-                            prim.local_clip_rect);
+                            prim_local_clip_rect);
                     }
                     return false;
                 }
             };
 
             let clip_chain = frame_state
                 .clip_store
                 .build_clip_chain_instance(
                     prim_instance.clip_chain_id,
                     local_rect,
-                    prim.local_clip_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,
                     &frame_context.world_rect,
                     &clip_node_collector,
                     &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
-                    if cfg!(debug_assertions) && is_chased {
+                    if prim_instance.is_chased() {
                         println!("\tunable to build the clip chain, skipping");
                     }
                     prim_instance.clipped_world_rect = None;
                     return false;
                 }
             };
 
-            if cfg!(debug_assertions) && is_chased {
+            if prim_instance.is_chased() {
                 println!("\teffective clip chain from {:?} {}",
                     clip_chain.clips_range,
                     if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
                 );
             }
 
             pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system;
 
             prim_instance.combined_local_clip_rect = if pic_context.apply_local_clip_rect {
                 clip_chain.local_clip_rect
             } else {
-                prim.local_clip_rect
-            };
-
-            let pic_rect = match pic_state.map_local_to_pic
-                                          .map(&prim.local_rect) {
-                Some(pic_rect) => pic_rect,
-                None => return false,
+                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)
             {
@@ -2026,122 +2054,161 @@ impl PrimitiveStore {
                 None => {
                     return false;
                 }
             };
 
             prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim_instance.update_clip_task(
-                prim.local_rect,
-                prim.local_clip_rect,
-                &mut prim.details,
+                prim_local_rect,
+                prim_local_clip_rect,
                 prim_context,
                 clipped_world_rect,
                 pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
-                is_chased,
                 &clip_node_collector,
+                &mut self.primitives,
             );
 
-            if cfg!(debug_assertions) && is_chased {
+            if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
-
-            pic_state.rect = pic_state.rect.union(&pic_rect);
+        }
+
+        #[cfg(debug_assertions)]
+        {
+            prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
-        prim_instance.prepare_prim_for_render_inner(
-            prim.local_rect,
-            &mut prim.details,
-            prim_context,
-            pic_context,
-            pic_state,
-            &mut self.pictures,
-            frame_context,
-            frame_state,
-            display_list,
-            plane_split_anchor,
-            is_chased,
-        );
+        match prim_instance.kind {
+            PrimitiveInstanceKind::Picture { pic_index } => {
+                let pic = &mut self.pictures[pic_index.0];
+                if pic.prepare_for_render(
+                    pic_index,
+                    prim_instance,
+                    &prim_local_rect,
+                    pic_state,
+                    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,
+                            plane_split_anchor,
+                        );
+                    }
+                } else {
+                    prim_instance.clipped_world_rect = None;
+                }
+
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut prim_instance.gpu_location) {
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        pic.local_rect.size.width,
+                        pic.local_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
+                    request.write_segment(
+                        pic.local_rect,
+                        [0.0; 4],
+                    );
+                }
+            }
+            PrimitiveInstanceKind::Primitive { prim_index } => {
+                let prim_details = &mut self.primitives[prim_index.0].details;
+
+                prim_instance.prepare_prim_for_render_inner(
+                    prim_local_rect,
+                    prim_details,
+                    prim_context,
+                    pic_context,
+                    pic_state,
+                    frame_context,
+                    frame_state,
+                    display_list,
+                );
+            }
+        }
 
         true
     }
 
     pub fn prepare_primitives(
         &mut self,
-        prim_instances: &mut [PrimitiveInstance],
+        prim_list: &mut PrimitiveList,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        for (plane_split_anchor, prim_instance) in prim_instances.iter_mut().enumerate() {
+        for (plane_split_anchor, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
             prim_instance.clipped_world_rect = None;
 
-            let prim_index = prim_instance.prim_index;
-            let is_chased = Some(prim_index) == self.chase_id;
-
-            if cfg!(debug_assertions) && is_chased {
+            if prim_instance.is_chased() {
+                #[cfg(debug_assertions)]
                 println!("\tpreparing {:?} in {:?}",
-                    prim_instance.prim_index, pic_context.pipeline_id);
+                    prim_instance.id, pic_context.pipeline_id);
             }
 
-            // Do some basic checks first, that can early out
-            // without even knowing the local rect.
-            if !frame_state
-                .resources
-                .prim_data_store[prim_instance.prim_data_handle]
-                .is_backface_visible
-            {
-                pic_state.map_local_to_containing_block.set_target_spatial_node(
-                    prim_instance.spatial_node_index,
-                    frame_context.clip_scroll_tree,
-                );
-
-                match pic_state.map_local_to_containing_block.visible_face() {
-                    VisibleFace::Back => {
-                        if cfg!(debug_assertions) && is_chased {
-                            println!("\tculled for not having visible back faces, transform {:?}",
-                                pic_state.map_local_to_containing_block);
-                        }
-                        continue;
-                    }
-                    VisibleFace::Front => {
-                        if cfg!(debug_assertions) && is_chased {
-                            println!("\tprim {:?} is not culled for visible face {:?}, transform {:?}",
-                                prim_index,
-                                pic_state.map_local_to_containing_block.visible_face(),
-                                pic_state.map_local_to_containing_block);
-                            println!("\tpicture context {:?}", pic_context);
-                        }
-                    }
+            // Run through the list of cluster(s) this primitive belongs
+            // to. As soon as we find one visible cluster that this
+            // primitive belongs to, then the primitive itself can be
+            // considered visible.
+            // TODO(gw): Initially, primitive clusters are only used
+            //           to group primitives by backface visibility and
+            //           whether a spatial node is invertible or not.
+            //           In the near future, clusters will also act as
+            //           a simple spatial hash for grouping.
+            // TODO(gw): For now, walk the primitive list and check if
+            //           it is visible in any clusters, as this is a
+            //           simple way to retain correct render order. In
+            //           the future, it might make sense to invert this
+            //           and have the cluster visibility pass produce
+            //           an index buffer / set of primitive instances
+            //           that we sort into render order.
+            let mut in_visible_cluster = false;
+            for ci in prim_instance.cluster_range.start .. prim_instance.cluster_range.end {
+                // Map from the cluster range index to a cluster index
+                let cluster_index = prim_list.prim_cluster_map[ci as usize];
+
+                // Get the cluster and see if is visible
+                let cluster = &prim_list.clusters[cluster_index.0 as usize];
+                in_visible_cluster |= cluster.is_visible;
+
+                // As soon as a primitive is in a visible cluster, it's considered
+                // visible and we don't need to consult other clusters.
+                if cluster.is_visible {
+                    break;
                 }
             }
 
+            // If the primitive wasn't in any visible clusters, it can be skipped.
+            if !in_visible_cluster {
+                continue;
+            }
+
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0];
 
-            if !spatial_node.invertible {
-                if cfg!(debug_assertions) && is_chased {
-                    println!("\tculled for the scroll node transform being invertible");
-                }
-                continue;
-            }
-
             // 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,
             );
 
@@ -2159,17 +2226,16 @@ impl PrimitiveStore {
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 display_list,
                 plane_split_anchor,
-                is_chased,
             ) {
                 frame_state.profile_counters.visible_primitives.inc();
             }
         }
     }
 }
 
 fn build_gradient_stops_request(
@@ -2459,31 +2525,44 @@ impl BrushPrimitive {
     }
 }
 
 impl PrimitiveInstance {
     fn update_clip_task_for_brush(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
-        prim_details: &mut PrimitiveDetails,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
+        primitives: &mut [Primitive],
     ) -> bool {
-        let brush = match *prim_details {
-            PrimitiveDetails::Brush(ref mut brush) => brush,
-            PrimitiveDetails::TextRun(..) => return false,
+        let brush = match self.kind {
+            PrimitiveInstanceKind::Picture { .. } => return false,
+            PrimitiveInstanceKind::Primitive { prim_index } => {
+                let prim = &mut primitives[prim_index.0];
+                match prim.details {
+                    PrimitiveDetails::Brush(ref mut brush) => brush,
+                    PrimitiveDetails::TextRun(..) => return false,
+                }
+            }
         };
 
+        // Reset clip tasks from previous frame
+        if let Some(ref mut desc) = brush.segment_desc {
+            for segment in &mut desc.segments {
+                segment.clip_task_id = BrushSegmentTaskId::Opaque;
+            }
+        }
+
         brush.write_brush_segment_description(
             prim_local_rect,
             prim_local_clip_rect,
             prim_clip_chain,
             frame_state,
         );
 
         let segment_desc = match brush.segment_desc {
@@ -2535,58 +2614,36 @@ impl PrimitiveInstance {
                     frame_context,
                     frame_state,
                 );
             }
         }
 
         true
     }
-
-    // Returns true if the primitive *might* need a clip mask. If
-    // false, there is no need to even check for clip masks for
-    // this primitive.
-    fn reset_clip_task(
-        &mut self,
-        details: &mut PrimitiveDetails,
-    ) {
-        self.clip_task_id = None;
-        match *details {
-            PrimitiveDetails::Brush(ref mut brush) => {
-                if let Some(ref mut desc) = brush.segment_desc {
-                    for segment in &mut desc.segments {
-                        segment.clip_task_id = BrushSegmentTaskId::Opaque;
-                    }
-                }
-            }
-            PrimitiveDetails::TextRun(..) => {}
-        }
-    }
 }
 
 impl PrimitiveInstance {
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_details: &mut PrimitiveDetails,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
-        pictures: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
-        plane_split_anchor: usize,
-        is_chased: bool,
     ) {
         let mut is_tiled = false;
-        #[cfg(debug_assertions)]
-        {
-            self.prepared_frame_id = frame_state.render_tasks.frame_id();
-        }
+
+        pic_state.is_cacheable &= self.is_cacheable(
+            prim_details,
+            frame_state.resource_cache,
+        );
 
         self.opacity = match *prim_details {
             PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_context.spatial_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     &transform,
@@ -2818,17 +2875,17 @@ impl PrimitiveInstance {
                         // Work out the device pixel size to be used to cache this line decoration.
                         let size = get_line_decoration_sizes(
                             &prim_local_rect.size,
                             orientation,
                             style,
                             wavy_line_thickness,
                         );
 
-                        if cfg!(debug_assertions) && is_chased {
+                        if self.is_chased() {
                             println!("\tline decoration opaque={}, sizes={:?}", self.opacity.is_opaque, size);
                         }
 
                         if let Some((inline_size, block_size)) = size {
                             let size = match orientation {
                                 LineOrientation::Horizontal => LayoutSize::new(inline_size, block_size),
                                 LineOrientation::Vertical => LayoutSize::new(block_size, inline_size),
                             };
@@ -3095,41 +3152,16 @@ impl PrimitiveInstance {
                         let stride = stretch_size + tile_spacing;
                         if stride.width >= prim_local_rect.size.width &&
                            stride.height >= prim_local_rect.size.height {
                             stops_opacity
                         } else {
                             PrimitiveOpacity::translucent()
                         }
                     }
-                    BrushKind::Picture { pic_index, .. } => {
-                        let pic = &mut pictures[pic_index.0];
-                        if pic.prepare_for_render(
-                            pic_index,
-                            self,
-                            &prim_local_rect,
-                            pic_state,
-                            frame_context,
-                            frame_state,
-                        ) {
-                            if let Some(ref mut splitter) = pic_state.plane_splitter {
-                                PicturePrimitive::add_split_plane(
-                                    splitter,
-                                    frame_state.transforms,
-                                    self,
-                                    prim_local_rect,
-                                    plane_split_anchor,
-                                );
-                            }
-                        } else {
-                            self.clipped_world_rect = None;
-                        }
-
-                        PrimitiveOpacity::translucent()
-                    }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
                         if opacity_binding.update(frame_context.scene_properties) {
                             frame_state.gpu_cache.invalidate(&mut self.gpu_location);
                         }
@@ -3141,28 +3173,29 @@ impl PrimitiveInstance {
         };
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
+        let is_chased = self.is_chased();
         if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_location) {
             match *prim_details {
                 PrimitiveDetails::TextRun(ref mut text) => {
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveDetails::Brush(ref mut brush) => {
                     brush.write_gpu_blocks(&mut request, prim_local_rect);
 
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
-                                if cfg!(debug_assertions) && is_chased {
+                                if is_chased {
                                     println!("\t\t{:?}", segment);
                                 }
                                 // has to match VECS_PER_SEGMENT
                                 request.write_segment(
                                     segment.local_rect,
                                     segment.extra_data,
                                 );
                             }
@@ -3178,48 +3211,48 @@ impl PrimitiveInstance {
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
         prim_local_rect: LayoutRect,
         prim_local_clip_rect: LayoutRect,
-        prim_details: &mut PrimitiveDetails,
         prim_context: &PrimitiveContext,
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        is_chased: bool,
         clip_node_collector: &Option<ClipNodeCollector>,
+        primitives: &mut [Primitive],
     ) {
-        if cfg!(debug_assertions) && is_chased {
+        if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
+
         // Reset clips from previous frames since we may clip differently each frame.
-        self.reset_clip_task(prim_details);
+        self.clip_task_id = None;
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
             prim_local_rect,
             prim_local_clip_rect,
-            prim_details,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
+            primitives,
         ) {
-            if cfg!(debug_assertions) && is_chased {
+            if self.is_chased() {
                 println!("\tsegment tasks have been created for clipping");
             }
             return;
         }
 
         if clip_chain.needs_mask {
             if let Some((device_rect, _, _)) = get_raster_rects(
                 clip_chain.pic_clip_rect,
@@ -3235,17 +3268,17 @@ impl PrimitiveInstance {
                     frame_state.clip_store,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut frame_state.resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
-                if cfg!(debug_assertions) && is_chased {
+                if self.is_chased() {
                     println!("\tcreated task {:?} with device rect {:?}",
                         clip_task_id, device_rect);
                 }
                 self.clip_task_id = Some(clip_task_id);
                 pic_state.tasks.push(clip_task_id);
             }
         }
     }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -12,16 +12,17 @@ use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
+use picture::SurfaceInfo;
 use prim_store::{PrimitiveStore, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_backend::FrameResources;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
@@ -45,16 +46,17 @@ pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub use_dual_source_blending: bool,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub resources: &'a FrameResources,
+    pub surfaces: &'a [SurfaceInfo],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -763,16 +763,27 @@ impl YuvFormat {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
     pub repeat: bool,
 }
 
+impl ImageMask {
+    /// Get a local clipping rect contributed by this mask.
+    pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
+        if self.repeat {
+            None
+        } else {
+            Some(self.rect)
+        }
+    }
+}
+
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
 
 impl Not for ClipMode {
@@ -859,16 +870,29 @@ impl ComplexClipRegion {
         rect: LayoutRect,
         radii: BorderRadius,
         mode: ClipMode,
     ) -> Self {
         ComplexClipRegion { rect, radii, mode }
     }
 }
 
+impl ComplexClipRegion {
+    /// Get a local clipping rect contributed by this clip region.
+    pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
+        match self.mode {
+            ClipMode::Clip => {
+                Some(self.rect)
+            }
+            ClipMode::ClipOut => {
+                None
+            }
+        }
+    }
+}
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ClipChainId(pub u64, pub PipelineId);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ClipId {
     Spatial(usize, PipelineId),
     Clip(usize, PipelineId),
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-a2997bc78ce6e161fd379e04e2e4479525d12782
+62af01cdf4f09f8f403e4f66f067e8db7ae42329