Test scroll. try: -b do -p linux64 -u all[linux64-qr] -t none
authorGlenn Watson <github@intuitionlibrary.com>
Thu, 16 Nov 2017 07:14:56 +1000
changeset 1352467 e00aabbecfbe81b2525ef23550f806b05888a233
parent 1352367 e070277ec199fa96fa490ed52d33646a376d0d80
child 1590857 da451d8f3070a35e45c02f2d4f0236a17b54f4ff
push id235714
push usergwatson@mozilla.com
push dateWed, 15 Nov 2017 21:21:14 +0000
treeherdertry@e00aabbecfbe [default view] [failures only]
milestone59.0a1
Test scroll. try: -b do -p linux64 -u all[linux64-qr] -t none
gfx/webrender/examples/animation.rs
gfx/webrender/res/ps_blend.glsl
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_bindings/src/bindings.rs
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -19,17 +19,19 @@ extern crate webrender;
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
 use euclid::Radians;
 use webrender::api::*;
 
 struct App {
     property_key: PropertyBindingKey<LayoutTransform>,
+    opacity_key: PropertyBindingKey<f32>,
     transform: LayoutTransform,
+    opacity: f32,
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _resources: &mut ResourceUpdates,
@@ -44,70 +46,84 @@ impl Example for App {
             radii: BorderRadius::uniform(50.0),
             mode: ClipMode::Clip,
         };
         let info = LayoutPrimitiveInfo {
             local_clip: LocalClip::RoundedRect(bounds, complex_clip),
             .. LayoutPrimitiveInfo::new(bounds)
         };
 
+        let filters = vec![
+            FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key), self.opacity),
+        ];
+
         builder.push_stacking_context(
             &info,
             ScrollPolicy::Scrollable,
             Some(PropertyBinding::Binding(self.property_key)),
             TransformStyle::Flat,
             None,
             MixBlendMode::Normal,
-            Vec::new(),
+            filters,
         );
 
         // Fill it with a white rect
         builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
         builder.pop_stacking_context();
     }
 
     fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
         match event {
             glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
-                let (offset_x, offset_y, angle) = match key {
-                    glutin::VirtualKeyCode::Down => (0.0, 10.0, 0.0),
-                    glutin::VirtualKeyCode::Up => (0.0, -10.0, 0.0),
-                    glutin::VirtualKeyCode::Right => (10.0, 0.0, 0.0),
-                    glutin::VirtualKeyCode::Left => (-10.0, 0.0, 0.0),
-                    glutin::VirtualKeyCode::Comma => (0.0, 0.0, 0.1),
-                    glutin::VirtualKeyCode::Period => (0.0, 0.0, -0.1),
+                let (offset_x, offset_y, angle, delta_opacity) = match key {
+                    glutin::VirtualKeyCode::Down => (0.0, 10.0, 0.0, 0.0),
+                    glutin::VirtualKeyCode::Up => (0.0, -10.0, 0.0, 0.0),
+                    glutin::VirtualKeyCode::Right => (10.0, 0.0, 0.0, 0.0),
+                    glutin::VirtualKeyCode::Left => (-10.0, 0.0, 0.0, 0.0),
+                    glutin::VirtualKeyCode::Comma => (0.0, 0.0, 0.1, 0.0),
+                    glutin::VirtualKeyCode::Period => (0.0, 0.0, -0.1, 0.0),
+                    glutin::VirtualKeyCode::Z => (0.0, 0.0, 0.0, -0.1),
+                    glutin::VirtualKeyCode::X => (0.0, 0.0, 0.0, 0.1),
                     _ => return false,
                 };
                 // Update the transform based on the keyboard input and push it to
                 // webrender using the generate_frame API. This will recomposite with
                 // the updated transform.
+                self.opacity += delta_opacity;
                 let new_transform = self.transform
                     .pre_rotate(0.0, 0.0, 1.0, Radians::new(angle))
                     .post_translate(LayoutVector3D::new(offset_x, offset_y, 0.0));
                 api.generate_frame(
                     document_id,
                     Some(DynamicProperties {
                         transforms: vec![
                             PropertyValue {
                                 key: self.property_key,
                                 value: new_transform,
                             },
                         ],
-                        floats: vec![],
+                        floats: vec![
+                            PropertyValue {
+                                key: self.opacity_key,
+                                value: self.opacity,
+                            }
+                        ],
                     }),
                 );
                 self.transform = new_transform;
             }
             _ => (),
         }
 
         false
     }
 }
 
 fn main() {
     let mut app = App {
         property_key: PropertyBindingKey::new(42), // arbitrary magic number
+        opacity_key: PropertyBindingKey::new(43),
         transform: LayoutTransform::create_translation(0.0, 0.0, 0.0),
+        opacity: 0.5,
     };
     boilerplate::main_wrapper(&mut app, None);
 }
--- a/gfx/webrender/res/ps_blend.glsl
+++ b/gfx/webrender/res/ps_blend.glsl
@@ -85,21 +85,16 @@ void main(void) {
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 /* 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/. */
 
-vec4 Blur(float radius, vec2 direction) {
-    // TODO(gw): Support blur in WR2!
-    return vec4(1.0);
-}
-
 vec4 Contrast(vec4 Cs, float amount) {
     return vec4(Cs.rgb * amount - 0.5 * amount + 0.5, 1.0);
 }
 
 vec4 Invert(vec4 Cs, float amount) {
     Cs.rgb /= Cs.a;
 
     vec3 color = mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
@@ -130,18 +125,17 @@ void main(void) {
     vec4 Cs = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
 
     if (Cs.a == 0.0) {
         discard;
     }
 
     switch (vOp) {
         case 0:
-            // Gaussian blur is specially handled:
-            oFragColor = Cs;// Blur(vAmount, vec2(0,0));
+            oFragColor = Cs;
             break;
         case 1:
             oFragColor = Contrast(Cs, vAmount);
             break;
         case 4:
             oFragColor = Invert(Cs, vAmount);
             break;
         case 7:
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -1,15 +1,16 @@
 /* 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::{BorderRadiusKind, ColorF, LayerPoint, LayerRect, LayerSize, LayerVector2D};
 use api::{BorderRadius, BoxShadowClipMode, LayoutSize, LayerPrimitiveInfo};
-use api::{ClipMode, ComplexClipRegion, EdgeAaSegmentMask, LocalClip, ClipAndScrollInfo};
+use api::{ClipMode, ClipAndScrollInfo, ComplexClipRegion, EdgeAaSegmentMask, LocalClip};
+use api::{PipelineId};
 use clip::ClipSource;
 use frame_builder::FrameBuilder;
 use prim_store::{PrimitiveContainer, RectangleContent, RectanglePrimitive};
 use prim_store::{BrushMaskKind, BrushKind, BrushPrimitive};
 use picture::PicturePrimitive;
 use util::RectHelpers;
 use render_task::MAX_BLUR_STD_DEVIATION;
 
@@ -19,16 +20,17 @@ pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 // The amount of padding added to the border corner drawn in the box shadow
 // mask. This ensures that we get a few pixels past the corner that can be
 // blurred without being affected by the border radius.
 pub const MASK_CORNER_PADDING: f32 = 4.0;
 
 impl FrameBuilder {
     pub fn add_box_shadow(
         &mut self,
+        pipeline_id: PipelineId,
         clip_and_scroll: ClipAndScrollInfo,
         prim_info: &LayerPrimitiveInfo,
         box_offset: &LayerVector2D,
         color: &ColorF,
         blur_radius: f32,
         spread_radius: f32,
         border_radius: BorderRadius,
         clip_mode: BoxShadowClipMode,
@@ -46,18 +48,18 @@ impl FrameBuilder {
             }
         };
 
         let shadow_radius = adjust_border_radius_for_box_shadow(
             border_radius,
             spread_amount,
         );
         let shadow_rect = prim_info.rect
-                                   .translate(box_offset)
-                                   .inflate(spread_amount, spread_amount);
+            .translate(box_offset)
+            .inflate(spread_amount, spread_amount);
 
         if blur_radius == 0.0 {
             let mut clips = Vec::new();
 
             let fast_info = match clip_mode {
                 BoxShadowClipMode::Outset => {
                     // TODO(gw): Add a fast path for ClipOut + zero border radius!
                     clips.push(ClipSource::RoundedRectangle(
@@ -180,23 +182,22 @@ impl FrameBuilder {
                     // Create a box shadow picture and add the mask primitive to it.
                     let pic_rect = shadow_rect.inflate(blur_offset, blur_offset);
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
                         blur_radius,
                         *color,
                         Vec::new(),
                         clip_mode,
                         radii_kind,
+                        pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
-                        &brush_rect,
                         clip_and_scroll
                     );
-                    pic_prim.build();
 
                     // TODO(gw): Right now, we always use a clip out
                     //           mask for outset shadows. We can make this
                     //           much more efficient when we have proper
                     //           segment logic, by avoiding drawing
                     //           most of the pixels inside and just
                     //           clipping out along the edges.
                     extra_clips.push(ClipSource::RoundedRectangle(
@@ -259,23 +260,22 @@ impl FrameBuilder {
                     // the brush primitive to it.
                     let mut pic_prim = PicturePrimitive::new_box_shadow(
                         blur_radius,
                         *color,
                         Vec::new(),
                         BoxShadowClipMode::Inset,
                         // TODO(gw): Make use of optimization for inset.
                         BorderRadiusKind::NonUniform,
+                        pipeline_id,
                     );
                     pic_prim.add_primitive(
                         brush_prim_index,
-                        &brush_rect,
                         clip_and_scroll
                     );
-                    pic_prim.build();
 
                     // Draw the picture one pixel outside the original
                     // rect to account for the inflate above. This
                     // extra edge will be clipped by the local clip
                     // rect set below.
                     let pic_rect = prim_info.rect.inflate(inflate_size, inflate_size);
                     let pic_info = LayerPrimitiveInfo::with_clip_rect(
                         pic_rect,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -8,17 +8,17 @@ use api::{LayerToWorldTransform, LayoutP
 use border::BorderCornerClipSource;
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::ResourceCache;
 use util::{extract_inner_rect_safe, TransformedRect};
 
-const MAX_CLIP: f32 = 1000000.0;
+pub const MAX_CLIP: f32 = 1000000.0;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub main: LayerRect,
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,24 +1,25 @@
 /* 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::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, LayoutVector2D, PipelineId};
 use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity};
-use api::{StickyOffsetBounds, WorldPoint};
+use api::{LayoutTransform, PropertyBinding, StickyOffsetBounds, WorldPoint};
 use clip::{ClipSourcesHandle, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId, TransformUpdateState};
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use render_task::{ClipChain, ClipChainNode, ClipWorkItem};
 use resource_cache::ResourceCache;
+use scene::SceneProperties;
 use spring::{DAMPING, STIFFNESS, Spring};
 use std::rc::Rc;
 use util::{MatrixHelpers, MaxRect};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
@@ -180,22 +181,26 @@ impl ClipScrollNode {
         clip_rect: LayerRect,
     ) -> Self {
         Self::new(pipeline_id, Some(parent_id), &clip_rect, NodeType::Clip(handle))
     }
 
     pub fn new_reference_frame(
         parent_id: Option<ClipId>,
         frame_rect: &LayerRect,
-        transform: &LayerToScrollTransform,
+        source_transform: Option<PropertyBinding<LayoutTransform>>,
+        source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
     ) -> Self {
+        let identity = LayoutTransform::identity();
         let info = ReferenceFrameInfo {
-            transform: *transform,
+            resolved_transform: LayerToScrollTransform::identity(),
+            source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
+            source_perspective: source_perspective.unwrap_or(identity),
             origin_in_parent_reference_frame,
         };
         Self::new(pipeline_id, parent_id, frame_rect, NodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
         parent_id: ClipId,
         frame_rect: LayerRect,
@@ -268,22 +273,23 @@ impl ClipScrollNode {
     pub fn update(
         &mut self,
         state: &mut TransformUpdateState,
         node_data: &mut Vec<ClipScrollNodeData>,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
+        scene_properties: &SceneProperties,
     ) {
         // We set this earlier so that we can use it before we have all the data necessary
         // to populate the ClipScrollNodeData.
         self.node_data_index = ClipScrollNodeIndex(node_data.len() as u32);
 
-        self.update_transform(state);
+        self.update_transform(state, scene_properties);
         self.update_clip_work_item(
             state,
             device_pixel_ratio,
             clip_store,
             resource_cache,
             gpu_cache,
         );
 
@@ -304,16 +310,17 @@ impl ClipScrollNode {
                     local_clip_rect,
                     reference_frame_relative_scroll_offset:
                         self.reference_frame_relative_scroll_offset,
                     scroll_offset: self.scroll_offset(),
                 }
             }
             None => {
                 state.combined_outer_clip_bounds = DeviceIntRect::zero();
+                self.combined_clip_outer_bounds = DeviceIntRect::zero();
                 ClipScrollNodeData::invalid()
             }
         };
 
         // Write the data that will be made available to the GPU for this node.
         node_data.push(data);
     }
 
@@ -360,30 +367,43 @@ impl ClipScrollNode {
             },
             prev: current_clip_chain,
         }));
 
         state.combined_outer_clip_bounds = self.combined_clip_outer_bounds;
         state.parent_clip_chain = self.clip_chain_node.clone();
     }
 
-    pub fn update_transform(&mut self, state: &mut TransformUpdateState) {
+    pub fn update_transform(
+        &mut self,
+        state: &mut TransformUpdateState,
+        scene_properties: &SceneProperties,
+    ) {
         // We calculate this here to avoid a double-borrow later.
         let sticky_offset = self.calculate_sticky_offset(
             &state.nearest_scrolling_ancestor_offset,
             &state.nearest_scrolling_ancestor_viewport,
         );
 
         let (local_transform, accumulated_scroll_offset) = match self.node_type {
-            NodeType::ReferenceFrame(ref info) => {
-                self.combined_local_viewport_rect = info.transform
+            NodeType::ReferenceFrame(ref mut info) => {
+                // Resolve the transform against any property bindings.
+                let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+                info.resolved_transform = LayerToScrollTransform::create_translation(
+                    info.origin_in_parent_reference_frame.x,
+                    info.origin_in_parent_reference_frame.y,
+                    0.0
+                ).pre_mul(&source_transform)
+                 .pre_mul(&info.source_perspective);
+
+                self.combined_local_viewport_rect = info.resolved_transform
                     .with_destination::<LayerPixel>()
                     .inverse_rect_footprint(&state.parent_combined_viewport_rect);
                 self.reference_frame_relative_scroll_offset = LayerVector2D::zero();
-                (info.transform, state.parent_accumulated_scroll_offset)
+                (info.resolved_transform, state.parent_accumulated_scroll_offset)
             }
             NodeType::Clip(_) | NodeType::ScrollFrame(_) => {
                 // Move the parent's viewport into the local space (of the node origin)
                 // and intersect with the local clip rectangle to get the local viewport.
                 self.combined_local_viewport_rect =
                     state.parent_combined_viewport_rect
                     .intersection(&self.local_clip_rect)
                     .unwrap_or(LayerRect::zero());
@@ -431,17 +451,17 @@ impl ClipScrollNode {
             NodeType::ReferenceFrame(ref info) => {
                 state.parent_reference_frame_transform = self.world_viewport_transform;
                 state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
                 state.parent_accumulated_scroll_offset = LayerVector2D::zero();
                 state.nearest_scrolling_ancestor_viewport =
                     state.nearest_scrolling_ancestor_viewport
                        .translate(&info.origin_in_parent_reference_frame);
 
-                if !info.transform.preserves_2d_axis_alignment() {
+                if !info.resolved_transform.preserves_2d_axis_alignment() {
                     state.current_coordinate_system_id = state.next_coordinate_system_id;
                     state.next_coordinate_system_id = state.next_coordinate_system_id.next();
                 }
             },
             NodeType::Clip(..) => {
                 state.parent_combined_viewport_rect = self.combined_local_viewport_rect;
             },
             NodeType::ScrollFrame(ref scrolling) => {
@@ -782,15 +802,22 @@ impl ScrollingState {
     }
 }
 
 /// Contains information about reference frames.
 #[derive(Copy, Clone, Debug)]
 pub struct ReferenceFrameInfo {
     /// The transformation that establishes this reference frame, relative to the parent
     /// reference frame. The origin of the reference frame is included in the transformation.
-    pub transform: LayerToScrollTransform,
+    pub resolved_transform: LayerToScrollTransform,
+
+    /// The source transform and perspective matrices provided by the stacking context
+    /// that forms this reference frame. We maintain the property binding information
+    /// here so that we can resolve the animated transform and update the tree each
+    /// frame.
+    pub source_transform: PropertyBinding<LayoutTransform>,
+    pub source_perspective: LayoutTransform,
 
     /// The original, not including the transform and relative to the parent reference frame,
     /// origin of this reference frame. This is already rolled into the `transform' property, but
     /// we also store it here to properly transform the viewport for sticky positioning.
     pub origin_in_parent_reference_frame: LayerVector2D,
 }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,23 +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::{ClipId, DeviceIntRect, LayerPoint, LayerRect, LayerToScrollTransform};
+use api::{ClipId, DeviceIntRect, LayerPoint, LayerRect};
 use api::{LayerToWorldTransform, LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase};
-use api::{ScrollLayerState, ScrollLocation, WorldPoint};
+use api::{PropertyBinding, LayoutTransform, ScrollLayerState, ScrollLocation, WorldPoint};
 use clip::ClipStore;
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState, StickyFrameInfo};
 use gpu_cache::GpuCache;
 use gpu_types::ClipScrollNodeData;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use render_task::ClipChain;
 use resource_cache::ResourceCache;
+use scene::SceneProperties;
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -188,19 +189,18 @@ impl ClipScrollTree {
             }
         };
 
         if !node.local_clip_rect.contains(&transformed_point) {
             cache.insert(*node_id, None);
             return false;
         }
 
-        let point_in_clips = transformed_point - node.local_clip_rect.origin.to_vector();
         for &(ref clip, _) in clip_store.get(&clip_sources_handle).clips() {
-            if !clip.contains(&point_in_clips) {
+            if !clip.contains(&transformed_point) {
                 cache.insert(*node_id, None);
                 return false;
             }
         }
 
         cache.insert(*node_id, Some(point_in_layer));
 
         true
@@ -329,16 +329,17 @@ impl ClipScrollTree {
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pan: LayerPoint,
         node_data: &mut Vec<ClipScrollNodeData>,
+        scene_properties: &SceneProperties,
     ) {
         if self.nodes.is_empty() {
             return;
         }
 
         let root_reference_frame_id = self.root_reference_frame_id();
         let root_viewport = self.nodes[&root_reference_frame_id].local_clip_rect;
 
@@ -360,28 +361,30 @@ impl ClipScrollTree {
         self.update_node(
             root_reference_frame_id,
             &mut state,
             device_pixel_ratio,
             clip_store,
             resource_cache,
             gpu_cache,
             node_data,
+            scene_properties,
         );
     }
 
     fn update_node(
         &mut self,
         layer_id: ClipId,
         state: &mut TransformUpdateState,
         device_pixel_ratio: f32,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         node_data: &mut Vec<ClipScrollNodeData>,
+        scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
             let node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
@@ -389,30 +392,32 @@ impl ClipScrollTree {
 
             node.update(
                 &mut state,
                 node_data,
                 device_pixel_ratio,
                 clip_store,
                 resource_cache,
                 gpu_cache,
+                scene_properties,
             );
 
             node.children.clone()
         };
 
         for child_layer_id in node_children {
             self.update_node(
                 child_layer_id,
                 &mut state,
                 device_pixel_ratio,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 node_data,
+                scene_properties,
             );
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
@@ -436,32 +441,34 @@ impl ClipScrollTree {
         let new_id = ClipId::DynamicallyAddedNode(self.current_new_node_item, pipeline_id);
         self.current_new_node_item += 1;
         new_id
     }
 
     pub fn add_reference_frame(
         &mut self,
         rect: &LayerRect,
-        transform: &LayerToScrollTransform,
+        source_transform: Option<PropertyBinding<LayoutTransform>>,
+        source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
         parent_id: Option<ClipId>,
         root_for_pipeline: bool,
     ) -> ClipId {
         let reference_frame_id = if root_for_pipeline {
             ClipId::root_reference_frame(pipeline_id)
         } else {
             self.generate_new_clip_id(pipeline_id)
         };
 
         let node = ClipScrollNode::new_reference_frame(
             parent_id,
             rect,
-            transform,
+            source_transform,
+            source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
     pub fn add_sticky_frame(
@@ -511,17 +518,17 @@ impl ClipScrollTree {
                 let clips = clip_store.get(&clip_sources_handle).clips();
                 pt.new_level(format!("Clip Sources [{}]", clips.len()));
                 for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
-                pt.new_level(format!("ReferenceFrame {:?}", info.transform));
+                pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
                 pt.add_item(format!("id: {:?}", id));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
                 pt.add_item(format!("id: {:?}", id));
                 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
                 pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
             }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1133,25 +1133,24 @@ impl Device {
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.mode = mode;
 
         let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
         let type_ = gl_type_for_texture_format(format);
 
+        self.bind_texture(DEFAULT_TEXTURE, texture);
+        self.set_texture_parameters(texture.target, filter);
+
         match mode {
             RenderTargetMode::RenderTarget => {
-                self.bind_texture(DEFAULT_TEXTURE, texture);
-                self.set_texture_parameters(texture.target, filter);
-                self.update_texture_storage(texture, layer_count, resized);
+                self.update_texture_storage(texture, resized);
             }
             RenderTargetMode::None => {
-                self.bind_texture(DEFAULT_TEXTURE, texture);
-                self.set_texture_parameters(texture.target, filter);
                 let expanded_data: Vec<u8>;
                 let actual_pixels = if pixels.is_some() && format == ImageFormat::A8 &&
                     cfg!(any(target_arch = "arm", target_arch = "aarch64"))
                 {
                     expanded_data = pixels
                         .unwrap()
                         .iter()
                         .flat_map(|&byte| repeat(byte).take(4))
@@ -1192,72 +1191,75 @@ impl Device {
                     _ => panic!("BUG: Unexpected texture target!"),
                 }
             }
         }
     }
 
     /// Updates the texture storage for the texture, creating
     /// FBOs as required.
-    fn update_texture_storage(&mut self, texture: &mut Texture, layer_count: i32, resized: bool) {
-        assert!(layer_count > 0);
+    fn update_texture_storage(&mut self, texture: &mut Texture, resized: bool) {
+        assert!(texture.layer_count > 0);
         assert_eq!(texture.target, gl::TEXTURE_2D_ARRAY);
 
-        let current_layer_count = texture.fbo_ids.len() as i32;
+        let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
         // If the texture is already the required size skip.
-        if current_layer_count == layer_count && !resized {
+        if needed_layer_count == 0 && !resized {
             return;
         }
 
         let (internal_format, gl_format) =
             gl_texture_formats_for_image_format(&*self.gl, texture.format);
         let type_ = gl_type_for_texture_format(texture.format);
 
         self.gl.tex_image_3d(
             texture.target,
             0,
             internal_format as gl::GLint,
             texture.width as gl::GLint,
             texture.height as gl::GLint,
-            layer_count,
+            texture.layer_count,
             0,
             gl_format,
             type_,
             None,
         );
 
-        let needed_layer_count = layer_count - current_layer_count;
         if needed_layer_count > 0 {
             // Create more framebuffers to fill the gap
             let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
             texture
                 .fbo_ids
-                .extend(new_fbos.into_iter().map(|id| FBOId(id)));
+                .extend(new_fbos.into_iter().map(FBOId));
         } else if needed_layer_count < 0 {
             // Remove extra framebuffers
-            for old in texture.fbo_ids.drain(layer_count as usize ..) {
+            for old in texture.fbo_ids.drain(texture.layer_count as usize ..) {
                 self.gl.delete_framebuffers(&[old.0]);
             }
         }
 
-        let depth_rb = if let Some(rbo) = texture.depth_rb {
-            rbo.0
-        } else {
-            let renderbuffer_ids = self.gl.gen_renderbuffers(1);
-            let depth_rb = renderbuffer_ids[0];
-            texture.depth_rb = Some(RBOId(depth_rb));
-            depth_rb
+        let (depth_rb, depth_alloc) = match texture.depth_rb {
+            Some(rbo) => (rbo.0, resized),
+            None => {
+                let renderbuffer_ids = self.gl.gen_renderbuffers(1);
+                let depth_rb = renderbuffer_ids[0];
+                texture.depth_rb = Some(RBOId(depth_rb));
+                (depth_rb, true)
+            }
         };
-        self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
-        self.gl.renderbuffer_storage(
-            gl::RENDERBUFFER,
-            gl::DEPTH_COMPONENT24,
-            texture.width as gl::GLsizei,
-            texture.height as gl::GLsizei,
-        );
+
+        if depth_alloc {
+            self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
+            self.gl.renderbuffer_storage(
+                gl::RENDERBUFFER,
+                gl::DEPTH_COMPONENT24,
+                texture.width as gl::GLsizei,
+                texture.height as gl::GLsizei,
+            );
+        }
 
         for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
             self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
             self.gl.framebuffer_texture_layer(
                 gl::FRAMEBUFFER,
                 gl::COLOR_ATTACHMENT0,
                 texture.id,
                 0,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -1,32 +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::{BuiltDisplayListIter, ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion};
 use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, Epoch, FilterOp};
 use api::{ImageDisplayItem, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect};
-use api::{LayerSize, LayerToScrollTransform, LayerVector2D};
-use api::{LayoutRect, LayoutSize, LayoutTransform};
+use api::{LayerSize, LayerVector2D};
+use api::{LayoutRect, LayoutSize, LayoutTransform, PropertyBinding};
 use api::{LocalClip, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
 use api::{ScrollLocation, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
 use api::{ClipMode, TileOffset, TransformStyle, WorldPoint};
 use clip::ClipRegion;
 use clip_scroll_node::StickyFrameInfo;
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
 use internal_types::{FastHashMap, FastHashSet, RendererFrame};
 use prim_store::RectangleContent;
 use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
 use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
-use scene::{Scene, StackingContextHelpers, ScenePipeline};
+use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
 use tiling::{CompositeOps, Frame};
 use util::ComplexClipRegionHelpers;
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
@@ -43,16 +43,17 @@ struct FlattenContext<'a> {
     tiled_image_map: TiledImageMap,
     pipeline_epochs: Vec<(PipelineId, Epoch)>,
     replacements: Vec<(ClipId, ClipId)>,
     /// Opaque rectangle vector, stored here in order to
     /// avoid re-allocation on each use.
     opaque_parts: Vec<LayoutRect>,
     /// Same for the transparent rectangles.
     transparent_parts: Vec<LayoutRect>,
+    output_pipelines: &'a FastHashSet<PipelineId>,
 }
 
 impl<'a> FlattenContext<'a> {
     /// Since WebRender still handles fixed position and reference frame content internally
     /// we need to apply this table of id replacements only to the id that affects the
     /// position of a node. We can eventually remove this when clients start handling
     /// reference frames themselves. This method applies these replacements.
     fn apply_scroll_frame_id_replacement(&self, id: ClipId) -> ClipId {
@@ -83,31 +84,28 @@ impl<'a> FlattenContext<'a> {
     fn flatten_root(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         frame_size: &LayoutSize,
         root_reference_frame_id: ClipId,
         root_scroll_frame_id: ClipId,
     ) {
+        let clip_id = ClipId::root_scroll_node(pipeline_id);
+
         self.builder.push_stacking_context(
-            &LayerVector2D::zero(),
             pipeline_id,
             CompositeOps::default(),
             TransformStyle::Flat,
             true,
             true,
+            ClipAndScrollInfo::simple(clip_id),
+            self.output_pipelines,
         );
 
-        // We do this here, rather than above because we want any of the top-level
-        // stacking contexts in the display list to be treated like root stacking contexts.
-        // FIXME(mrobinson): Currently only the first one will, which for the moment is
-        // sufficient for all our use cases.
-        self.builder.notify_waiting_for_root_stacking_context();
-
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         if self.scene.root_pipeline_id != Some(pipeline_id) {
             if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
                     let info = LayerPrimitiveInfo::new(root_bounds);
                     self.builder.add_solid_rectangle(
@@ -116,17 +114,21 @@ impl<'a> FlattenContext<'a> {
                         RectangleContent::Fill(bg_color),
                         None,
                     );
                 }
             }
         }
 
 
-        self.flatten_items(traversal, pipeline_id, LayerVector2D::zero());
+        self.flatten_items(
+            traversal,
+            pipeline_id,
+            LayerVector2D::zero(),
+        );
 
         if self.builder.config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
             let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
             self.builder.add_solid_rectangle(
                 ClipAndScrollInfo::simple(root_reference_frame_id),
                 &LayerPrimitiveInfo::new(scrollbar_rect),
                 RectangleContent::Fill(DEFAULT_SCROLLBAR_COLOR),
@@ -149,17 +151,21 @@ impl<'a> FlattenContext<'a> {
                     Some(item) => item,
                     None => break,
                 };
 
                 if SpecificDisplayItem::PopStackingContext == *item.item() {
                     return;
                 }
 
-                self.flatten_item(item, pipeline_id, reference_frame_relative_offset)
+                self.flatten_item(
+                    item,
+                    pipeline_id,
+                    reference_frame_relative_offset,
+                )
             };
 
             // If flatten_item created a sub-traversal, we need `traversal` to have the
             // same state as the completed subtraversal, so we reinitialize it here.
             if let Some(subtraversal) = subtraversal {
                 *traversal = subtraversal;
             }
         }
@@ -235,17 +241,16 @@ impl<'a> FlattenContext<'a> {
                 .pipelines
                 .get(&pipeline_id)
                 .expect("No display list?!")
                 .display_list;
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(
                     display_list,
                     filters,
-                    &self.scene.properties,
                 ),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             self.replacements.push((
                 context_scroll_node_id,
@@ -253,53 +258,48 @@ impl<'a> FlattenContext<'a> {
             ));
         }
 
         // If we have a transformation, we establish a new reference frame. This means
         // that fixed position stacking contexts are positioned relative to us.
         let is_reference_frame =
             stacking_context.transform.is_some() || stacking_context.perspective.is_some();
         if is_reference_frame {
-            let transform = stacking_context.transform.as_ref();
-            let transform = self.scene.properties.resolve_layout_transform(transform);
-            let perspective = stacking_context
-                .perspective
-                .unwrap_or_else(LayoutTransform::identity);
             let origin = reference_frame_relative_offset + bounds.origin.to_vector();
-            let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0)
-                .pre_mul(&transform)
-                .pre_mul(&perspective);
-
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
             let mut clip_id = self.apply_scroll_frame_id_replacement(context_scroll_node_id);
             clip_id = self.builder.push_reference_frame(
                 Some(clip_id),
                 pipeline_id,
                 &reference_frame_bounds,
-                &transform,
+                stacking_context.transform,
+                stacking_context.perspective,
                 origin,
                 false,
                 self.clip_scroll_tree,
             );
             self.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerVector2D::zero();
         } else {
             reference_frame_relative_offset = LayerVector2D::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y,
             );
-        }
+        };
+
+        let sc_scroll_node_id = self.apply_scroll_frame_id_replacement(context_scroll_node_id);
 
         self.builder.push_stacking_context(
-            &reference_frame_relative_offset,
             pipeline_id,
             composition_operations,
             stacking_context.transform_style,
             is_backface_visible,
             false,
+            ClipAndScrollInfo::simple(sc_scroll_node_id),
+            self.output_pipelines,
         );
 
         self.flatten_items(
             traversal,
             pipeline_id,
             reference_frame_relative_offset,
         );
 
@@ -342,22 +342,22 @@ impl<'a> FlattenContext<'a> {
             clip_region,
             self.clip_scroll_tree,
         );
 
         self.pipeline_epochs.push((pipeline_id, pipeline.epoch));
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let origin = reference_frame_relative_offset + bounds.origin.to_vector();
-        let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0);
         let iframe_reference_frame_id = self.builder.push_reference_frame(
             Some(clip_id),
             pipeline_id,
             &iframe_rect,
-            &transform,
+            None,
+            None,
             origin,
             true,
             self.clip_scroll_tree,
         );
 
         self.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
@@ -513,16 +513,17 @@ impl<'a> FlattenContext<'a> {
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info
                     .box_bounds
                     .translate(&reference_frame_relative_offset);
                 let mut prim_info = prim_info.clone();
                 prim_info.rect = bounds;
                 self.builder.add_box_shadow(
+                    pipeline_id,
                     clip_and_scroll,
                     &prim_info,
                     &box_shadow_info.offset,
                     &box_shadow_info.color,
                     box_shadow_info.blur_radius,
                     box_shadow_info.spread_radius,
                     box_shadow_info.border_radius,
                     box_shadow_info.clip_mode,
@@ -1083,16 +1084,17 @@ impl FrameContext {
     pub fn create(
         &mut self,
         old_builder: Option<FrameBuilder>,
         scene: &Scene,
         resource_cache: &mut ResourceCache,
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
+        output_pipelines: &FastHashSet<PipelineId>,
     ) -> Option<FrameBuilder> {
         let root_pipeline_id = match scene.root_pipeline_id {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return old_builder,
         };
 
         let root_pipeline = match scene.pipelines.get(&root_pipeline_id) {
             Some(root_pipeline) => root_pipeline,
@@ -1123,16 +1125,17 @@ impl FrameContext {
                 ),
                 clip_scroll_tree: &mut self.clip_scroll_tree,
                 font_instances: resource_cache.get_font_instances(),
                 tiled_image_map: resource_cache.get_tiled_image_map(),
                 pipeline_epochs: Vec::new(),
                 replacements: Vec::new(),
                 opaque_parts: Vec::new(),
                 transparent_parts: Vec::new(),
+                output_pipelines,
             };
 
             roller.builder.push_root(
                 root_pipeline_id,
                 &root_pipeline.viewport_size,
                 &root_pipeline.content_size,
                 roller.clip_scroll_tree,
             );
@@ -1149,16 +1152,18 @@ impl FrameContext {
             roller.flatten_root(
                 &mut root_pipeline.display_list.iter(),
                 root_pipeline_id,
                 &root_pipeline.viewport_size,
                 reference_frame_id,
                 scroll_frame_id,
             );
 
+            debug_assert!(roller.builder.picture_stack.is_empty());
+
             self.pipeline_epoch_map.extend(roller.pipeline_epochs.drain(..));
             roller.builder
         };
 
         self.clip_scroll_tree
             .finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
         Some(frame_builder)
     }
@@ -1175,31 +1180,31 @@ impl FrameContext {
     pub fn build_renderer_frame(
         &mut self,
         frame_builder: &mut FrameBuilder,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         device_pixel_ratio: f32,
         pan: LayerPoint,
-        output_pipelines: &FastHashSet<PipelineId>,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
+        scene_properties: &SceneProperties,
     ) -> RendererFrame {
         let frame = frame_builder.build(
             resource_cache,
             gpu_cache,
             self.id,
             &mut self.clip_scroll_tree,
             pipelines,
             device_pixel_ratio,
             pan,
-            output_pipelines,
             texture_cache_profile,
             gpu_cache_profile,
+            scene_properties,
         );
 
         self.get_renderer_frame_impl(Some(frame))
     }
 
     pub fn get_renderer_frame(&self) -> RendererFrame {
         self.get_renderer_frame_impl(None)
     }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,87 +1,76 @@
 /* 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::{BorderDetails, BorderDisplayItem, BuiltDisplayList};
-use api::{ClipAndScrollInfo, ClipId, ColorF, PremultipliedColorF};
+use api::{ClipAndScrollInfo, ClipId, ColorF, PropertyBinding};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
-use api::{ExtendMode, FilterOp, FontRenderMode};
+use api::{ExtendMode, FontRenderMode, LayoutTransform};
 use api::{GlyphInstance, GlyphOptions, GradientStop, HitTestFlags, HitTestItem, HitTestResult};
 use api::{ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerPrimitiveInfo, LayerRect};
-use api::{LayerPixel, LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation};
+use api::{LayerSize, LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation};
 use api::{LineStyle, LocalClip, PipelineId, RepeatMode};
 use api::{ScrollSensitivity, Shadow, TileOffset, TransformStyle};
-use api::{WorldPixel, WorldPoint, YuvColorSpace, YuvData, device_length};
+use api::{WorldPoint, YuvColorSpace, YuvData};
 use app_units::Au;
 use border::ImageBorderSegment;
-use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains};
+use clip::{ClipRegion, ClipSource, ClipSources, ClipStore, Contains, MAX_CLIP};
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
-use euclid::{SideOffsets2D, TypedTransform3D, vec2, vec3};
+use euclid::{SideOffsets2D, vec2};
 use frame::FrameId;
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCache;
-use internal_types::{FastHashMap, FastHashSet, HardwareCompositeOp};
-use picture::{PictureKind, PicturePrimitive};
-use plane_split::{BspSplitter, Polygon, Splitter};
+use internal_types::{FastHashMap, FastHashSet};
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
 use prim_store::{TexelRect, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, LinePrimitive, PrimitiveKind};
-use prim_store::{PrimitiveContainer, PrimitiveIndex, PrimitiveRun};
+use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{RectangleContent, RectanglePrimitive, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{AlphaRenderItem, ClearMode, RenderTask, RenderTaskId, RenderTaskLocation};
+use render_task::{RenderTask, RenderTaskLocation};
 use render_task::RenderTaskTree;
 use resource_cache::ResourceCache;
-use scene::ScenePipeline;
+use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize, f32, i32};
-use tiling::{CompositeOps, ContextIsolation, Frame, PrimitiveRunCmd, RenderPass};
-use tiling::{RenderTargetContext, RenderTargetKind, ScrollbarPrimitive, StackingContext};
-use tiling::StackingContextIndex;
+use tiling::{CompositeOps, Frame};
+use tiling::{RenderPass};
+use tiling::{RenderTargetContext, ScrollbarPrimitive};
 use util::{self, pack_as_float, RectHelpers, recycle_vec};
-use box_shadow::BLUR_SAMPLE_SCALE;
 
 #[derive(Debug)]
 pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
 
-/// Construct a polygon from stacking context boundaries.
-/// `anchor` here is an index that's going to be preserved in all the
-/// splits of the polygon.
-fn make_polygon(
-    stacking_context: &StackingContext,
-    node: &ClipScrollNode,
-    anchor: usize,
-) -> Polygon<f64, WorldPixel> {
-    //TODO: only work with `isolated_items_bounds.size` worth of space
-    // This can be achieved by moving the `origin` shift
-    // from the primitive local coordinates into the layer transformation.
-    // Which in turn needs it to be a render task property obeyed by all primitives
-    // upon rendering, possibly not limited to `write_*_vertex` implementations.
-    let size = stacking_context.isolated_items_bounds.bottom_right();
-    let bounds = LayerRect::new(LayerPoint::zero(), LayerSize::new(size.x, size.y));
-    let mat = TypedTransform3D::row_major(
-        node.world_content_transform.m11 as f64,
-        node.world_content_transform.m12 as f64,
-        node.world_content_transform.m13 as f64,
-        node.world_content_transform.m14 as f64,
-        node.world_content_transform.m21 as f64,
-        node.world_content_transform.m22 as f64,
-        node.world_content_transform.m23 as f64,
-        node.world_content_transform.m24 as f64,
-        node.world_content_transform.m31 as f64,
-        node.world_content_transform.m32 as f64,
-        node.world_content_transform.m33 as f64,
-        node.world_content_transform.m34 as f64,
-        node.world_content_transform.m41 as f64,
-        node.world_content_transform.m42 as f64,
-        node.world_content_transform.m43 as f64,
-        node.world_content_transform.m44 as f64);
-    Polygon::from_transformed_rect(bounds.cast().unwrap(), mat, anchor)
+/// Properties of a stacking context that are maintained
+/// during creation of the scene. These structures are
+/// not persisted after the initial scene build.
+struct StackingContext {
+    /// Pipeline this stacking context belongs to.
+    pipeline_id: PipelineId,
+
+    /// Filters / mix-blend-mode effects
+    composite_ops: CompositeOps,
+
+    /// If true, visible when backface is visible.
+    is_backface_visible: bool,
+
+    /// Allow subpixel AA for text runs on this stacking context.
+    /// This is a temporary hack while we don't support subpixel AA
+    /// on transparent stacking contexts.
+    allow_subpixel_aa: bool,
+
+    /// CSS transform-style property.
+    transform_style: TransformStyle,
+
+    /// The primitive index for the root Picture primitive
+    /// that this stacking context is mapped to.
+    pic_prim_index: PrimitiveIndex,
 }
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
@@ -106,52 +95,49 @@ impl HitTestingItem {
 pub struct HitTestingRun(Vec<HitTestingItem>, ClipAndScrollInfo);
 
 /// A builder structure for `RendererFrame`
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
-    cmds: Vec<PrimitiveRunCmd>,
     hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 
-    stacking_context_store: Vec<StackingContext>,
-
     // A stack of the current shadow primitives.
     // The sub-Vec stores a buffer of fast-path primitives to be appended on pop.
     shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ClipAndScrollInfo)>)>,
     // If we're doing any fast-path shadows, we buffer the "real"
     // content here, to be appended when the shadow stack is empty.
     pending_shadow_contents: Vec<(PrimitiveIndex, ClipAndScrollInfo, LayerPrimitiveInfo)>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
-    /// A stack of stacking contexts used for creating ClipScrollGroups as
-    /// primitives are added to the frame.
-    stacking_context_stack: Vec<StackingContextIndex>,
+    /// A stack of the current pictures, used during scene building.
+    pub picture_stack: Vec<PrimitiveIndex>,
 
-    /// Whether or not we've pushed a root stacking context for the current pipeline.
-    has_root_stacking_context: bool,
+    /// A temporary stack of stacking context properties, used only
+    /// during scene building.
+    sc_stack: Vec<StackingContext>,
 }
 
 pub struct PrimitiveContext<'a> {
     pub device_pixel_ratio: f32,
     pub display_list: &'a BuiltDisplayList,
     pub clip_node: &'a ClipScrollNode,
     pub scroll_node: &'a ClipScrollNode,
 }
 
 impl<'a> PrimitiveContext<'a> {
-    fn new(
+    pub fn new(
         device_pixel_ratio: f32,
         display_list: &'a BuiltDisplayList,
         clip_node: &'a ClipScrollNode,
         scroll_node: &'a ClipScrollNode,
     ) -> Self {
         PrimitiveContext {
             device_pixel_ratio,
             display_list,
@@ -165,46 +151,42 @@ impl FrameBuilder {
     pub fn new(
         previous: Option<Self>,
         screen_size: DeviceUintSize,
         background_color: Option<ColorF>,
         config: FrameBuilderConfig,
     ) -> Self {
         match previous {
             Some(prev) => FrameBuilder {
-                stacking_context_store: recycle_vec(prev.stacking_context_store),
-                cmds: recycle_vec(prev.cmds),
                 hit_testing_runs: recycle_vec(prev.hit_testing_runs),
                 shadow_prim_stack: recycle_vec(prev.shadow_prim_stack),
                 pending_shadow_contents: recycle_vec(prev.pending_shadow_contents),
                 scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                 reference_frame_stack: recycle_vec(prev.reference_frame_stack),
-                stacking_context_stack: recycle_vec(prev.stacking_context_stack),
+                picture_stack: recycle_vec(prev.picture_stack),
+                sc_stack: recycle_vec(prev.sc_stack),
                 prim_store: prev.prim_store.recycle(),
                 clip_store: prev.clip_store.recycle(),
                 screen_size,
                 background_color,
                 config,
-                has_root_stacking_context: false,
             },
             None => FrameBuilder {
-                stacking_context_store: Vec::new(),
-                cmds: Vec::new(),
                 hit_testing_runs: Vec::new(),
                 shadow_prim_stack: Vec::new(),
                 pending_shadow_contents: Vec::new(),
                 scrollbar_prims: Vec::new(),
                 reference_frame_stack: Vec::new(),
-                stacking_context_stack: Vec::new(),
+                picture_stack: Vec::new(),
+                sc_stack: Vec::new(),
                 prim_store: PrimitiveStore::new(),
                 clip_store: ClipStore::new(),
                 screen_size,
                 background_color,
                 config,
-                has_root_stacking_context: false,
             },
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
@@ -217,21 +199,23 @@ impl FrameBuilder {
             clip_sources.push(ClipSource::Rectangle(main));
             clip_sources.push(ClipSource::RoundedRectangle(
                 region.rect,
                 region.radii,
                 region.mode,
             ));
         }
 
+        let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
+
         let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.local_clip.clip_rect(),
-            info.is_backface_visible,
+            info.is_backface_visible && stacking_context.is_backface_visible,
             clip_sources,
             info.tag,
             container,
         );
 
         prim_index
     }
 
@@ -259,36 +243,26 @@ impl FrameBuilder {
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
         prim_index: PrimitiveIndex,
         clip_and_scroll: ClipAndScrollInfo,
     ) {
-        match self.cmds.last_mut().unwrap() {
-            &mut PrimitiveRunCmd::PrimitiveRun(
-                ref mut run,
-            ) => if run.clip_and_scroll == clip_and_scroll &&
-                run.base_prim_index.0 + run.count == prim_index.0
-            {
-                run.count += 1;
-                return;
-            },
-            &mut PrimitiveRunCmd::PushStackingContext(..) |
-            &mut PrimitiveRunCmd::PopStackingContext => {}
-        }
-
-        let run = PrimitiveRun {
-            base_prim_index: prim_index,
-            count: 1,
-            clip_and_scroll,
-        };
-
-        self.cmds.push(PrimitiveRunCmd::PrimitiveRun(run));
+        // Add primitive to the top-most Picture on the stack.
+        // TODO(gw): Let's consider removing the extra indirection
+        //           needed to get a specific primitive index...
+        let pic_prim_index = self.picture_stack.last().unwrap();
+        let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
+        let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
+        pic.add_primitive(
+            prim_index,
+            clip_and_scroll
+        );
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
@@ -297,88 +271,309 @@ impl FrameBuilder {
     ) -> PrimitiveIndex {
         self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
         let prim_index = self.create_primitive(info, clip_sources, container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         prim_index
     }
 
-    pub fn notify_waiting_for_root_stacking_context(&mut self) {
-        self.has_root_stacking_context = false;
-    }
-
     pub fn push_stacking_context(
         &mut self,
-        reference_frame_offset: &LayerVector2D,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
         is_backface_visible: bool,
         is_pipeline_root: bool,
+        clip_and_scroll: ClipAndScrollInfo,
+        output_pipelines: &FastHashSet<PipelineId>,
     ) {
-        if let Some(parent_index) = self.stacking_context_stack.last() {
-            let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
+        // Construct the necessary set of Picture primitives
+        // to draw this stacking context.
+        let current_reference_frame_id = self.current_reference_frame_id();
+
+        // An arbitrary large clip rect. For now, we don't
+        // specify a clip specific to the stacking context.
+        // However, now that they are represented as Picture
+        // primitives, we can apply any kind of clip mask
+        // to them, as for a normal primitive. This is needed
+        // to correctly handle some CSS cases (see #1957).
+        let max_clip = LayerRect::new(
+            LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
+            LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP),
+        );
+
+        // If there is no root picture, create one for the main framebuffer.
+        if self.sc_stack.is_empty() {
+            // Should be no pictures at all if the stack is empty...
+            debug_assert!(self.prim_store.cpu_pictures.is_empty());
+            debug_assert_eq!(transform_style, TransformStyle::Flat);
+
+            // This picture stores primitive runs for items on the
+            // main framebuffer.
+            let pic = PicturePrimitive::new_image(
+                None,
+                false,
+                pipeline_id,
+                current_reference_frame_id,
+                None,
+            );
+
+            // No clip sources needed for the main framebuffer.
+            let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
 
-            if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
-                // the parent stacking context of a stacking context with mix-blend-mode
-                // must be drawn with a transparent background, unless the parent stacking context
-                // is the root of the page
-                let isolation = &mut self.stacking_context_store[parent_index.0].isolation;
-                if *isolation != ContextIsolation::None {
-                    error!(
-                        "Isolation conflict detected on {:?}: {:?}",
-                        parent_index,
-                        *isolation
-                    );
+            // Add root picture primitive. The provided layer rect
+            // is zero, because we don't yet know the size of the
+            // picture. Instead, this is calculated recursively
+            // when we cull primitives.
+            let prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                true,
+                clip_sources,
+                None,
+                PrimitiveContainer::Picture(pic),
+            );
+
+            self.picture_stack.push(prim_index);
+        } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
+            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+            // the stacking context needs to be isolated to blend correctly as per
+            // the CSS spec.
+            // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
+            //           is hacky and depends on how we create a root stacking context
+            //           during flattening.
+            let current_pic_prim_index = self.picture_stack.last().unwrap();
+            let pic_cpu_prim_index = self.prim_store.cpu_metadata[current_pic_prim_index.0].cpu_prim_index;
+            let parent_pic = &mut self.prim_store.cpu_pictures[pic_cpu_prim_index.0];
+
+            match parent_pic.kind {
+                PictureKind::Image { ref mut composite_mode, .. } => {
+                    // If not already isolated for some other reason,
+                    // make this picture as isolated.
+                    if composite_mode.is_none() {
+                        *composite_mode = Some(PictureCompositeMode::Blit);
+                    }
                 }
-                *isolation = ContextIsolation::Full;
+                PictureKind::TextShadow { .. } |
+                PictureKind::BoxShadow { .. } => {
+                    panic!("bug: text/box pictures invalid here");
+                }
             }
         }
 
-        let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
-        let reference_frame_id = self.current_reference_frame_id();
-        self.stacking_context_store.push(StackingContext::new(
+        // Get the transform-style of the parent stacking context,
+        // which determines if we *might* need to draw this on
+        // an intermediate surface for plane splitting purposes.
+        let parent_transform_style = match self.sc_stack.last() {
+            Some(sc) => sc.transform_style,
+            None => TransformStyle::Flat,
+        };
+
+        // If either the parent or this stacking context is preserve-3d
+        // then we are in a 3D context.
+        let is_in_3d_context = composite_ops.count() == 0 &&
+                               (parent_transform_style == TransformStyle::Preserve3D ||
+                                transform_style == TransformStyle::Preserve3D);
+
+        // TODO(gw): For now, we don't handle filters and mix-blend-mode when there
+        //           is a 3D rendering context. We can easily do this in the future
+        //           by creating a chain of pictures for the effects, and ensuring
+        //           that the last composited picture is what's used as the input to
+        //           the plane splitting code.
+        let mut parent_pic_prim_index = if is_in_3d_context {
+            // If we're in a 3D context, we will parent the picture
+            // to the first stacking context we find in the stack that
+            // is transform-style: flat. This follows the spec
+            // by hoisting these items out into the same 3D context
+            // for plane splitting.
+            self.sc_stack
+                .iter()
+                .rev()
+                .find(|sc| sc.transform_style == TransformStyle::Flat)
+                .map(|sc| sc.pic_prim_index)
+                .unwrap()
+        } else {
+            *self.picture_stack.last().unwrap()
+        };
+
+        // For each filter, create a new image with that composite mode.
+        for filter in &composite_ops.filters {
+            let src_prim = PicturePrimitive::new_image(
+                Some(PictureCompositeMode::Filter(*filter)),
+                false,
+                pipeline_id,
+                current_reference_frame_id,
+                None,
+            );
+            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            let src_prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                is_backface_visible,
+                src_clip_sources,
+                None,
+                PrimitiveContainer::Picture(src_prim),
+            );
+
+            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+            parent_pic_prim_index = src_prim_index;
+            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+            pic.add_primitive(
+                src_prim_index,
+                clip_and_scroll,
+            );
+
+            self.picture_stack.push(src_prim_index);
+        }
+
+        // Same for mix-blend-mode.
+        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
+            let src_prim = PicturePrimitive::new_image(
+                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+                false,
+                pipeline_id,
+                current_reference_frame_id,
+                None,
+            );
+            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            let src_prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                is_backface_visible,
+                src_clip_sources,
+                None,
+                PrimitiveContainer::Picture(src_prim),
+            );
+
+            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+            parent_pic_prim_index = src_prim_index;
+            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+            pic.add_primitive(
+                src_prim_index,
+                clip_and_scroll,
+            );
+
+            self.picture_stack.push(src_prim_index);
+        }
+
+        // By default, this picture will be collapsed into
+        // the owning target.
+        let mut composite_mode = None;
+        let mut frame_output_pipeline_id = None;
+
+        // If this stacking context if the root of a pipeline, and the caller
+        // has requested it as an output frame, create a render task to isolate it.
+        if is_pipeline_root && output_pipelines.contains(&pipeline_id) {
+            composite_mode = Some(PictureCompositeMode::Blit);
+            frame_output_pipeline_id = Some(pipeline_id);
+        }
+
+        if is_in_3d_context {
+            // TODO(gw): For now, as soon as this picture is in
+            //           a 3D context, we draw it to an intermediate
+            //           surface and apply plane splitting. However,
+            //           there is a large optimization opportunity here.
+            //           During culling, we can check if there is actually
+            //           perspective present, and skip the plane splitting
+            //           completely when that is not the case.
+            composite_mode = Some(PictureCompositeMode::Blit);
+        }
+
+        // Add picture for this actual stacking context contents to render into.
+        let sc_prim = PicturePrimitive::new_image(
+            composite_mode,
+            is_in_3d_context,
             pipeline_id,
-            *reference_frame_offset,
-            !self.has_root_stacking_context,
-            is_pipeline_root,
-            reference_frame_id,
-            transform_style,
+            current_reference_frame_id,
+            frame_output_pipeline_id,
+        );
+
+        let sc_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+        let sc_prim_index = self.prim_store.add_primitive(
+            &LayerRect::zero(),
+            &max_clip,
+            is_backface_visible,
+            sc_clip_sources,
+            None,
+            PrimitiveContainer::Picture(sc_prim),
+        );
+
+        let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+        let sc_pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+        sc_pic.add_primitive(
+            sc_prim_index,
+            clip_and_scroll,
+        );
+
+        // Add this as the top-most picture for primitives to be added to.
+        self.picture_stack.push(sc_prim_index);
+
+        // TODO(gw): This is super conservative. We can expand on this a lot
+        //           once all the picture code is in place and landed.
+        let allow_subpixel_aa = composite_ops.count() == 0 &&
+                                transform_style == TransformStyle::Flat;
+
+        // Push the SC onto the stack, so we know how to handle things in
+        // pop_stacking_context.
+        let sc = StackingContext {
             composite_ops,
             is_backface_visible,
-        ));
-        self.has_root_stacking_context = true;
-        self.cmds
-            .push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
-        self.stacking_context_stack.push(stacking_context_index);
+            pipeline_id,
+            allow_subpixel_aa,
+            transform_style,
+            // TODO(gw): This is not right when filters are present (but we
+            //           don't handle that right now, per comment above).
+            pic_prim_index: sc_prim_index,
+        };
+
+        self.sc_stack.push(sc);
     }
 
     pub fn pop_stacking_context(&mut self) {
-        self.cmds.push(PrimitiveRunCmd::PopStackingContext);
-        self.stacking_context_stack.pop();
+        let sc = self.sc_stack.pop().unwrap();
+
+        // Remove the picture for this stacking contents.
+        self.picture_stack.pop().expect("bug");
+
+        // Remove the picture for any filter/mix-blend-mode effects.
+        for _ in 0 .. sc.composite_ops.count() {
+            self.picture_stack.pop().expect("bug: mismatched picture stack");
+        }
+
+        // By the time the stacking context stack is empty, we should
+        // also have cleared the picture stack.
+        if self.sc_stack.is_empty() {
+            self.picture_stack.pop().expect("bug: picture stack invalid");
+            debug_assert!(self.picture_stack.is_empty());
+        }
+
         assert!(
             self.shadow_prim_stack.is_empty(),
             "Found unpopped text shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
         rect: &LayerRect,
-        transform: &LayerToScrollTransform,
+        source_transform: Option<PropertyBinding<LayoutTransform>>,
+        source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         root_for_pipeline: bool,
         clip_scroll_tree: &mut ClipScrollTree,
     ) -> ClipId {
         let new_id = clip_scroll_tree.add_reference_frame(
             rect,
-            transform,
+            source_transform,
+            source_perspective,
             origin_in_parent_reference_frame,
             pipeline_id,
             parent_id,
             root_for_pipeline,
         );
         self.reference_frame_stack.push(new_id);
         new_id
     }
@@ -412,17 +607,17 @@ impl FrameBuilder {
         let viewport_clip = LayerRect::new(
             LayerPoint::new(-viewport_offset.x, -viewport_offset.y),
             LayerSize::new(clip_size.width, clip_size.height),
         );
 
         let root_id = clip_scroll_tree.root_reference_frame_id();
         if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&root_id) {
             if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
-                info.transform = LayerToScrollTransform::create_translation(
+                info.resolved_transform = LayerToScrollTransform::create_translation(
                     viewport_offset.x,
                     viewport_offset.y,
                     0.0,
                 );
             }
             root_node.local_clip_rect = viewport_clip;
         }
 
@@ -435,22 +630,22 @@ impl FrameBuilder {
     pub fn push_root(
         &mut self,
         pipeline_id: PipelineId,
         viewport_size: &LayerSize,
         content_size: &LayerSize,
         clip_scroll_tree: &mut ClipScrollTree,
     ) -> ClipId {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
-        let identity = &LayerToScrollTransform::identity();
         self.push_reference_frame(
             None,
             pipeline_id,
             &viewport_rect,
-            identity,
+            None,
+            None,
             LayerVector2D::zero(),
             true,
             clip_scroll_tree,
         );
 
         let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
         clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
 
@@ -511,17 +706,18 @@ impl FrameBuilder {
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ClipAndScrollInfo,
         info: &LayerPrimitiveInfo,
     ) {
-        let prim = PicturePrimitive::new_text_shadow(shadow);
+        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+        let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
 
         // Create an empty shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
         // part of this shadow context.
         let prim_index = self.create_primitive(
             info,
             Vec::new(),
@@ -532,27 +728,17 @@ impl FrameBuilder {
         self.shadow_prim_stack.push((prim_index, pending));
     }
 
     pub fn pop_all_shadows(&mut self) {
         assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
 
         // Borrowcheck dance
         let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
-        for (prim_index, pending_primitives) in shadows.drain(..) {
-            {
-                // By now, the local rect of the text shadow has been calculated. It
-                // is calculated as the items in the shadow are added. It's now
-                // safe to offset the local rect by the offset of the shadow, which
-                // is then used when blitting the shadow to the final location.
-                let metadata = &mut self.prim_store.cpu_metadata[prim_index.0];
-                let prim = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
-                metadata.local_rect = prim.build();
-            }
-
+        for (_, pending_primitives) in shadows.drain(..) {
             // Push any fast-path shadows now
             for (prim_index, clip_and_scroll) in pending_primitives {
                 self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
             }
         }
 
         let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
         for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
@@ -616,17 +802,17 @@ impl FrameBuilder {
             orientation,
         };
 
         let mut fast_shadow_prims = Vec::new();
         for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
             match picture.kind {
-                PictureKind::TextShadow { offset, color, blur_radius } if blur_radius == 0.0 => {
+                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
                     fast_shadow_prims.push((idx, offset, color));
                 }
                 _ => {}
             }
         }
 
         for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
             let mut line = line.clone();
@@ -662,17 +848,16 @@ impl FrameBuilder {
             let picture =
                 &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
 
             match picture.kind {
                 // Only run real blurs here (fast path zero blurs are handled above).
                 PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
                     picture.add_primitive(
                         prim_index,
-                        &info.rect,
                         clip_and_scroll,
                     );
                 }
                 _ => {}
             }
         }
     }
 
@@ -1048,17 +1233,16 @@ impl FrameBuilder {
         run_offset: LayoutVector2D,
         info: &LayerPrimitiveInfo,
         font: &FontInstance,
         text_color: &ColorF,
         glyph_range: ItemRange<GlyphInstance>,
         glyph_count: usize,
         glyph_options: Option<GlyphOptions>,
     ) {
-        let original_rect = info.rect;
         // Trivial early out checks
         if font.size.0 <= 0 {
             return;
         }
 
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
@@ -1077,23 +1261,22 @@ impl FrameBuilder {
             .limit_by(font.render_mode);
         if let Some(options) = glyph_options {
             render_mode = render_mode.limit_by(options.render_mode);
         }
 
         // There are some conditions under which we can't use
         // subpixel text rendering, even if enabled.
         if render_mode == FontRenderMode::Subpixel {
-            // text on a stacking context that has filters
+            // text on a picture that has filters
             // (e.g. opacity) can't use sub-pixel.
             // TODO(gw): It's possible we can relax this in
             //           the future, if we modify the way
             //           we handle subpixel blending.
-            if let Some(sc_index) = self.stacking_context_stack.last() {
-                let stacking_context = &self.stacking_context_store[sc_index.0];
+            if let Some(ref stacking_context) = self.sc_stack.last() {
                 if !stacking_context.allow_subpixel_aa {
                     render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
         let prim_font = FontInstance::new(
             font.font_key,
@@ -1123,17 +1306,17 @@ impl FrameBuilder {
         // *before* the visual text primitive in order to get the correct paint
         // order. Store them in a Vec first to work around borrowck issues.
         // TODO(gw): Refactor to avoid having to store them in a Vec first.
         let mut fast_shadow_prims = Vec::new();
         for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
             let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
             let picture_prim = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
             match picture_prim.kind {
-                PictureKind::TextShadow { offset, color, blur_radius } if blur_radius == 0.0 => {
+                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
                     let mut text_prim = prim.clone();
                     text_prim.font.color = color.into();
                     text_prim.offset += offset;
                     fast_shadow_prims.push((idx, text_prim));
                 }
                 _ => {}
             }
         }
@@ -1181,17 +1364,16 @@ impl FrameBuilder {
             let picture =
                 &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
 
             match picture.kind {
                 // Only run real blurs here (fast path zero blurs are handled above).
                 PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
                     picture.add_primitive(
                         prim_index,
-                        &original_rect,
                         clip_and_scroll,
                     );
                 }
                 _ => {}
             }
         }
     }
 
@@ -1258,30 +1440,16 @@ impl FrameBuilder {
         self.add_primitive(
             clip_and_scroll,
             info,
             Vec::new(),
             PrimitiveContainer::YuvImage(prim_cpu),
         );
     }
 
-    fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
-        self.stacking_context_stack.push(stacking_context_index);
-
-        // Reset bounding rect to zero. We will calculate it as we collect primitives
-        // from various scroll layers. In handle_pop_stacking_context , we use this to
-        // calculate the device bounding rect. In the future, we could cache this during
-        // the initial adding of items for the common case (where there is only a single
-        // scroll layer for items in a stacking context).
-        let stacking_context =
-            &mut self.stacking_context_store[stacking_context_index.0];
-        stacking_context.screen_bounds = DeviceIntRect::zero();
-        stacking_context.isolated_items_bounds = LayerRect::zero();
-    }
-
     pub fn hit_test(
         &self,
         clip_scroll_tree: &ClipScrollTree,
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags
     ) -> HitTestResult {
         let point = if flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
@@ -1341,191 +1509,80 @@ impl FrameBuilder {
                 }
             }
         }
 
         result.items.dedup();
         return result;
     }
 
-
-    fn handle_primitive_run(
-        &mut self,
-        run: &PrimitiveRun,
-        render_tasks: &mut RenderTaskTree,
-        gpu_cache: &mut GpuCache,
-        resource_cache: &mut ResourceCache,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
-        clip_scroll_tree: &ClipScrollTree,
-        device_pixel_ratio: f32,
-        profile_counters: &mut FrameProfileCounters,
-    ) {
-        let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-        let scroll_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
-        let clip_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.clip_node_id()];
-
-        if !clip_node.is_visible() {
-            debug!("{:?} of clipped out {:?}", run.base_prim_index, stacking_context_index);
-            return;
-        }
-
-        let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
-        let pipeline_id = {
-            if !stacking_context.can_contribute_to_scene() {
-                return;
-            }
-
-            // At least one primitive in this stacking context is visible, so the stacking
-            // context is visible.
-            stacking_context.is_visible = true;
-            stacking_context.pipeline_id
-        };
-
-        debug!(
-            "\t{:?} of {:?}",
-            run.base_prim_index,
-            stacking_context_index,
-        );
-
-        let display_list = &pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list;
-
-        if !stacking_context.is_backface_visible && scroll_node.world_content_transform.is_backface_visible() {
-            return;
-        }
-
-        let prim_context = PrimitiveContext::new(
-            device_pixel_ratio,
-            display_list,
-            clip_node,
-            scroll_node,
-        );
-
-        let result = self.prim_store.prepare_prim_run(
-            run,
-            &prim_context,
-            gpu_cache,
-            resource_cache,
-            render_tasks,
-            &mut self.clip_store,
-        );
-
-        if result.visible_primitives > 0 {
-            stacking_context.screen_bounds = stacking_context
-                .screen_bounds
-                .union(&result.device_rect);
-            stacking_context.isolated_items_bounds = stacking_context
-                .isolated_items_bounds
-                .union(&result.local_rect);
-            stacking_context.has_any_primitive = true;
-
-            profile_counters.visible_primitives.add(result.visible_primitives);
-        }
-    }
-
-    fn handle_pop_stacking_context(
-        &mut self,
-        screen_rect: &DeviceIntRect,
-        clip_scroll_tree: &ClipScrollTree) {
-        let stacking_context_index = self.stacking_context_stack.pop().unwrap();
-
-        let (bounding_rect, is_visible, is_preserve_3d, reference_id, reference_bounds) = {
-            let stacking_context =
-                &mut self.stacking_context_store[stacking_context_index.0];
-            if !stacking_context.has_any_primitive {
-                stacking_context.isolated_items_bounds = stacking_context.children_sc_bounds;
-            } else if stacking_context.isolation != ContextIsolation::Items {
-                stacking_context.isolated_items_bounds = stacking_context
-                    .isolated_items_bounds
-                    .union(&stacking_context.children_sc_bounds);
-            }
-            stacking_context.screen_bounds = stacking_context
-                .screen_bounds
-                .intersection(screen_rect)
-                .unwrap_or(DeviceIntRect::zero());
-            (
-                stacking_context.screen_bounds.clone(),
-                stacking_context.is_visible,
-                stacking_context.isolation == ContextIsolation::Items,
-                stacking_context.reference_frame_id,
-                stacking_context
-                    .isolated_items_bounds
-                    .translate(&stacking_context.reference_frame_offset),
-            )
-        };
-
-        if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
-            let parent = &mut self.stacking_context_store[parent_index.0];
-            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
-            let child_bounds = reference_bounds.translate(&-parent.reference_frame_offset);
-            let frame_node = clip_scroll_tree
-                .nodes
-                .get(&reference_id)
-                .unwrap();
-            let local_transform = match frame_node.node_type {
-                NodeType::ReferenceFrame(ref info) => info.transform,
-                _ => LayerToScrollTransform::identity(),
-            };
-            let transformed_bounds = local_transform
-                .with_destination::<LayerPixel>()
-                .transform_rect(&child_bounds);
-            parent.children_sc_bounds = parent.children_sc_bounds.union(&transformed_bounds);
-            // add children local bounds only for non-item-isolated contexts
-            if !is_preserve_3d && parent.reference_frame_id == reference_id {
-                parent.isolated_items_bounds = parent.isolated_items_bounds.union(&child_bounds);
-            }
-            // Per-primitive stacking context visibility checks do not take into account
-            // visibility of child stacking contexts, so do that now.
-            parent.is_visible = parent.is_visible || is_visible;
-        }
-    }
-
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
-        screen_rect: &DeviceIntRect,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_ratio: f32,
+        scene_properties: &SceneProperties,
     ) {
         profile_scope!("cull");
 
-        debug!("processing commands...");
-        let commands = mem::replace(&mut self.cmds, Vec::new());
-        for cmd in &commands {
-            match *cmd {
-                PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
-                    self.handle_push_stacking_context(stacking_context_index)
-                }
-                PrimitiveRunCmd::PrimitiveRun(ref run) => {
-                    self.handle_primitive_run(
-                        run,
-                        render_tasks,
-                        gpu_cache,
-                        resource_cache,
-                        pipelines,
-                        clip_scroll_tree,
-                        device_pixel_ratio,
-                        profile_counters,
-                    );
-                }
-                PrimitiveRunCmd::PopStackingContext => {
-                    self.handle_pop_stacking_context(screen_rect, clip_scroll_tree);
-                }
-            }
-        }
+        // The root picture is always the first one added.
+        let prim_run_cmds = mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new());
+        let root_clip_scroll_node = &clip_scroll_tree.nodes[&clip_scroll_tree.root_reference_frame_id()];
+
+        let display_list = &pipelines
+            .get(&root_clip_scroll_node.pipeline_id)
+            .expect("No display list?")
+            .display_list;
+
+        let root_prim_context = PrimitiveContext::new(
+            device_pixel_ratio,
+            display_list,
+            root_clip_scroll_node,
+            root_clip_scroll_node,
+        );
+
+        let mut child_tasks = Vec::new();
+
+        self.prim_store.reset_prim_visibility();
 
-        mem::replace(&mut self.cmds, commands);
+        self.prim_store.prepare_prim_runs(
+            &prim_run_cmds,
+            root_clip_scroll_node.pipeline_id,
+            gpu_cache,
+            resource_cache,
+            render_tasks,
+            &mut self.clip_store,
+            clip_scroll_tree,
+            pipelines,
+            &root_prim_context,
+            true,
+            &mut child_tasks,
+            profile_counters,
+            None,
+            scene_properties,
+        );
+
+        let pic = &mut self.prim_store.cpu_pictures[0];
+        pic.runs = prim_run_cmds;
+
+        let root_render_task = RenderTask::new_alpha_batch(
+            DeviceIntPoint::zero(),
+            RenderTaskLocation::Fixed,
+            PrimitiveIndex(0),
+            None,
+            child_tasks,
+        );
+
+        pic.render_task_id = Some(render_tasks.add(root_render_task));
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
             let scroll_frame = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
@@ -1547,346 +1604,28 @@ impl FrameBuilder {
 
             metadata.local_rect.origin.x = frame_rect.origin.x + frame_rect.size.width -
                 (metadata.local_rect.size.width + SCROLLBAR_PADDING);
             metadata.local_rect.origin.y = util::lerp(min_y, max_y, amount_scrolled);
             metadata.local_clip_rect = metadata.local_rect;
         }
     }
 
-    fn build_render_task(
-        &mut self,
-        clip_scroll_tree: &ClipScrollTree,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
-        output_pipelines: &FastHashSet<PipelineId>,
-        device_pixel_ratio: f32,
-    ) -> RenderTaskId {
-        profile_scope!("build_render_task");
-
-        let mut next_z = 0;
-        let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
-        let mut current_task =
-            RenderTask::new_alpha_batch(DeviceIntPoint::zero(), RenderTaskLocation::Fixed, None);
-        // A stack of the alpha batcher tasks. We create them on the way down,
-        // and then actually populate with items and dependencies on the way up.
-        let mut alpha_task_stack = Vec::new();
-        // A map of "preserve-3d" contexts. We are baking these into render targets
-        // and only compositing once we are out of "preserve-3d" hierarchy.
-        // The stacking contexts that fall into this category are
-        //  - ones with `ContextIsolation::Items`, for their actual items to be backed
-        //  - immediate children of `ContextIsolation::Items`
-        let mut preserve_3d_map_stack: Vec<FastHashMap<StackingContextIndex, RenderTaskId>> =
-            Vec::new();
-        // The plane splitter stack, using a simple BSP tree.
-        let mut splitter_stack = Vec::new();
-
-        debug!("build_render_task()");
-
-        for cmd in &self.cmds {
-            match *cmd {
-                PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
-                    let parent_isolation = sc_stack
-                        .last()
-                        .map(|index| self.stacking_context_store[index.0].isolation);
-                    let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-                    sc_stack.push(stacking_context_index);
-
-                    if !stacking_context.is_visible {
-                        continue;
-                    }
-
-                    debug!(
-                        "\tpush {:?} {:?}",
-                        stacking_context_index,
-                        stacking_context.isolation
-                    );
-
-                    let stacking_context_rect = &stacking_context.screen_bounds;
-                    let composite_count = stacking_context.composite_ops.count();
-
-                    // If this stacking context if the root of a pipeline, and the caller
-                    // has requested it as an output frame, create a render task to isolate it.
-                    if stacking_context.is_pipeline_root &&
-                        output_pipelines.contains(&stacking_context.pipeline_id)
-                    {
-                        alpha_task_stack.push(current_task);
-                        current_task = RenderTask::new_dynamic_alpha_batch(
-                            stacking_context_rect,
-                            Some(stacking_context.pipeline_id),
-                        );
-                    }
-
-                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0
-                    {
-                        alpha_task_stack.push(current_task);
-                        current_task =
-                            RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
-                    }
-
-                    if parent_isolation == Some(ContextIsolation::Items) ||
-                        stacking_context.isolation == ContextIsolation::Items
-                    {
-                        if parent_isolation != Some(ContextIsolation::Items) {
-                            splitter_stack.push(BspSplitter::new());
-                            preserve_3d_map_stack.push(FastHashMap::default());
-                        }
-                        alpha_task_stack.push(current_task);
-                        current_task =
-                            RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
-                        //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
-                        // that have no child items (only other stacking contexts). However, we don't know if
-                        // there are any items at this time (in `PushStackingContext`).
-                        //Note: the reason we add the polygon for splitting during `Push*` as opposed to `Pop*`
-                        // is because we need to preserve the order of drawing for planes that match together.
-                        let frame_node = clip_scroll_tree
-                            .nodes
-                            .get(&stacking_context.reference_frame_id)
-                            .unwrap();
-                        let sc_polygon =
-                            make_polygon(stacking_context, frame_node, stacking_context_index.0);
-                        debug!(
-                            "\tsplitter[{}]: add {:?} -> {:?} with bounds {:?}",
-                            splitter_stack.len(),
-                            stacking_context_index,
-                            sc_polygon,
-                            stacking_context.isolated_items_bounds
-                        );
-                        splitter_stack.last_mut().unwrap().add(sc_polygon);
-                    }
-
-                    for _ in 0 .. composite_count {
-                        alpha_task_stack.push(current_task);
-                        current_task =
-                            RenderTask::new_dynamic_alpha_batch(stacking_context_rect, None);
-                    }
-                }
-                PrimitiveRunCmd::PopStackingContext => {
-                    let stacking_context_index = sc_stack.pop().unwrap();
-                    let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-                    let composite_count = stacking_context.composite_ops.count();
-
-                    if !stacking_context.is_visible {
-                        continue;
-                    }
-
-                    debug!("\tpop {:?}", stacking_context_index);
-                    let parent_isolation = sc_stack
-                        .last()
-                        .map(|index| self.stacking_context_store[index.0].isolation);
-
-                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0
-                    {
-                        let mut prev_task = alpha_task_stack.pop().unwrap();
-                        let screen_origin = current_task.as_alpha_batch().screen_origin;
-                        let current_task_size = current_task.get_dynamic_size();
-                        let current_task_id = render_tasks.add(current_task);
-                        let item = AlphaRenderItem::HardwareComposite(
-                            stacking_context_index,
-                            current_task_id,
-                            HardwareCompositeOp::PremultipliedAlpha,
-                            screen_origin,
-                            next_z,
-                            current_task_size,
-                        );
-                        next_z += 1;
-                        prev_task.as_alpha_batch_mut().items.push(item);
-                        prev_task.children.push(current_task_id);
-                        current_task = prev_task;
-                    }
-
-                    for filter in &stacking_context.composite_ops.filters {
-                        let mut prev_task = alpha_task_stack.pop().unwrap();
-                        let screen_origin = current_task.as_alpha_batch().screen_origin;
-                        let current_task_id = render_tasks.add(current_task);
-                        match *filter {
-                            FilterOp::Blur(blur_radius) => {
-                                let blur_radius = device_length(blur_radius, device_pixel_ratio);
-                                let blur_std_deviation = blur_radius.0 as f32;
-                                let inflate_size = blur_std_deviation * BLUR_SAMPLE_SCALE;
-                                render_tasks.get_mut(current_task_id)
-                                            .inflate(inflate_size as i32);
-                                let blur_render_task = RenderTask::new_blur(
-                                    blur_std_deviation,
-                                    current_task_id,
-                                    render_tasks,
-                                    RenderTargetKind::Color,
-                                    &[],
-                                    ClearMode::Transparent,
-                                    PremultipliedColorF::TRANSPARENT,
-                                );
-                                let blur_render_task_id = render_tasks.add(blur_render_task);
-                                let item = AlphaRenderItem::HardwareComposite(
-                                    stacking_context_index,
-                                    blur_render_task_id,
-                                    HardwareCompositeOp::PremultipliedAlpha,
-                                    DeviceIntPoint::new(
-                                        screen_origin.x - inflate_size as i32,
-                                        screen_origin.y - inflate_size as i32,
-                                    ),
-                                    next_z,
-                                    render_tasks.get(current_task_id).get_dynamic_size(),
-                                );
-                                prev_task.as_alpha_batch_mut().items.push(item);
-                                prev_task.children.push(blur_render_task_id);
-                                current_task = prev_task;
-                            }
-                            _ => {
-                                let item = AlphaRenderItem::Blend(
-                                    stacking_context_index,
-                                    current_task_id,
-                                    *filter,
-                                    next_z,
-                                );
-                                prev_task.as_alpha_batch_mut().items.push(item);
-                                prev_task.children.push(current_task_id);
-                                current_task = prev_task;
-                            }
-                        }
-                        next_z += 1;
-                    }
-
-                    if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
-                        let backdrop_task =
-                            RenderTask::new_readback(stacking_context.screen_bounds);
-                        let source_task_id = render_tasks.add(current_task);
-                        let backdrop_task_id = render_tasks.add(backdrop_task);
-
-                        let mut prev_task = alpha_task_stack.pop().unwrap();
-                        let item = AlphaRenderItem::Composite(
-                            stacking_context_index,
-                            source_task_id,
-                            backdrop_task_id,
-                            mix_blend_mode,
-                            next_z,
-                        );
-                        next_z += 1;
-                        prev_task.as_alpha_batch_mut().items.push(item);
-                        prev_task.children.push(source_task_id);
-                        prev_task.children.push(backdrop_task_id);
-                        current_task = prev_task;
-                    }
-
-                    if parent_isolation == Some(ContextIsolation::Items) ||
-                        stacking_context.isolation == ContextIsolation::Items
-                    {
-                        //Note: we don't register the dependent tasks here. It's only done
-                        // when we are out of the `preserve-3d` branch (see the code below),
-                        // since this is only where the parent task is known.
-                        let current_task_id = render_tasks.add(current_task);
-                        preserve_3d_map_stack
-                            .last_mut()
-                            .unwrap()
-                            .insert(stacking_context_index, current_task_id);
-                        current_task = alpha_task_stack.pop().unwrap();
-                    }
-
-                    if parent_isolation != Some(ContextIsolation::Items) &&
-                        stacking_context.isolation == ContextIsolation::Items
-                    {
-                        debug!("\tsplitter[{}]: flush", splitter_stack.len());
-                        let mut splitter = splitter_stack.pop().unwrap();
-                        // Flush the accumulated plane splits onto the task tree.
-                        // Notice how this is done before splitting in order to avoid duplicate tasks.
-                        current_task
-                            .children
-                            .extend(preserve_3d_map_stack.last().unwrap().values().cloned());
-                        // 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 sc_index = StackingContextIndex(poly.anchor);
-                            let task_id = preserve_3d_map_stack.last().unwrap()[&sc_index];
-                            debug!("\t\tproduce {:?} -> {:?} for {:?}", sc_index, poly, task_id);
-                            let pp = &poly.points;
-                            let gpu_blocks = [
-                                [pp[0].x as f32, pp[0].y as f32, pp[0].z as f32, pp[1].x as f32].into(),
-                                [pp[1].y as f32, pp[1].z as f32, pp[2].x as f32, pp[2].y as f32].into(),
-                                [pp[2].z as f32, pp[3].x as f32, pp[3].y as f32, pp[3].z as f32].into(),
-                            ];
-                            let handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
-                            let item =
-                                AlphaRenderItem::SplitComposite(sc_index, task_id, handle, next_z);
-                            current_task.as_alpha_batch_mut().items.push(item);
-                        }
-                        preserve_3d_map_stack.pop();
-                        next_z += 1;
-                    }
-
-                    if stacking_context.is_pipeline_root &&
-                        output_pipelines.contains(&stacking_context.pipeline_id)
-                    {
-                        let mut prev_task = alpha_task_stack.pop().unwrap();
-                        let screen_origin = current_task.as_alpha_batch().screen_origin;
-                        let current_task_size = current_task.get_dynamic_size();
-                        let current_task_id = render_tasks.add(current_task);
-                        let item = AlphaRenderItem::HardwareComposite(
-                            stacking_context_index,
-                            current_task_id,
-                            HardwareCompositeOp::PremultipliedAlpha,
-                            screen_origin,
-                            next_z,
-                            current_task_size,
-                        );
-                        next_z += 1;
-                        prev_task.as_alpha_batch_mut().items.push(item);
-                        prev_task.children.push(current_task_id);
-                        current_task = prev_task;
-                    }
-                }
-                PrimitiveRunCmd::PrimitiveRun(ref run) => {
-                    let stacking_context_index = *sc_stack.last().unwrap();
-                    if !self.stacking_context_store[stacking_context_index.0].is_visible {
-                        continue;
-                    }
-
-                    debug!("\trun of {} items", run.count);
-
-                    let clip_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.clip_node_id()];
-                    if !clip_node.is_visible() {
-                        continue;
-                    }
-                    let scroll_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
-
-                    for i in 0 .. run.count {
-                        let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
-                        if self.prim_store.cpu_metadata[prim_index.0].screen_rect.is_some() {
-                            self.prim_store
-                                .add_render_tasks_for_prim(prim_index, &mut current_task);
-                            let item =
-                                AlphaRenderItem::Primitive(
-                                    clip_node.node_data_index,
-                                    scroll_node.node_data_index,
-                                    prim_index,
-                                    next_z
-                                );
-                            current_task.as_alpha_batch_mut().items.push(item);
-                            next_z += 1;
-                        }
-                    }
-                }
-            }
-        }
-
-        debug_assert!(alpha_task_stack.is_empty());
-        debug_assert!(preserve_3d_map_stack.is_empty());
-        render_tasks.add(current_task)
-    }
-
     pub fn build(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         frame_id: FrameId,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, ScenePipeline>,
         device_pixel_ratio: f32,
         pan: LayerPoint,
-        output_pipelines: &FastHashSet<PipelineId>,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
+        scene_properties: &SceneProperties,
     ) -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
@@ -1906,40 +1645,38 @@ impl FrameBuilder {
         clip_scroll_tree.update_tree(
             &screen_rect,
             device_pixel_ratio,
             &mut self.clip_store,
             resource_cache,
             gpu_cache,
             pan,
             &mut node_data,
+            scene_properties,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         let mut render_tasks = RenderTaskTree::new();
 
         self.build_layer_screen_rects_and_cull_layers(
-            &screen_rect,
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut profile_counters,
             device_pixel_ratio,
+            scene_properties,
         );
 
-        let main_render_task_id = self.build_render_task(
-            clip_scroll_tree,
-            gpu_cache,
-            &mut render_tasks,
-            output_pipelines,
-            device_pixel_ratio,
-        );
+        let main_render_task_id = self.prim_store
+                                      .cpu_pictures[0]
+                                      .render_task_id
+                                      .expect("bug: no root render task!");
 
         let mut required_pass_count = 0;
         render_tasks.max_depth(main_render_task_id, 0, &mut required_pass_count);
 
         resource_cache.block_until_all_resources_added(gpu_cache, texture_cache_profile);
 
         let mut deferred_resolves = vec![];
 
@@ -1951,20 +1688,20 @@ impl FrameBuilder {
             passes.push(RenderPass::new(index == required_pass_count - 1));
         }
 
         render_tasks.assign_to_passes(main_render_task_id, passes.len() - 1, &mut passes);
 
         for pass in &mut passes {
             let ctx = RenderTargetContext {
                 device_pixel_ratio,
-                stacking_context_store: &self.stacking_context_store,
                 prim_store: &self.prim_store,
                 resource_cache,
                 node_data: &node_data,
+                clip_scroll_tree,
             };
 
             pass.build(
                 &ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -4,17 +4,16 @@
 
 use api::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch};
 use api::{ExternalImageData, ExternalImageId};
 use api::{ImageFormat, PipelineId};
 use api::DebugCommand;
 use device::TextureFilter;
 use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
-use renderer::BlendMode;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 
@@ -182,29 +181,13 @@ pub enum ResultMsg {
         BackendProfileCounters,
     ),
     UpdateResources {
         updates: TextureUpdateList,
         cancel_rendering: bool,
     },
 }
 
-#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
-pub struct StackingContextIndex(pub usize);
-
 #[derive(Clone, Copy, Debug)]
 pub struct UvRect {
     pub uv0: DevicePoint,
     pub uv1: DevicePoint,
 }
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum HardwareCompositeOp {
-    PremultipliedAlpha,
-}
-
-impl HardwareCompositeOp {
-    pub fn to_blend_mode(&self) -> BlendMode {
-        match *self {
-            HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
-        }
-    }
-}
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,214 +1,388 @@
 /* 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::{BorderRadiusKind, ColorF, ClipAndScrollInfo};
-use api::{device_length, DeviceIntSize};
+use api::{BorderRadiusKind, ColorF, ClipAndScrollInfo, FilterOp, MixBlendMode};
+use api::{device_length, DeviceIntRect, DeviceIntSize, PipelineId};
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerSize, LayerVector2D, Shadow};
+use api::{ClipId, PremultipliedColorF};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use frame_builder::PrimitiveContext;
 use gpu_cache::GpuDataRequest;
-use prim_store::{PrimitiveIndex, PrimitiveRun};
+use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskTree};
+use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  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
    picture into its parent.
  * A configuration describing how to draw the primitives on
    this picture (e.g. in screen space or local space).
  */
 
+/// 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),
+    /// Apply a CSS filter.
+    Filter(FilterOp),
+    /// Draw to intermediate surface, copy straight across. This
+    /// is used for CSS isolation, and plane splitting.
+    Blit,
+}
+
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
         offset: LayerVector2D,
         color: ColorF,
         blur_radius: f32,
+        content_rect: LayerRect,
     },
     BoxShadow {
         blur_radius: f32,
         color: ColorF,
         blur_regions: Vec<LayerRect>,
         clip_mode: BoxShadowClipMode,
         radii_kind: BorderRadiusKind,
+        content_rect: LayerRect,
+    },
+    Image {
+        // If a mix-blend-mode, contains the render task for
+        // the readback of the framebuffer that we use to sample
+        // from in the mix-blend-mode shader.
+        readback_render_task_id: Option<RenderTaskId>,
+        /// How this picture should be composited.
+        /// If None, don't composite - just draw directly on parent surface.
+        composite_mode: Option<PictureCompositeMode>,
+        // If true, this picture is part of a 3D context.
+        is_in_3d_context: bool,
+        // If requested as a frame output (for rendering
+        // pages to a texture), this is the pipeline this
+        // picture is the root of.
+        frame_output_pipeline_id: Option<PipelineId>,
+        // The original reference frame ID for this picture.
+        // It is only different if this is part of a 3D
+        // rendering context.
+        reference_frame_id: ClipId,
+        real_local_rect: LayerRect,
     },
 }
 
 #[derive(Debug)]
 pub struct PicturePrimitive {
-    pub prim_runs: Vec<PrimitiveRun>,
+    // If this picture is drawn to an intermediate surface,
+    // the associated render task.
     pub render_task_id: Option<RenderTaskId>,
+
+    // Details specific to this type of picture.
     pub kind: PictureKind,
-    pub content_rect: LayerRect,
+
+    // List of primitive runs that make up this picture.
+    pub runs: Vec<PrimitiveRun>,
+
+    // The pipeline that the primitives on this picture belong to.
+    pub pipeline_id: PipelineId,
+
+    // If true, apply visibility culling to primitives on this
+    // picture. For text shadows and box shadows, we want to
+    // unconditionally draw them.
+    pub cull_children: bool,
 
     // TODO(gw): Add a mode that specifies if this
     //           picture should be rasterized in
     //           screen-space or local-space.
 }
 
 impl PicturePrimitive {
-    pub fn new_text_shadow(shadow: Shadow) -> Self {
+    pub fn new_text_shadow(shadow: Shadow, pipeline_id: PipelineId) -> Self {
         PicturePrimitive {
-            prim_runs: Vec::new(),
+            runs: Vec::new(),
             render_task_id: None,
-            content_rect: LayerRect::zero(),
             kind: PictureKind::TextShadow {
                 offset: shadow.offset,
                 color: shadow.color,
                 blur_radius: shadow.blur_radius,
+                content_rect: LayerRect::zero(),
             },
+            pipeline_id,
+            cull_children: false,
+        }
+    }
+
+    pub fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
+        match self.kind {
+            PictureKind::Image { ref mut composite_mode, .. } => {
+                match composite_mode {
+                    &mut Some(PictureCompositeMode::Filter(ref mut filter)) => {
+                        match filter {
+                            &mut FilterOp::Opacity(ref binding, ref mut value) => {
+                                *value = properties.resolve_float(binding, *value);
+                            }
+                            _ => {}
+                        }
+
+                        filter.is_visible()
+                    }
+                    _ => true,
+                }
+            }
+            _ => true
         }
     }
 
     pub fn new_box_shadow(
         blur_radius: f32,
         color: ColorF,
         blur_regions: Vec<LayerRect>,
         clip_mode: BoxShadowClipMode,
         radii_kind: BorderRadiusKind,
+        pipeline_id: PipelineId,
     ) -> Self {
         PicturePrimitive {
-            prim_runs: Vec::new(),
+            runs: Vec::new(),
             render_task_id: None,
-            content_rect: LayerRect::zero(),
             kind: PictureKind::BoxShadow {
                 blur_radius,
                 color,
                 blur_regions,
                 clip_mode,
                 radii_kind,
+                content_rect: LayerRect::zero(),
             },
+            pipeline_id,
+            cull_children: false,
+        }
+    }
+
+    pub fn new_image(
+        composite_mode: Option<PictureCompositeMode>,
+        is_in_3d_context: bool,
+        pipeline_id: PipelineId,
+        reference_frame_id: ClipId,
+        frame_output_pipeline_id: Option<PipelineId>,
+    ) -> PicturePrimitive {
+        PicturePrimitive {
+            runs: Vec::new(),
+            render_task_id: None,
+            kind: PictureKind::Image {
+                readback_render_task_id: None,
+                composite_mode,
+                is_in_3d_context,
+                frame_output_pipeline_id,
+                reference_frame_id,
+                real_local_rect: LayerRect::zero(),
+            },
+            pipeline_id,
+            cull_children: true,
         }
     }
 
     pub fn add_primitive(
         &mut self,
         prim_index: PrimitiveIndex,
-        local_rect: &LayerRect,
         clip_and_scroll: ClipAndScrollInfo
     ) {
-        // TODO(gw): Accumulating the primitive local rect
-        //           into the content rect here is fine, for now.
-        //           The only way pictures are currently used,
-        //           all the items added to a picture are known
-        //           to be in the same local space. Once we start
-        //           using pictures for other uses, we will need
-        //           to consider the space of a primitive in order
-        //           to build a correct contect rect!
-        self.content_rect = self.content_rect.union(local_rect);
-
-        if let Some(ref mut run) = self.prim_runs.last_mut() {
+        if let Some(ref mut run) = self.runs.last_mut() {
             if run.clip_and_scroll == clip_and_scroll &&
                run.base_prim_index.0 + run.count == prim_index.0 {
                 run.count += 1;
                 return;
             }
         }
 
-        self.prim_runs.push(PrimitiveRun {
+        self.runs.push(PrimitiveRun {
             base_prim_index: prim_index,
             count: 1,
             clip_and_scroll,
         });
     }
 
-    pub fn build(&mut self) -> LayerRect {
+    pub fn update_local_rect(&mut self,
+        prim_local_rect: LayerRect,
+        prim_run_rect: PrimitiveRunLocalRect,
+    ) -> LayerRect {
+        let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space;
+
         match self.kind {
-            PictureKind::TextShadow { offset, blur_radius, .. } => {
+            PictureKind::Image { composite_mode, ref mut real_local_rect, .. } => {
+                *real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
+
+                match composite_mode {
+                    Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                        let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
+                        local_content_rect.inflate(inflate_size, inflate_size)
+                    }
+                    _ => {
+                        local_content_rect
+                    }
+                }
+            }
+            PictureKind::TextShadow { offset, blur_radius, ref mut content_rect, .. } => {
                 let blur_offset = blur_radius * BLUR_SAMPLE_SCALE;
 
-                self.content_rect = self.content_rect.inflate(
+                *content_rect = local_content_rect.inflate(
                     blur_offset,
                     blur_offset,
                 );
 
-                self.content_rect.translate(&offset)
+                content_rect.translate(&offset)
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, radii_kind, .. } => {
+            PictureKind::BoxShadow { blur_radius, clip_mode, radii_kind, ref mut content_rect, .. } => {
                 // We need to inflate the content rect if outset.
                 match clip_mode {
                     BoxShadowClipMode::Outset => {
                         let blur_offset = blur_radius * BLUR_SAMPLE_SCALE;
 
                         // If the radii are uniform, we can render just the top
                         // left corner and mirror it across the primitive. In
                         // this case, shift the content rect to leave room
                         // for the blur to take effect.
                         match radii_kind {
                             BorderRadiusKind::Uniform => {
                                 let origin = LayerPoint::new(
-                                    self.content_rect.origin.x - blur_offset,
-                                    self.content_rect.origin.y - blur_offset,
+                                    local_content_rect.origin.x - blur_offset,
+                                    local_content_rect.origin.y - blur_offset,
                                 );
                                 let size = LayerSize::new(
-                                    self.content_rect.size.width + blur_offset,
-                                    self.content_rect.size.height + blur_offset,
+                                    local_content_rect.size.width + blur_offset,
+                                    local_content_rect.size.height + blur_offset,
                                 );
-                                self.content_rect = LayerRect::new(origin, size);
+                                *content_rect = LayerRect::new(origin, size);
                             }
                             BorderRadiusKind::NonUniform => {
                                 // For a non-uniform radii, we need to expand
                                 // the content rect on all sides for the blur.
-                                self.content_rect = self.content_rect.inflate(
+                                *content_rect = local_content_rect.inflate(
                                     blur_offset,
                                     blur_offset,
                                 );
                             }
                         }
                     }
-                    BoxShadowClipMode::Inset => {}
+                    BoxShadowClipMode::Inset => {
+                        *content_rect = local_content_rect;
+                    }
                 }
 
-                self.content_rect
+                prim_local_rect
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_context: &PrimitiveContext,
         render_tasks: &mut RenderTaskTree,
+        screen_rect: &DeviceIntRect,
+        child_tasks: Vec<RenderTaskId>,
+        parent_tasks: &mut Vec<RenderTaskId>,
     ) {
-        // This is a shadow element. Create a render task that will
-        // render the text run to a target, and then apply a gaussian
-        // blur to that text run in order to build the actual primitive
-        // which will be blitted to the framebuffer.
+        match self.kind {
+            PictureKind::Image {
+                ref mut readback_render_task_id,
+                composite_mode,
+                frame_output_pipeline_id,
+                ..
+            } => {
+                match composite_mode {
+                    Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
+                        let picture_task = RenderTask::new_dynamic_alpha_batch(
+                            screen_rect,
+                            prim_index,
+                            None,
+                            child_tasks,
+                        );
+
+                        let blur_radius = device_length(blur_radius, prim_context.device_pixel_ratio);
+                        let blur_std_deviation = blur_radius.0 as f32;
+                        let picture_task_id = render_tasks.add(picture_task);
+
+                        let blur_render_task = RenderTask::new_blur(
+                            blur_std_deviation,
+                            picture_task_id,
+                            render_tasks,
+                            RenderTargetKind::Color,
+                            &[],
+                            ClearMode::Transparent,
+                            PremultipliedColorF::TRANSPARENT,
+                        );
 
-        // TODO(gw): Rounding the content rect here to device pixels is not
-        // technically correct. Ideally we should ceil() here, and ensure that
-        // the extra part pixel in the case of fractional sizes is correctly
-        // handled. For now, just use rounding which passes the existing
-        // Gecko tests.
-        let cache_width =
-            (self.content_rect.size.width * prim_context.device_pixel_ratio).round() as i32;
-        let cache_height =
-            (self.content_rect.size.height * prim_context.device_pixel_ratio).round() as i32;
-        let cache_size = DeviceIntSize::new(cache_width, cache_height);
+                        let blur_render_task_id = render_tasks.add(blur_render_task);
+                        self.render_task_id = Some(blur_render_task_id);
+                    }
+                    Some(PictureCompositeMode::MixBlend(..)) => {
+                        let picture_task = RenderTask::new_dynamic_alpha_batch(
+                            screen_rect,
+                            prim_index,
+                            None,
+                            child_tasks,
+                        );
+
+                        let readback_task_id = render_tasks.add(RenderTask::new_readback(*screen_rect));
+
+                        *readback_render_task_id = Some(readback_task_id);
+                        parent_tasks.push(readback_task_id);
+
+                        self.render_task_id = Some(render_tasks.add(picture_task));
+                    }
+                    Some(PictureCompositeMode::Filter(..)) | Some(PictureCompositeMode::Blit) => {
+                        let picture_task = RenderTask::new_dynamic_alpha_batch(
+                            screen_rect,
+                            prim_index,
+                            frame_output_pipeline_id,
+                            child_tasks,
+                        );
 
-        match self.kind {
-            PictureKind::TextShadow { blur_radius, color, .. } => {
+                        self.render_task_id = Some(render_tasks.add(picture_task));
+                    }
+                    None => {
+                        parent_tasks.extend(child_tasks);
+                        self.render_task_id = None;
+                    }
+                }
+            }
+            PictureKind::TextShadow { blur_radius, color, content_rect, .. } => {
+                // This is a shadow element. Create a render task that will
+                // render the text run to a target, and then apply a gaussian
+                // blur to that text run in order to build the actual primitive
+                // which will be blitted to the framebuffer.
+
                 let blur_radius = device_length(blur_radius, prim_context.device_pixel_ratio);
 
+                // TODO(gw): Rounding the content rect here to device pixels is not
+                // technically correct. Ideally we should ceil() here, and ensure that
+                // the extra part pixel in the case of fractional sizes is correctly
+                // handled. For now, just use rounding which passes the existing
+                // Gecko tests.
+                let cache_width =
+                    (content_rect.size.width * prim_context.device_pixel_ratio).round() as i32;
+                let cache_height =
+                    (content_rect.size.height * prim_context.device_pixel_ratio).round() as i32;
+                let cache_size = DeviceIntSize::new(cache_width, cache_height);
+
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let blur_std_deviation = blur_radius.0 as f32 * 0.5;
 
                 let picture_task = RenderTask::new_picture(
                     cache_size,
                     prim_index,
                     RenderTargetKind::Color,
-                    self.content_rect.origin,
+                    content_rect.origin,
                     color.premultiplied(),
                     ClearMode::Transparent,
                 );
 
                 let picture_task_id = render_tasks.add(picture_task);
 
                 let render_task = RenderTask::new_blur(
                     blur_std_deviation,
@@ -217,19 +391,30 @@ impl PicturePrimitive {
                     RenderTargetKind::Color,
                     &[],
                     ClearMode::Transparent,
                     color.premultiplied(),
                 );
 
                 self.render_task_id = Some(render_tasks.add(render_task));
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, ref blur_regions, color, .. } => {
+            PictureKind::BoxShadow { blur_radius, clip_mode, ref blur_regions, color, content_rect, .. } => {
                 let blur_radius = device_length(blur_radius, prim_context.device_pixel_ratio);
 
+                // TODO(gw): Rounding the content rect here to device pixels is not
+                // technically correct. Ideally we should ceil() here, and ensure that
+                // the extra part pixel in the case of fractional sizes is correctly
+                // handled. For now, just use rounding which passes the existing
+                // Gecko tests.
+                let cache_width =
+                    (content_rect.size.width * prim_context.device_pixel_ratio).round() as i32;
+                let cache_height =
+                    (content_rect.size.height * prim_context.device_pixel_ratio).round() as i32;
+                let cache_size = DeviceIntSize::new(cache_width, cache_height);
+
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let blur_std_deviation = blur_radius.0 as f32 * 0.5;
 
                 let blur_clear_mode = match clip_mode {
                     BoxShadowClipMode::Outset => {
                         ClearMode::One
@@ -238,17 +423,17 @@ impl PicturePrimitive {
                         ClearMode::Zero
                     }
                 };
 
                 let picture_task = RenderTask::new_picture(
                     cache_size,
                     prim_index,
                     RenderTargetKind::Alpha,
-                    self.content_rect.origin,
+                    content_rect.origin,
                     color.premultiplied(),
                     ClearMode::Zero,
                 );
 
                 let picture_task_id = render_tasks.add(picture_task);
 
                 let render_task = RenderTask::new_blur(
                     blur_std_deviation,
@@ -258,23 +443,28 @@ impl PicturePrimitive {
                     blur_regions,
                     blur_clear_mode,
                     color.premultiplied(),
                 );
 
                 self.render_task_id = Some(render_tasks.add(render_task));
             }
         }
+
+        if let Some(render_task_id) = self.render_task_id {
+            parent_tasks.push(render_task_id);
+        }
     }
 
     pub fn write_gpu_blocks(&self, mut _request: GpuDataRequest) {
         // TODO(gw): We'll need to write the GPU blocks
         //           here specific to a brush primitive
         //           once we start drawing pictures as brushes!
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             PictureKind::TextShadow { .. } => RenderTargetKind::Color,
             PictureKind::BoxShadow { .. } => RenderTargetKind::Alpha,
+            PictureKind::Image { .. } => RenderTargetKind::Color,
         }
     }
 }
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -35,49 +35,46 @@ fn dwrite_texture_type(render_mode: Font
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
     }
 }
 
 fn dwrite_measure_mode(
     render_mode: FontRenderMode,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_MEASURING_MODE {
-    if let Some(FontInstancePlatformOptions {
-        force_gdi_rendering: true,
-        ..
-    }) = options
-    {
+    let FontInstancePlatformOptions { force_gdi_rendering, use_embedded_bitmap, .. } =
+        options.unwrap_or_default();
+    if force_gdi_rendering || use_embedded_bitmap {
         return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
     }
 
     match render_mode {
         FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
     }
 }
 
 fn dwrite_render_mode(
     font_face: &dwrote::FontFace,
     render_mode: FontRenderMode,
     em_size: f32,
     measure_mode: dwrote::DWRITE_MEASURING_MODE,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_RENDERING_MODE {
-    if let Some(FontInstancePlatformOptions {
-        force_gdi_rendering: true,
-        ..
-    }) = options
-    {
-        return dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC;
-    }
+    let FontInstancePlatformOptions { force_gdi_rendering, use_embedded_bitmap, .. } =
+        options.unwrap_or_default();
 
     let dwrite_render_mode = match render_mode {
         FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_RENDERING_MODE_ALIASED,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => {
-            font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
+            if force_gdi_rendering || use_embedded_bitmap {
+                dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC
+            } else {
+                font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
+            }
         }
     };
 
     if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
         // Outline mode is not supported
         return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
     }
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2,45 +2,43 @@
  * 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::{BorderRadius, BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect};
 use api::{DevicePoint, ExtendMode, GlyphInstance, GlyphKey};
 use api::{GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, LayerPoint, LayerRect};
 use api::{ClipMode, LayerSize, LayerVector2D, LineOrientation, LineStyle};
 use api::{ClipAndScrollInfo, EdgeAaSegmentMask, PremultipliedColorF, TileOffset};
-use api::{YuvColorSpace, YuvFormat};
+use api::{ClipId, LayerTransform, PipelineId, YuvColorSpace, YuvFormat};
 use border::BorderCornerInstance;
-use clip::{ClipSourcesHandle, ClipStore, Geometry};
+use clip_scroll_tree::ClipScrollTree;
+use clip::{ClipSourcesHandle, ClipStore};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::FontInstance;
+use internal_types::FastHashMap;
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
-use picture::PicturePrimitive;
-use render_task::{ClipWorkItem, ClipChainNode, RenderTask, RenderTaskId, RenderTaskTree};
+use picture::{PictureKind, PicturePrimitive};
+use profiler::FrameProfileCounters;
+use render_task::{ClipWorkItem, ClipChainNode};
+use render_task::{RenderTask, RenderTaskId, RenderTaskTree};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
 use resource_cache::{ImageProperties, ResourceCache};
+use scene::{ScenePipeline, SceneProperties};
 use std::{mem, usize};
 use std::rc::Rc;
 use util::{pack_as_float, recycle_vec, MatrixHelpers, TransformedRect, TransformedRectKind};
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
     pub clip_and_scroll: ClipAndScrollInfo,
 }
 
-#[derive(Debug)]
-pub struct PrimitiveRunResult {
-    pub local_rect: LayerRect,
-    pub device_rect: DeviceIntRect,
-    pub visible_primitives: usize,
-}
-
 #[derive(Debug, Copy, Clone)]
 pub struct PrimitiveOpacity {
     pub is_opaque: bool,
 }
 
 impl PrimitiveOpacity {
     pub fn opaque() -> PrimitiveOpacity {
         PrimitiveOpacity { is_opaque: true }
@@ -56,16 +54,37 @@ impl PrimitiveOpacity {
         }
     }
 
     pub fn accumulate(&mut self, alpha: f32) {
         self.is_opaque = self.is_opaque && alpha == 1.0;
     }
 }
 
+// Represents the local space rect of a list of
+// primitive runs. For most primitive runs, the
+// primitive runs are attached to the parent they
+// are declared in. However, when a primitive run
+// is part of a 3d rendering context, it may get
+// hoisted to a higher level in the picture tree.
+// When this happens, we need to also calculate the
+// local space rects in the original space. This
+// allows constructing the true world space polygons
+// for the primitive, to enable the plane splitting
+// logic to work correctly.
+// TODO(gw) In the future, we can probably simplify
+//          this - perhaps calculate the world space
+//          polygons directly and store internally
+//          in the picture structure.
+#[derive(Debug)]
+pub struct PrimitiveRunLocalRect {
+    pub local_rect_in_actual_parent_space: LayerRect,
+    pub local_rect_in_original_parent_space: LayerRect,
+}
+
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
 /// updated on the CPU when the texture size changes.
 #[derive(Copy, Clone, Debug)]
 pub struct TexelRect {
     pub uv0: DevicePoint,
@@ -1057,64 +1076,38 @@ impl PrimitiveStore {
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
     }
 
-    /// Add any task dependencies for this primitive to the provided task.
-    pub fn add_render_tasks_for_prim(&self, prim_index: PrimitiveIndex, task: &mut RenderTask) {
-        // Add any dynamic render tasks needed to render this primitive
-        let metadata = &self.cpu_metadata[prim_index.0];
-
-        let render_task_id = match metadata.prim_kind {
-            PrimitiveKind::Picture => {
-                let picture = &self.cpu_pictures[metadata.cpu_prim_index.0];
-                picture.render_task_id
-            }
-            PrimitiveKind::Rectangle |
-            PrimitiveKind::TextRun |
-            PrimitiveKind::Image |
-            PrimitiveKind::AlignedGradient |
-            PrimitiveKind::YuvImage |
-            PrimitiveKind::Border |
-            PrimitiveKind::AngleGradient |
-            PrimitiveKind::RadialGradient |
-            PrimitiveKind::Line |
-            PrimitiveKind::Brush => None,
-        };
-
-        if let Some(render_task_id) = render_task_id {
-            task.children.push(render_task_id);
-        }
-
-        if let Some(clip_task_id) = metadata.clip_task_id {
-            task.children.push(clip_task_id);
-        }
-    }
-
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_context: &PrimitiveContext,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
+        child_tasks: Vec<RenderTaskId>,
+        parent_tasks: &mut Vec<RenderTaskId>,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Rectangle | PrimitiveKind::Border | PrimitiveKind::Line => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
                         prim_context,
-                        render_tasks
+                        render_tasks,
+                        metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
+                        child_tasks,
+                        parent_tasks,
                     );
             }
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 text.prepare_for_render(
                     resource_cache,
                     prim_context.device_pixel_ratio,
                     prim_context.display_list,
@@ -1221,18 +1214,20 @@ impl PrimitiveStore {
         &mut self,
         prim_index: PrimitiveIndex,
         prim_context: &PrimitiveContext,
         prim_screen_rect: DeviceIntRect,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
+        tasks: &mut Vec<RenderTaskId>,
     ) -> bool {
         let metadata = &mut self.cpu_metadata[prim_index.0];
+        metadata.clip_task_id = None;
         let transform = &prim_context.scroll_node.world_content_transform;
 
         clip_store.get_mut(&metadata.clip_sources).update(
             transform,
             gpu_cache,
             resource_cache,
             prim_context.device_pixel_ratio,
         );
@@ -1278,150 +1273,284 @@ impl PrimitiveStore {
                 clip_store,
                 is_axis_aligned,
                 prim_context.scroll_node.coordinate_system_id,
             )
         } else {
             None
         };
 
-        metadata.clip_task_id = clip_task.map(|clip_task| render_tasks.add(clip_task));
+        if let Some(clip_task) = clip_task {
+            let clip_task_id = render_tasks.add(clip_task);
+
+            metadata.clip_task_id = Some(clip_task_id);
+            tasks.push(clip_task_id);
+        }
+
         true
     }
 
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_context: &PrimitiveContext,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
-    ) -> Option<Geometry> {
-        let (geometry, dependent_primitives) = {
+        clip_scroll_tree: &ClipScrollTree,
+        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        perform_culling: bool,
+        parent_tasks: &mut Vec<RenderTaskId>,
+        scene_properties: &SceneProperties,
+        profile_counters: &mut FrameProfileCounters,
+    ) -> Option<LayerRect> {
+        // Reset the visibility of this primitive.
+        // Do some basic checks first, that can early out
+        // without even knowing the local rect.
+        let (cpu_prim_index, dependencies, cull_children) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             metadata.screen_rect = None;
 
+            if perform_culling &&
+               !metadata.is_backface_visible &&
+               prim_context.scroll_node.world_content_transform.is_backface_visible() {
+                return None;
+            }
+
+            let (dependencies, cull_children) = match metadata.prim_kind {
+                PrimitiveKind::Picture => {
+                    let pic = &mut self.cpu_pictures[metadata.cpu_prim_index.0];
+
+                    if !pic.resolve_scene_properties(scene_properties) {
+                        return None;
+                    }
+
+                    let rfid = match pic.kind {
+                        PictureKind::Image { reference_frame_id, .. } => Some(reference_frame_id),
+                        _ => None,
+                    };
+                    (Some((pic.pipeline_id, mem::replace(&mut pic.runs, Vec::new()), rfid)), pic.cull_children)
+                }
+                _ => {
+                    (None, true)
+                }
+            };
+
+            (metadata.cpu_prim_index, dependencies, cull_children)
+        };
+
+        // 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 mut child_tasks = Vec::new();
+        if let Some((pipeline_id, dependencies, rfid)) = dependencies {
+            let result = self.prepare_prim_runs(
+                &dependencies,
+                pipeline_id,
+                gpu_cache,
+                resource_cache,
+                render_tasks,
+                clip_store,
+                clip_scroll_tree,
+                pipelines,
+                prim_context,
+                cull_children,
+                &mut child_tasks,
+                profile_counters,
+                rfid,
+                scene_properties,
+            );
+
+            let metadata = &mut self.cpu_metadata[prim_index.0];
+
+            // Restore the dependencies (borrow check dance)
+            let pic = &mut self.cpu_pictures[cpu_prim_index.0];
+            pic.runs = dependencies;
+
+            metadata.local_rect = pic.update_local_rect(
+                metadata.local_rect,
+                result,
+            );
+        }
+
+        let (local_rect, device_rect) = {
+            let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 warn!("invalid primitive rect {:?}", metadata.local_rect);
                 return None;
             }
 
-            if !metadata.is_backface_visible &&
-               prim_context.scroll_node.world_content_transform.is_backface_visible() {
-                return None;
-            }
-
             let local_rect = metadata
                 .local_rect
                 .intersection(&metadata.local_clip_rect);
 
             let local_rect = match local_rect {
                 Some(local_rect) => local_rect,
-                None => return None,
+                None if perform_culling => return None,
+                None => LayerRect::zero(),
             };
 
             let xf_rect = TransformedRect::new(
                 &local_rect,
                 &prim_context.scroll_node.world_content_transform,
                 prim_context.device_pixel_ratio
             );
 
             let clip_bounds = &prim_context.clip_node.combined_clip_outer_bounds;
             metadata.screen_rect = xf_rect.bounding_rect
                                           .intersection(clip_bounds);
 
-            let geometry = match metadata.screen_rect {
-                Some(device_rect) => Geometry {
-                    local_rect,
-                    device_rect,
-                },
-                None => return None,
+            let device_rect = match metadata.screen_rect {
+                Some(device_rect) => device_rect,
+                None => {
+                    if perform_culling {
+                        return None
+                    } else {
+                        DeviceIntRect::zero()
+                    }
+                }
             };
 
-            let dependencies = match metadata.prim_kind {
-                PrimitiveKind::Picture =>
-                    self.cpu_pictures[metadata.cpu_prim_index.0].prim_runs.clone(),
-                _ => Vec::new(),
-            };
-            (geometry, dependencies)
+            (local_rect, device_rect)
         };
 
-        // Recurse into any sub primitives and prepare them for rendering first.
-        // TODO(gw): This code is a bit hacky to work around the borrow checker.
-        //           Specifically, the clone() below on the primitive list for
-        //           text shadow primitives. Consider restructuring this code to
-        //           avoid borrow checker issues.
-        for run in dependent_primitives {
-            for i in 0 .. run.count {
-                let sub_prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
-
-                self.prepare_prim_for_render_inner(
-                    sub_prim_index,
-                    prim_context,
-                    resource_cache,
-                    gpu_cache,
-                    render_tasks,
-                );
-            }
-        }
-
         if !self.update_clip_task(
             prim_index,
             prim_context,
-            geometry.device_rect,
+            device_rect,
             resource_cache,
             gpu_cache,
             render_tasks,
             clip_store,
-        ) {
+            parent_tasks,
+        ) && perform_culling {
             return None;
         }
 
         self.prepare_prim_for_render_inner(
             prim_index,
             prim_context,
             resource_cache,
             gpu_cache,
             render_tasks,
+            child_tasks,
+            parent_tasks,
         );
 
-        Some(geometry)
+        Some(local_rect)
     }
 
-    pub fn prepare_prim_run(
+    // TODO(gw): Make this simpler / more efficient by tidying
+    //           up the logic that early outs from prepare_prim_for_render.
+    pub fn reset_prim_visibility(&mut self) {
+        for md in &mut self.cpu_metadata {
+            md.screen_rect = None;
+        }
+    }
+
+    pub fn prepare_prim_runs(
         &mut self,
-        run: &PrimitiveRun,
-        prim_context: &PrimitiveContext,
+        runs: &[PrimitiveRun],
+        pipeline_id: PipelineId,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         render_tasks: &mut RenderTaskTree,
         clip_store: &mut ClipStore,
-    ) -> PrimitiveRunResult {
-        let mut result = PrimitiveRunResult {
-            local_rect: LayerRect::zero(),
-            device_rect: DeviceIntRect::zero(),
-            visible_primitives: 0,
+        clip_scroll_tree: &ClipScrollTree,
+        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        parent_prim_context: &PrimitiveContext,
+        perform_culling: bool,
+        parent_tasks: &mut Vec<RenderTaskId>,
+        profile_counters: &mut FrameProfileCounters,
+        original_reference_frame_id: Option<ClipId>,
+        scene_properties: &SceneProperties,
+    ) -> PrimitiveRunLocalRect {
+        let mut result = PrimitiveRunLocalRect {
+            local_rect_in_actual_parent_space: LayerRect::zero(),
+            local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
-        for i in 0 .. run.count {
-            let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
+        for run in runs {
+            // TODO(gw): Perhaps we can restructure this to not need to create
+            //           a new primitive context for every run (if the hash
+            //           lookups ever show up in a profile).
+            let scroll_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
+            let clip_node = &clip_scroll_tree.nodes[&run.clip_and_scroll.clip_node_id()];
+
+            if perform_culling && !clip_node.is_visible() {
+                debug!("{:?} of clipped out {:?}", run.base_prim_index, pipeline_id);
+                continue;
+            }
+
+            let parent_relative_transform = parent_prim_context
+                .scroll_node
+                .world_content_transform
+                .inverse()
+                .map(|inv_parent| {
+                    inv_parent.pre_mul(&scroll_node.world_content_transform)
+                });
+
+            let original_relative_transform = original_reference_frame_id
+                .and_then(|original_reference_frame_id| {
+                    let parent = clip_scroll_tree
+                        .nodes[&original_reference_frame_id]
+                        .world_content_transform;
+                    parent.inverse()
+                        .map(|inv_parent| {
+                            inv_parent.pre_mul(&scroll_node.world_content_transform)
+                        })
+                });
+
+            let display_list = &pipelines
+                .get(&pipeline_id)
+                .expect("No display list?")
+                .display_list;
 
-            if let Some(prim_geom) = self.prepare_prim_for_render(
-                prim_index,
-                prim_context,
-                resource_cache,
-                gpu_cache,
-                render_tasks,
-                clip_store,
-            ) {
-                result.local_rect = result.local_rect.union(&prim_geom.local_rect);
-                result.device_rect = result.device_rect.union(&prim_geom.device_rect);
-                result.visible_primitives += 1;
+            let child_prim_context = PrimitiveContext::new(
+                parent_prim_context.device_pixel_ratio,
+                display_list,
+                clip_node,
+                scroll_node,
+            );
+
+            for i in 0 .. run.count {
+                let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
+
+                if let Some(prim_local_rect) = self.prepare_prim_for_render(
+                    prim_index,
+                    &child_prim_context,
+                    resource_cache,
+                    gpu_cache,
+                    render_tasks,
+                    clip_store,
+                    clip_scroll_tree,
+                    pipelines,
+                    perform_culling,
+                    parent_tasks,
+                    scene_properties,
+                    profile_counters,
+                ) {
+                    profile_counters.visible_primitives.inc();
+
+                    if let Some(ref matrix) = original_relative_transform {
+                        let bounds = get_local_bounding_rect(&prim_local_rect, matrix);
+                        result.local_rect_in_original_parent_space =
+                            result.local_rect_in_original_parent_space.union(&bounds);
+                    }
+
+                    if let Some(ref matrix) = parent_relative_transform {
+                        let bounds = get_local_bounding_rect(&prim_local_rect, matrix);
+                        result.local_rect_in_actual_parent_space =
+                            result.local_rect_in_actual_parent_space.union(&bounds);
+                    }
+                }
             }
         }
 
         result
     }
 }
 
 //Test for one clip region contains another
@@ -1443,8 +1572,34 @@ impl InsideTest<ComplexClipRegion> for C
             clip.radii.top_right.width >= self.radii.top_right.width - delta_right &&
             clip.radii.top_right.height >= self.radii.top_right.height - delta_top &&
             clip.radii.bottom_left.width >= self.radii.bottom_left.width - delta_left &&
             clip.radii.bottom_left.height >= self.radii.bottom_left.height - delta_bottom &&
             clip.radii.bottom_right.width >= self.radii.bottom_right.width - delta_right &&
             clip.radii.bottom_right.height >= self.radii.bottom_right.height - delta_bottom
     }
 }
+
+fn get_local_bounding_rect(
+    local_rect: &LayerRect,
+    matrix: &LayerTransform
+) -> LayerRect {
+    let vertices = [
+        matrix.transform_point3d(&local_rect.origin.to_3d()),
+        matrix.transform_point3d(&local_rect.bottom_left().to_3d()),
+        matrix.transform_point3d(&local_rect.bottom_right().to_3d()),
+        matrix.transform_point3d(&local_rect.top_right().to_3d()),
+    ];
+
+    let mut x0 = vertices[0].x;
+    let mut y0 = vertices[0].y;
+    let mut x1 = vertices[0].x;
+    let mut y1 = vertices[0].y;
+
+    for v in &vertices[1..] {
+        x0 = x0.min(v.x);
+        y0 = y0.min(v.y);
+        x1 = x1.max(v.x);
+        y1 = y1.max(v.y);
+    }
+
+    LayerRect::new(LayerPoint::new(x0, y0), LayerSize::new(x1 - x0, y1 - y0))
+}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -88,16 +88,17 @@ impl Document {
         let accumulated_scale_factor = self.accumulated_scale_factor();
         self.frame_builder = self.frame_ctx.create(
             self.frame_builder.take(),
             &self.scene,
             resource_cache,
             self.window_size,
             self.inner_rect,
             accumulated_scale_factor,
+            &self.output_pipelines,
         );
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
@@ -111,19 +112,19 @@ impl Document {
             Some(ref mut builder) => {
                 self.frame_ctx.build_renderer_frame(
                     builder,
                     resource_cache,
                     gpu_cache,
                     &self.scene.pipelines,
                     accumulated_scale_factor,
                     pan,
-                    &self.output_pipelines,
                     &mut resource_profile.texture_cache,
                     &mut resource_profile.gpu_cache,
+                    &self.scene.properties,
                 )
             }
             None => {
                 self.frame_ctx.get_renderer_frame()
             }
         }
     }
 }
@@ -405,29 +406,18 @@ impl RenderBackend {
                 profile_scope!("GetScrollNodeState");
                 tx.send(doc.frame_ctx.get_scroll_node_state()).unwrap();
                 DocumentOp::Nop
             }
             DocumentMsg::GenerateFrame(property_bindings) => {
                 profile_scope!("GenerateFrame");
                 let _timer = profile_counters.total_time.timer();
 
-                // Ideally, when there are property bindings present,
-                // we won't need to rebuild the entire frame here.
-                // However, to avoid conflicts with the ongoing work to
-                // refactor how scroll roots + transforms work, this
-                // just rebuilds the frame if there are animated property
-                // bindings present for now.
-                // TODO(gw): Once the scrolling / reference frame changes
-                //           are completed, optimize the internals of
-                //           animated properties to not require a full
-                //           rebuild of the frame!
                 if let Some(property_bindings) = property_bindings {
                     doc.scene.properties.set_properties(property_bindings);
-                    doc.build_scene(&mut self.resource_cache);
                 }
 
                 if let Some(ref mut ros) = doc.render_on_scroll {
                     *ros = true;
                 }
 
                 if doc.scene.root_pipeline_id.is_some() {
                     let frame = doc.render(
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,25 +1,23 @@
 /* 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::{ClipId, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{FilterOp, LayerPoint, LayerRect, MixBlendMode};
+use api::{LayerPoint, LayerRect};
 use api::{PipelineId, PremultipliedColorF};
 use clip::{ClipSource, ClipSourcesWeakHandle, ClipStore};
 use clip_scroll_tree::CoordinateSystemId;
-use gpu_cache::GpuCacheHandle;
 use gpu_types::{ClipScrollNodeIndex};
-use internal_types::HardwareCompositeOp;
-use prim_store::PrimitiveIndex;
+use prim_store::{PrimitiveIndex};
 use std::{cmp, usize, f32, i32};
 use std::rc::Rc;
 use tiling::{RenderPass, RenderTargetIndex};
-use tiling::{RenderTargetKind, StackingContextIndex};
+use tiling::{RenderTargetKind};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache!
 
@@ -148,41 +146,19 @@ pub enum RenderTaskKey {
 
 #[derive(Debug)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug)]
-pub enum AlphaRenderItem {
-    Primitive(ClipScrollNodeIndex, ClipScrollNodeIndex, PrimitiveIndex, i32),
-    Blend(StackingContextIndex, RenderTaskId, FilterOp, i32),
-    Composite(
-        StackingContextIndex,
-        RenderTaskId,
-        RenderTaskId,
-        MixBlendMode,
-        i32,
-    ),
-    SplitComposite(StackingContextIndex, RenderTaskId, GpuCacheHandle, i32),
-    HardwareComposite(
-        StackingContextIndex,
-        RenderTaskId,
-        HardwareCompositeOp,
-        DeviceIntPoint,
-        i32,
-        DeviceIntSize,
-    ),
-}
-
-#[derive(Debug)]
 pub struct AlphaRenderTask {
     pub screen_origin: DeviceIntPoint,
-    pub items: Vec<AlphaRenderItem>,
+    pub prim_index: PrimitiveIndex,
     // If this render task is a registered frame output, this
     // contains the pipeline ID it maps to.
     pub frame_output_pipeline_id: Option<PipelineId>,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskSegment {
@@ -307,40 +283,53 @@ pub struct RenderTask {
     pub cache_key: Option<RenderTaskKey>,
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
 }
 
 impl RenderTask {
+    // TODO(gw): In the future we'll remove this
+    //           completely and convert everything
+    //           that is an alpha task to a Picture.
     pub fn new_alpha_batch(
         screen_origin: DeviceIntPoint,
         location: RenderTaskLocation,
+        prim_index: PrimitiveIndex,
         frame_output_pipeline_id: Option<PipelineId>,
+        children: Vec<RenderTaskId>,
     ) -> Self {
         RenderTask {
             cache_key: None,
-            children: Vec::new(),
+            children,
             location,
             kind: RenderTaskKind::Alpha(AlphaRenderTask {
                 screen_origin,
-                items: Vec::new(),
+                prim_index,
                 frame_output_pipeline_id,
             }),
             clear_mode: ClearMode::Transparent,
         }
     }
 
     pub fn new_dynamic_alpha_batch(
         rect: &DeviceIntRect,
+        prim_index: PrimitiveIndex,
         frame_output_pipeline_id: Option<PipelineId>,
+        children: Vec<RenderTaskId>,
     ) -> Self {
         let location = RenderTaskLocation::Dynamic(None, rect.size);
-        Self::new_alpha_batch(rect.origin, location, frame_output_pipeline_id)
+        Self::new_alpha_batch(
+            rect.origin,
+            location,
+            prim_index,
+            frame_output_pipeline_id,
+            children,
+        )
     }
 
     pub fn new_picture(
         size: DeviceIntSize,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
         content_origin: LayerPoint,
         color: PremultipliedColorF,
@@ -548,29 +537,16 @@ impl RenderTask {
             kind: RenderTaskKind::Scaling(target_kind),
             clear_mode: match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
         }
     }
 
-    pub fn as_alpha_batch_mut<'a>(&'a mut self) -> &'a mut AlphaRenderTask {
-        match self.kind {
-            RenderTaskKind::Alpha(ref mut task) => task,
-            RenderTaskKind::Picture(..) |
-            RenderTaskKind::CacheMask(..) |
-            RenderTaskKind::VerticalBlur(..) |
-            RenderTaskKind::Readback(..) |
-            RenderTaskKind::HorizontalBlur(..) |
-            RenderTaskKind::Alias(..) |
-            RenderTaskKind::Scaling(..) => unreachable!(),
-        }
-    }
-
     pub fn as_alpha_batch<'a>(&'a self) -> &'a AlphaRenderTask {
         match self.kind {
             RenderTaskKind::Alpha(ref task) => task,
             RenderTaskKind::Picture(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
@@ -688,59 +664,48 @@ impl RenderTask {
                         0.0,
                     ],
                 }
             }
             RenderTaskKind::Alias(..) => RenderTaskData { data: [0.0; 12] },
         }
     }
 
-    pub fn inflate(&mut self, device_radius: i32) {
-        match self.kind {
-            RenderTaskKind::Alpha(ref mut info) => {
-                match self.location {
-                    RenderTaskLocation::Fixed => {
-                        panic!("bug: inflate only supported for dynamic tasks");
-                    }
-                    RenderTaskLocation::Dynamic(_, ref mut size) => {
-                        size.width += device_radius * 2;
-                        size.height += device_radius * 2;
-                        info.screen_origin.x -= device_radius;
-                        info.screen_origin.y -= device_radius;
-                    }
-                }
-            }
-
-            RenderTaskKind::Readback(..) |
-            RenderTaskKind::CacheMask(..) |
-            RenderTaskKind::VerticalBlur(..) |
-            RenderTaskKind::HorizontalBlur(..) |
-            RenderTaskKind::Picture(..) |
-            RenderTaskKind::Alias(..) |
-            RenderTaskKind::Scaling(..) => {
-                panic!("bug: inflate only supported for alpha tasks");
-            }
-        }
-    }
-
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed => DeviceIntSize::zero(),
             RenderTaskLocation::Dynamic(_, size) => size,
         }
     }
 
     pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
         match self.location {
-            RenderTaskLocation::Fixed => (DeviceIntRect::zero(), RenderTargetIndex(0)),
-            RenderTaskLocation::Dynamic(origin_and_target_index, size) => {
-                let (origin, target_index) =
-                    origin_and_target_index.expect("Should have been allocated by now!");
+            RenderTaskLocation::Fixed => {
+                (DeviceIntRect::zero(), RenderTargetIndex(0))
+            }
+            // Previously, we only added render tasks after the entire
+            // primitive chain was determined visible. This meant that
+            // we could assert any render task in the list was also
+            // allocated (assigned to passes). Now, we add render
+            // tasks earlier, and the picture they belong to may be
+            // culled out later, so we can't assert that the task
+            // has been allocated.
+            // Render tasks that are created but not assigned to
+            // passes consume a row in the render task texture, but
+            // don't allocate any space in render targets nor
+            // draw any pixels.
+            // TODO(gw): Consider some kind of tag or other method
+            //           to mark a task as unused explicitly. This
+            //           would allow us to restore this debug check.
+            RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => {
                 (DeviceIntRect::new(origin, size), target_index)
             }
+            RenderTaskLocation::Dynamic(None, _) => {
+                (DeviceIntRect::zero(), RenderTargetIndex(0))
+            }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
             RenderTaskKind::Alpha(..) |
             RenderTaskKind::Readback(..) => RenderTargetKind::Color,
 
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -550,40 +550,35 @@ impl SourceTextureResolver {
 
         for texture in self.cache_texture_map {
             device.delete_texture(texture);
         }
     }
 
     fn end_pass(
         &mut self,
-        pass_index: usize,
-        pass_count: usize,
-        mut a8_texture: Option<Texture>,
-        mut rgba8_texture: Option<Texture>,
+        is_last: bool,
+        a8_texture: Option<Texture>,
+        rgba8_texture: Option<Texture>,
         a8_pool: &mut Vec<Texture>,
         rgba8_pool: &mut Vec<Texture>,
     ) {
         // If we have cache textures from previous pass, return them to the pool.
         rgba8_pool.extend(self.cache_rgba8_texture.take());
         a8_pool.extend(self.cache_a8_texture.take());
 
-        if pass_index == pass_count - 1 {
+        if is_last {
             // On the last pass, return the textures from this pass to the pool.
-            if let Some(texture) = rgba8_texture.take() {
-                rgba8_pool.push(texture);
-            }
-            if let Some(texture) = a8_texture.take() {
-                a8_pool.push(texture);
-            }
+            rgba8_pool.extend(rgba8_texture);
+            a8_pool.extend(a8_texture);
         } else {
             // We have another pass to process, make these textures available
             // as inputs to the next pass.
-            self.cache_rgba8_texture = rgba8_texture.take();
-            self.cache_a8_texture = a8_texture.take();
+            self.cache_rgba8_texture = rgba8_texture;
+            self.cache_a8_texture = a8_texture;
         }
     }
 
     // Bind a source texture to the device.
     fn bind(&self, texture_id: &SourceTexture, sampler: TextureSampler, device: &mut Device) {
         match *texture_id {
             SourceTexture::Invalid => {}
             SourceTexture::CacheA8 => {
@@ -3459,17 +3454,20 @@ impl Renderer {
         self.device.disable_stencil();
         self.device.set_blend(false);
 
         if frame.passes.is_empty() {
             self.device
                 .clear_target(Some(self.clear_color.to_array()), Some(1.0));
         } else {
             self.start_frame(frame);
+
             let pass_count = frame.passes.len();
+            let base_color_target_count = self.color_render_targets.len();
+            let base_alpha_target_count = self.alpha_render_targets.len();
 
             for (pass_index, pass) in frame.passes.iter_mut().enumerate() {
                 self.texture_resolver.bind(
                     &SourceTexture::CacheA8,
                     TextureSampler::CacheA8,
                     &mut self.device,
                 );
                 self.texture_resolver.bind(
@@ -3544,18 +3542,17 @@ impl Renderer {
                         clear_color,
                         &frame.render_tasks,
                         &projection,
                         frame_id,
                     );
                 }
 
                 self.texture_resolver.end_pass(
-                    pass_index,
-                    pass_count,
+                    pass_index == pass_count - 1,
                     pass.alpha_texture.take(),
                     pass.color_texture.take(),
                     &mut self.alpha_render_targets,
                     &mut self.color_render_targets,
                 );
 
                 // After completing the first pass, make the A8 target available as an
                 // input to any subsequent passes.
@@ -3564,18 +3561,18 @@ impl Renderer {
                         self.texture_resolver.resolve(&SourceTexture::CacheA8)
                     {
                         self.device
                             .bind_texture(TextureSampler::SharedCacheA8, shared_alpha_texture);
                     }
                 }
             }
 
-            self.color_render_targets.reverse();
-            self.alpha_render_targets.reverse();
+            self.color_render_targets[base_color_target_count..].reverse();
+            self.alpha_render_targets[base_alpha_target_count..].reverse();
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
 
             // Garbage collect any frame outputs that weren't used this frame.
             let device = &mut self.device;
             self.output_targets
                 .retain(|_, target| if target.last_access != frame_id {
                     device.delete_fbo(target.fbo_id);
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -37,46 +37,49 @@ impl SceneProperties {
             self.float_properties
                 .insert(property.key.id, property.value);
         }
     }
 
     /// Get the current value for a transform property.
     pub fn resolve_layout_transform(
         &self,
-        property: Option<&PropertyBinding<LayoutTransform>>,
+        property: &PropertyBinding<LayoutTransform>,
     ) -> LayoutTransform {
-        let property = match property {
-            Some(property) => property,
-            None => return LayoutTransform::identity(),
-        };
-
         match *property {
-            PropertyBinding::Value(matrix) => matrix,
-            PropertyBinding::Binding(ref key) => self.transform_properties
-                .get(&key.id)
-                .cloned()
-                .unwrap_or_else(|| {
-                    warn!("Property binding {:?} has an invalid value.", key);
-                    LayoutTransform::identity()
-                }),
+            PropertyBinding::Value(value) => value,
+            PropertyBinding::Binding(ref key) => {
+                self.transform_properties
+                    .get(&key.id)
+                    .cloned()
+                    .unwrap_or_else(|| {
+                        warn!("Property binding {:?} has an invalid value.", key);
+                        LayoutTransform::identity()
+                    })
+            }
         }
     }
 
     /// Get the current value for a float property.
-    pub fn resolve_float(&self, property: &PropertyBinding<f32>, default_value: f32) -> f32 {
+    pub fn resolve_float(
+        &self,
+        property: &PropertyBinding<f32>,
+        default_value: f32
+    ) -> f32 {
         match *property {
             PropertyBinding::Value(value) => value,
-            PropertyBinding::Binding(ref key) => self.float_properties
-                .get(&key.id)
-                .cloned()
-                .unwrap_or_else(|| {
-                    warn!("Property binding {:?} has an invalid value.", key);
-                    default_value
-                }),
+            PropertyBinding::Binding(ref key) => {
+                self.float_properties
+                    .get(&key.id)
+                    .cloned()
+                    .unwrap_or_else(|| {
+                        warn!("Property binding {:?} has an invalid value.", key);
+                        default_value
+                    })
+            }
         }
     }
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
 pub struct ScenePipeline {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
@@ -136,79 +139,80 @@ impl Scene {
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
             pipeline.epoch = epoch;
         }
     }
 }
 
+/// An arbitrary number which we assume opacity is invisible below.
+pub const OPACITY_EPSILON: f32 = 0.001;
+
 pub trait FilterOpHelpers {
-    fn resolve(self, properties: &SceneProperties) -> FilterOp;
+    fn is_visible(&self) -> bool;
     fn is_noop(&self) -> bool;
 }
 
 impl FilterOpHelpers for FilterOp {
-    fn resolve(self, properties: &SceneProperties) -> FilterOp {
-        match self {
-            FilterOp::Opacity(ref value) => {
-                let amount = properties.resolve_float(value, 1.0);
-                FilterOp::Opacity(PropertyBinding::Value(amount))
+    fn is_visible(&self) -> bool {
+        match *self {
+            FilterOp::Blur(..) |
+            FilterOp::Brightness(..) |
+            FilterOp::Contrast(..) |
+            FilterOp::Grayscale(..) |
+            FilterOp::HueRotate(..) |
+            FilterOp::Invert(..) |
+            FilterOp::Saturate(..) |
+            FilterOp::Sepia(..) => true,
+            FilterOp::Opacity(_, amount) => {
+                amount > OPACITY_EPSILON
             }
-            _ => self,
         }
     }
 
     fn is_noop(&self) -> bool {
         match *self {
             FilterOp::Blur(length) => length == 0.0,
             FilterOp::Brightness(amount) => amount == 1.0,
             FilterOp::Contrast(amount) => amount == 1.0,
             FilterOp::Grayscale(amount) => amount == 0.0,
             FilterOp::HueRotate(amount) => amount == 0.0,
             FilterOp::Invert(amount) => amount == 0.0,
-            FilterOp::Opacity(value) => match value {
-                PropertyBinding::Value(amount) => amount == 1.0,
-                PropertyBinding::Binding(..) => {
-                    panic!("bug: binding value should be resolved");
-                }
-            },
+            FilterOp::Opacity(_, amount) => amount >= 1.0,
             FilterOp::Saturate(amount) => amount == 1.0,
             FilterOp::Sepia(amount) => amount == 0.0,
         }
     }
 }
 
 pub trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filters: ItemRange<FilterOp>,
-        properties: &SceneProperties,
     ) -> Vec<FilterOp>;
 }
 
 impl StackingContextHelpers for StackingContext {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
         match self.mix_blend_mode {
             MixBlendMode::Normal => None,
             _ => Some(self.mix_blend_mode),
         }
     }
 
     fn filter_ops_for_compositing(
         &self,
         display_list: &BuiltDisplayList,
         input_filters: ItemRange<FilterOp>,
-        properties: &SceneProperties,
     ) -> Vec<FilterOp> {
+        // TODO(gw): Now that we resolve these later on,
+        //           we could probably make it a bit
+        //           more efficient than cloning these here.
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
-            let filter = filter.resolve(properties);
-            if filter.is_noop() {
-                continue;
-            }
             filters.push(filter);
         }
         filters
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,47 +1,96 @@
 /* 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::{BorderRadiusKind, ClipId, ColorF, DeviceIntPoint, ImageKey};
 use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
 use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
-use api::{MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
-use api::{LayerVector2D, TileOffset, YuvColorSpace, YuvFormat};
+use api::{MixBlendMode, PipelineId};
+use api::{TileOffset, YuvColorSpace, YuvFormat};
+use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide};
 use clip::{ClipSource, ClipStore};
-use clip_scroll_tree::CoordinateSystemId;
+use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId};
 use device::Texture;
+use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuCacheUpdateList};
 use gpu_types::{BlurDirection, BlurInstance, BrushInstance, BrushImageKind, ClipMaskInstance};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use gpu_types::{BRUSH_FLAG_USES_PICTURE, ClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, SourceTexture};
 use internal_types::BatchTextures;
-use picture::PictureKind;
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, PrimitiveRun, RectangleContent};
 use profiler::FrameProfileCounters;
-use render_task::{AlphaRenderItem, ClipWorkItem, MaskGeometryKind, MaskSegment};
+use render_task::{ClipWorkItem, MaskGeometryKind, MaskSegment};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKey, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::{GlyphFetchResult, ResourceCache};
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32);
 const MIN_TARGET_SIZE: u32 = 2048;
 
+// Helper to add an entire primitive run to a batch list.
+// TODO(gw): Restructure this so the param list isn't quite
+//           so daunting!
+impl PrimitiveRun {
+    fn add_to_batch(
+        &self,
+        clip_id: ClipScrollNodeIndex,
+        scroll_id: ClipScrollNodeIndex,
+        batch_list: &mut BatchList,
+        ctx: &RenderTargetContext,
+        gpu_cache: &mut GpuCache,
+        render_tasks: &RenderTaskTree,
+        task_id: RenderTaskId,
+        task_address: RenderTaskAddress,
+        deferred_resolves: &mut Vec<DeferredResolve>,
+        glyph_fetch_buffer: &mut Vec<GlyphFetchResult>,
+        splitter: &mut BspSplitter<f64, WorldPixel>,
+    ) {
+        for i in 0 .. self.count {
+            let prim_index = PrimitiveIndex(self.base_prim_index.0 + i);
+
+            let md = &ctx.prim_store.cpu_metadata[prim_index.0];
+
+            // Now that we walk the primitive runs in order to add
+            // items to batches, we need to check if they are
+            // visible here.
+            if md.screen_rect.is_some() {
+                add_to_batch(
+                    clip_id,
+                    scroll_id,
+                    prim_index,
+                    batch_list,
+                    ctx,
+                    gpu_cache,
+                    render_tasks,
+                    task_id,
+                    task_address,
+                    deferred_resolves,
+                    glyph_fetch_buffer,
+                    splitter,
+                );
+            }
+        }
+    }
+}
+
 trait AlphaBatchHelpers {
     fn get_blend_mode(
         &self,
         metadata: &PrimitiveMetadata,
         transform_kind: TransformedRectKind,
     ) -> BlendMode;
 }
 
@@ -106,23 +155,16 @@ impl AlphaBatchHelpers for PrimitiveStor
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub clip_id: ClipId,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayerRect,
 }
 
-#[derive(Debug)]
-pub enum PrimitiveRunCmd {
-    PushStackingContext(StackingContextIndex),
-    PopStackingContext,
-    PrimitiveRun(PrimitiveRun),
-}
-
 #[derive(Debug, Copy, Clone)]
 pub struct RenderTargetIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct RenderPassIndex(isize);
 
 #[derive(Debug)]
 struct DynamicTaskInfo {
@@ -290,505 +332,647 @@ impl BatchList {
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     tasks: Vec<RenderTaskId>,
     glyph_fetch_buffer: Vec<GlyphFetchResult>,
 }
 
-impl AlphaRenderItem {
-    fn add_to_batch(
-        &self,
-        batch_list: &mut BatchList,
-        ctx: &RenderTargetContext,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
-        task_id: RenderTaskId,
-        task_address: RenderTaskAddress,
-        deferred_resolves: &mut Vec<DeferredResolve>,
-        glyph_fetch_buffer: &mut Vec<GlyphFetchResult>,
-    ) {
-        match *self {
-            AlphaRenderItem::Blend(stacking_context_index, src_id, filter, z) => {
-                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
-                let key = BatchKey::new(
-                    BatchKind::Blend,
-                    BlendMode::PremultipliedAlpha,
-                    BatchTextures::no_texture(),
-                );
-                let src_task_address = render_tasks.get_task_address(src_id);
+// A free function that adds a primitive to a batch.
+// It can recursively call itself in some situations, for
+// example if it encounters a picture where the items
+// in that picture are being drawn into the same target.
+fn add_to_batch(
+    clip_id: ClipScrollNodeIndex,
+    scroll_id: ClipScrollNodeIndex,
+    prim_index: PrimitiveIndex,
+    batch_list: &mut BatchList,
+    ctx: &RenderTargetContext,
+    gpu_cache: &mut GpuCache,
+    render_tasks: &RenderTaskTree,
+    task_id: RenderTaskId,
+    task_address: RenderTaskAddress,
+    deferred_resolves: &mut Vec<DeferredResolve>,
+    glyph_fetch_buffer: &mut Vec<GlyphFetchResult>,
+    splitter: &mut BspSplitter<f64, WorldPixel>,
+) {
+    let z = prim_index.0 as i32;
+    let prim_metadata = ctx.prim_store.get_metadata(prim_index);
+    let scroll_node = &ctx.node_data[scroll_id.0 as usize];
+    // TODO(gw): Calculating this for every primitive is a bit
+    //           wasteful. We should probably cache this in
+    //           the scroll node...
+    let transform_kind = scroll_node.transform.transform_kind();
+    let item_bounding_rect = prim_metadata.screen_rect.as_ref().unwrap();
+    let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
+    let no_textures = BatchTextures::no_texture();
+    let clip_task_address = prim_metadata
+        .clip_task_id
+        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
+    let base_instance = SimplePrimitiveInstance::new(
+        prim_cache_address,
+        task_address,
+        clip_task_address,
+        clip_id,
+        scroll_id,
+        z,
+    );
 
-                let (filter_mode, amount) = match filter {
-                    FilterOp::Blur(..) => (0, 0.0),
-                    FilterOp::Contrast(amount) => (1, amount),
-                    FilterOp::Grayscale(amount) => (2, amount),
-                    FilterOp::HueRotate(angle) => (3, angle),
-                    FilterOp::Invert(amount) => (4, amount),
-                    FilterOp::Saturate(amount) => (5, amount),
-                    FilterOp::Sepia(amount) => (6, amount),
-                    FilterOp::Brightness(amount) => (7, amount),
-                    FilterOp::Opacity(PropertyBinding::Value(amount)) => (8, amount),
-                    FilterOp::Opacity(_) => unreachable!(),
-                };
+    let blend_mode = ctx.prim_store.get_blend_mode(prim_metadata, transform_kind);
 
-                let amount = (amount * 65535.0).round() as i32;
-                let batch = batch_list.get_suitable_batch(key, &stacking_context.screen_bounds);
+    match prim_metadata.prim_kind {
+        PrimitiveKind::Brush => {
+            panic!("BUG: brush type not expected in an alpha task (yet)");
+        }
+        PrimitiveKind::Border => {
+            let border_cpu =
+                &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
+            // TODO(gw): Select correct blend mode for edges and corners!!
+            let corner_kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::BorderCorner,
+            );
+            let corner_key = BatchKey::new(corner_kind, blend_mode, no_textures);
+            let edge_kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::BorderEdge,
+            );
+            let edge_key = BatchKey::new(edge_kind, blend_mode, no_textures);
 
-                let instance = CompositePrimitiveInstance::new(
-                    task_address,
-                    src_task_address,
-                    RenderTaskAddress(0),
-                    filter_mode,
-                    amount,
-                    z,
-                    0,
-                    0,
-                );
-
-                batch.push(PrimitiveInstance::from(instance));
+            // Work around borrow ck on borrowing batch_list twice.
+            {
+                let batch =
+                    batch_list.get_suitable_batch(corner_key, item_bounding_rect);
+                for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate()
+                {
+                    let sub_index = i as i32;
+                    match *instance_kind {
+                        BorderCornerInstance::None => {}
+                        BorderCornerInstance::Single => {
+                            batch.push(base_instance.build(
+                                sub_index,
+                                BorderCornerSide::Both as i32,
+                                0,
+                            ));
+                        }
+                        BorderCornerInstance::Double => {
+                            batch.push(base_instance.build(
+                                sub_index,
+                                BorderCornerSide::First as i32,
+                                0,
+                            ));
+                            batch.push(base_instance.build(
+                                sub_index,
+                                BorderCornerSide::Second as i32,
+                                0,
+                            ));
+                        }
+                    }
+                }
             }
-            AlphaRenderItem::HardwareComposite(
-                stacking_context_index,
-                src_id,
-                composite_op,
-                screen_origin,
-                z,
-                dest_rect,
-            ) => {
-                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
-                let src_task_address = render_tasks.get_task_address(src_id);
-                let key = BatchKey::new(
-                    BatchKind::HardwareComposite,
-                    composite_op.to_blend_mode(),
-                    BatchTextures::no_texture(),
-                );
-                let batch = batch_list.get_suitable_batch(key, &stacking_context.screen_bounds);
-                let dest_rect = if dest_rect.width > 0 && dest_rect.height > 0 {
-                    dest_rect
-                } else {
-                    render_tasks.get(src_id).get_dynamic_size()
-                };
 
-                let instance = CompositePrimitiveInstance::new(
-                    task_address,
-                    src_task_address,
-                    RenderTaskAddress(0),
-                    screen_origin.x,
-                    screen_origin.y,
-                    z,
-                    dest_rect.width,
-                    dest_rect.height,
-                );
+            let batch = batch_list.get_suitable_batch(edge_key, item_bounding_rect);
+            for border_segment in 0 .. 4 {
+                batch.push(base_instance.build(border_segment, 0, 0));
+            }
+        }
+        PrimitiveKind::Rectangle => {
+            let needs_clipping = prim_metadata.clip_task_id.is_some();
+            let kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::Rectangle(needs_clipping),
+            );
+            let key = BatchKey::new(kind, blend_mode, no_textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            batch.push(base_instance.build(0, 0, 0));
+        }
+        PrimitiveKind::Line => {
+            let kind =
+                BatchKind::Transformable(transform_kind, TransformBatchKind::Line);
+            let key = BatchKey::new(kind, blend_mode, no_textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            batch.push(base_instance.build(0, 0, 0));
+        }
+        PrimitiveKind::Image => {
+            let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
-                batch.push(PrimitiveInstance::from(instance));
+            let (color_texture_id, uv_address) = resolve_image(
+                image_cpu.image_key,
+                image_cpu.image_rendering,
+                image_cpu.tile_offset,
+                ctx.resource_cache,
+                gpu_cache,
+                deferred_resolves,
+            );
+
+            if color_texture_id == SourceTexture::Invalid {
+                warn!("Warnings: skip a PrimitiveKind::Image at {:?}.\n", item_bounding_rect);
+                return;
             }
-            AlphaRenderItem::Composite(stacking_context_index, source_id, backdrop_id, mode, z) => {
-                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
-                let key = BatchKey::new(
-                    BatchKind::Composite {
-                        task_id,
-                        source_id,
-                        backdrop_id,
-                    },
-                    BlendMode::PremultipliedAlpha,
-                    BatchTextures::no_texture(),
-                );
-                let batch = batch_list.get_suitable_batch(key, &stacking_context.screen_bounds);
-                let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
-                let source_task_address = render_tasks.get_task_address(source_id);
+
+            let batch_kind = match color_texture_id {
+                SourceTexture::External(ext_image) => {
+                    match ext_image.image_type {
+                        ExternalImageType::Texture2DHandle => {
+                            TransformBatchKind::Image(ImageBufferKind::Texture2D)
+                        }
+                        ExternalImageType::Texture2DArrayHandle => {
+                            TransformBatchKind::Image(ImageBufferKind::Texture2DArray)
+                        }
+                        ExternalImageType::TextureRectHandle => {
+                            TransformBatchKind::Image(ImageBufferKind::TextureRect)
+                        }
+                        ExternalImageType::TextureExternalHandle => {
+                            TransformBatchKind::Image(ImageBufferKind::TextureExternal)
+                        }
+                        ExternalImageType::ExternalBuffer => {
+                            // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
+                            // It should go through the non-external case.
+                            panic!(
+                                "Non-texture handle type should be handled in other way"
+                            );
+                        }
+                    }
+                }
+                _ => TransformBatchKind::Image(ImageBufferKind::Texture2DArray),
+            };
+
+            let textures = BatchTextures {
+                colors: [
+                    color_texture_id,
+                    SourceTexture::Invalid,
+                    SourceTexture::Invalid,
+                ],
+            };
+
+            let key = BatchKey::new(
+                BatchKind::Transformable(transform_kind, batch_kind),
+                blend_mode,
+                textures,
+            );
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            batch.push(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
+        }
+        PrimitiveKind::TextRun => {
+            let text_cpu =
+                &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
+
+            let font = text_cpu.get_font(ctx.device_pixel_ratio);
+
+            ctx.resource_cache.fetch_glyphs(
+                font,
+                &text_cpu.glyph_keys,
+                glyph_fetch_buffer,
+                gpu_cache,
+                |texture_id, glyph_format, glyphs| {
+                    debug_assert_ne!(texture_id, SourceTexture::Invalid);
 
-                let instance = CompositePrimitiveInstance::new(
-                    task_address,
-                    source_task_address,
-                    backdrop_task_address,
-                    mode as u32 as i32,
-                    0,
-                    z,
-                    0,
-                    0,
-                );
+                    let textures = BatchTextures {
+                        colors: [
+                            texture_id,
+                            SourceTexture::Invalid,
+                            SourceTexture::Invalid,
+                        ],
+                    };
+
+                    let kind = BatchKind::Transformable(
+                        transform_kind,
+                        TransformBatchKind::TextRun(glyph_format),
+                    );
+
+                    let key = BatchKey::new(kind, blend_mode, textures);
+                    let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+
+                    for glyph in glyphs {
+                        batch.push(base_instance.build(
+                            glyph.index_in_text_run,
+                            glyph.uv_rect_address.as_int(),
+                            0,
+                        ));
+                    }
+                },
+            );
+        }
+        PrimitiveKind::Picture => {
+            let picture =
+                &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
+
+            match picture.render_task_id {
+                Some(cache_task_id) => {
+                    let cache_task_address = render_tasks.get_task_address(cache_task_id);
+                    let textures = BatchTextures::render_target_cache();
 
-                batch.push(PrimitiveInstance::from(instance));
-            }
-            AlphaRenderItem::Primitive(clip_id, scroll_id, prim_index, z) => {
-                let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                let scroll_node = &ctx.node_data[scroll_id.0 as usize];
-                // TODO(gw): Calculating this for every primitive is a bit
-                //           wasteful. We should probably cache this in
-                //           the scroll node...
-                let transform_kind = scroll_node.transform.transform_kind();
-                let item_bounding_rect = prim_metadata.screen_rect.as_ref().unwrap();
-                let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
-                let no_textures = BatchTextures::no_texture();
-                let clip_task_address = prim_metadata
-                    .clip_task_id
-                    .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
-                let base_instance = SimplePrimitiveInstance::new(
-                    prim_cache_address,
-                    task_address,
-                    clip_task_address,
-                    clip_id,
-                    scroll_id,
-                    z,
-                );
+                    match picture.kind {
+                        PictureKind::TextShadow { .. } => {
+                            let kind = BatchKind::Brush(
+                                BrushBatchKind::Image(picture.target_kind()),
+                            );
+                            let key = BatchKey::new(kind, blend_mode, textures);
+                            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+
+                            let instance = BrushInstance {
+                                picture_address: task_address,
+                                prim_address: prim_cache_address,
+                                clip_id,
+                                scroll_id,
+                                clip_task_address,
+                                z,
+                                flags: 0,
+                                user_data0: cache_task_address.0 as i32,
+                                user_data1: BrushImageKind::Simple as i32,
+                            };
+                            batch.push(PrimitiveInstance::from(instance));
+                        }
+                        PictureKind::BoxShadow { radii_kind, .. } => {
+                            let kind = BatchKind::Brush(
+                                BrushBatchKind::Image(picture.target_kind()),
+                            );
+                            let key = BatchKey::new(kind, blend_mode, textures);
+                            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+
+                            let image_kind = match radii_kind {
+                                BorderRadiusKind::Uniform => {
+                                    BrushImageKind::Mirror
+                                }
+                                BorderRadiusKind::NonUniform => {
+                                    BrushImageKind::NinePatch
+                                }
+                            };
 
-                let blend_mode = ctx.prim_store.get_blend_mode(prim_metadata, transform_kind);
+                            let instance = BrushInstance {
+                                picture_address: task_address,
+                                prim_address: prim_cache_address,
+                                clip_id,
+                                scroll_id,
+                                clip_task_address,
+                                z,
+                                flags: 0,
+                                user_data0: cache_task_address.0 as i32,
+                                user_data1: image_kind as i32,
+                            };
+                            batch.push(PrimitiveInstance::from(instance));
+                        }
+                        PictureKind::Image {
+                            composite_mode,
+                            readback_render_task_id,
+                            is_in_3d_context,
+                            reference_frame_id,
+                            real_local_rect,
+                            ..
+                        } => {
+                            // If this picture is participating in a 3D rendering context,
+                            // then don't add it to any batches here. Instead, create a polygon
+                            // for it and add it to the current plane splitter.
+                            if is_in_3d_context {
+                                // Push into parent plane splitter.
+
+                                let real_xf = &ctx.clip_scroll_tree.nodes[&reference_frame_id].world_content_transform;
+
+                                let polygon = make_polygon(
+                                    real_local_rect,
+                                    &real_xf,
+                                    prim_index.0,
+                                );
+
+                                splitter.add(polygon);
+
+                                return;
+                            }
 
-                match prim_metadata.prim_kind {
-                    PrimitiveKind::Brush => {
-                        panic!("BUG: brush type not expected in an alpha task (yet)");
-                    }
-                    PrimitiveKind::Border => {
-                        let border_cpu =
-                            &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
-                        // TODO(gw): Select correct blend mode for edges and corners!!
-                        let corner_kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::BorderCorner,
-                        );
-                        let corner_key = BatchKey::new(corner_kind, blend_mode, no_textures);
-                        let edge_kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::BorderEdge,
-                        );
-                        let edge_key = BatchKey::new(edge_kind, blend_mode, no_textures);
+                            // Depending on the composite mode of the picture, we generate the
+                            // old style Composite primitive instances. In the future, we'll
+                            // remove these and pass them through the brush batching pipeline.
+                            // This will allow us to unify some of the shaders, apply clip masks
+                            // when compositing pictures, and also correctly apply pixel snapping
+                            // to picture compositing operations.
+                            let source_id = picture.render_task_id.expect("no source!?");
+
+                            match composite_mode.expect("bug: only composites here") {
+                                PictureCompositeMode::Filter(filter) => {
+                                    match filter {
+                                        FilterOp::Blur(..) => {
+                                            let src_task_address = render_tasks.get_task_address(source_id);
+                                            let key = BatchKey::new(
+                                                BatchKind::HardwareComposite,
+                                                BlendMode::PremultipliedAlpha,
+                                                BatchTextures::no_texture(),
+                                            );
+                                            let batch = batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                            let instance = CompositePrimitiveInstance::new(
+                                                task_address,
+                                                src_task_address,
+                                                RenderTaskAddress(0),
+                                                item_bounding_rect.origin.x,
+                                                item_bounding_rect.origin.y,
+                                                z,
+                                                item_bounding_rect.size.width,
+                                                item_bounding_rect.size.height,
+                                            );
+
+                                            batch.push(PrimitiveInstance::from(instance));
+                                        }
+                                        _ => {
+                                            let key = BatchKey::new(
+                                                BatchKind::Blend,
+                                                BlendMode::PremultipliedAlpha,
+                                                BatchTextures::no_texture(),
+                                            );
+                                            let src_task_address = render_tasks.get_task_address(source_id);
 
-                        // Work around borrow ck on borrowing batch_list twice.
-                        {
-                            let batch =
-                                batch_list.get_suitable_batch(corner_key, item_bounding_rect);
-                            for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate()
-                            {
-                                let sub_index = i as i32;
-                                match *instance_kind {
-                                    BorderCornerInstance::None => {}
-                                    BorderCornerInstance::Single => {
-                                        batch.push(base_instance.build(
-                                            sub_index,
-                                            BorderCornerSide::Both as i32,
-                                            0,
-                                        ));
+                                            let (filter_mode, amount) = match filter {
+                                                FilterOp::Blur(..) => (0, 0.0),
+                                                FilterOp::Contrast(amount) => (1, amount),
+                                                FilterOp::Grayscale(amount) => (2, amount),
+                                                FilterOp::HueRotate(angle) => (3, angle),
+                                                FilterOp::Invert(amount) => (4, amount),
+                                                FilterOp::Saturate(amount) => (5, amount),
+                                                FilterOp::Sepia(amount) => (6, amount),
+                                                FilterOp::Brightness(amount) => (7, amount),
+                                                FilterOp::Opacity(_, amount) => (8, amount),
+                                            };
+
+                                            let amount = (amount * 65535.0).round() as i32;
+                                            let batch = batch_list.get_suitable_batch(key, &item_bounding_rect);
+
+                                            let instance = CompositePrimitiveInstance::new(
+                                                task_address,
+                                                src_task_address,
+                                                RenderTaskAddress(0),
+                                                filter_mode,
+                                                amount,
+                                                z,
+                                                0,
+                                                0,
+                                            );
+
+                                            batch.push(PrimitiveInstance::from(instance));
+                                        }
                                     }
-                                    BorderCornerInstance::Double => {
-                                        batch.push(base_instance.build(
-                                            sub_index,
-                                            BorderCornerSide::First as i32,
-                                            0,
-                                        ));
-                                        batch.push(base_instance.build(
-                                            sub_index,
-                                            BorderCornerSide::Second as i32,
-                                            0,
-                                        ));
-                                    }
+                                }
+                                PictureCompositeMode::MixBlend(mode) => {
+                                    let backdrop_id = readback_render_task_id.expect("no backdrop!?");
+
+                                    let key = BatchKey::new(
+                                        BatchKind::Composite {
+                                            task_id,
+                                            source_id,
+                                            backdrop_id,
+                                        },
+                                        BlendMode::PremultipliedAlpha,
+                                        BatchTextures::no_texture(),
+                                    );
+                                    let batch = batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                    let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
+                                    let source_task_address = render_tasks.get_task_address(source_id);
+
+                                    let instance = CompositePrimitiveInstance::new(
+                                        task_address,
+                                        source_task_address,
+                                        backdrop_task_address,
+                                        mode as u32 as i32,
+                                        0,
+                                        z,
+                                        0,
+                                        0,
+                                    );
+
+                                    batch.push(PrimitiveInstance::from(instance));
+                                }
+                                PictureCompositeMode::Blit => {
+                                    let src_task_address = render_tasks.get_task_address(source_id);
+                                    let key = BatchKey::new(
+                                        BatchKind::HardwareComposite,
+                                        BlendMode::PremultipliedAlpha,
+                                        BatchTextures::no_texture(),
+                                    );
+                                    let batch = batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                    let instance = CompositePrimitiveInstance::new(
+                                        task_address,
+                                        src_task_address,
+                                        RenderTaskAddress(0),
+                                        item_bounding_rect.origin.x,
+                                        item_bounding_rect.origin.y,
+                                        z,
+                                        item_bounding_rect.size.width,
+                                        item_bounding_rect.size.height,
+                                    );
+
+                                    batch.push(PrimitiveInstance::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.
+                    picture.add_to_batch(
+                        task_id,
+                        ctx,
+                        gpu_cache,
+                        render_tasks,
+                        deferred_resolves,
+                        batch_list,
+                        glyph_fetch_buffer,
+                    );
+                }
+            }
+        }
+        PrimitiveKind::AlignedGradient => {
+            let gradient_cpu =
+                &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
+            let kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::AlignedGradient,
+            );
+            let key = BatchKey::new(kind, blend_mode, no_textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            for part_index in 0 .. (gradient_cpu.stops_count - 1) {
+                batch.push(base_instance.build(part_index as i32, 0, 0));
+            }
+        }
+        PrimitiveKind::AngleGradient => {
+            let kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::AngleGradient,
+            );
+            let key = BatchKey::new(kind, blend_mode, no_textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            batch.push(base_instance.build(0, 0, 0));
+        }
+        PrimitiveKind::RadialGradient => {
+            let kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::RadialGradient,
+            );
+            let key = BatchKey::new(kind, blend_mode, no_textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            batch.push(base_instance.build(0, 0, 0));
+        }
+        PrimitiveKind::YuvImage => {
+            let mut textures = BatchTextures::no_texture();
+            let mut uv_rect_addresses = [0; 3];
+            let image_yuv_cpu =
+                &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
 
-                        let batch = batch_list.get_suitable_batch(edge_key, item_bounding_rect);
-                        for border_segment in 0 .. 4 {
-                            batch.push(base_instance.build(border_segment, 0, 0));
+            //yuv channel
+            let channel_count = image_yuv_cpu.format.get_plane_num();
+            debug_assert!(channel_count <= 3);
+            for channel in 0 .. channel_count {
+                let image_key = image_yuv_cpu.yuv_key[channel];
+
+                let (texture, address) = resolve_image(
+                    image_key,
+                    image_yuv_cpu.image_rendering,
+                    None,
+                    ctx.resource_cache,
+                    gpu_cache,
+                    deferred_resolves,
+                );
+
+                if texture == SourceTexture::Invalid {
+                    warn!("Warnings: skip a PrimitiveKind::YuvImage at {:?}.\n", item_bounding_rect);
+                    return;
+                }
+
+                textures.colors[channel] = texture;
+                uv_rect_addresses[channel] = address.as_int(gpu_cache);
+            }
+
+            let get_buffer_kind = |texture: SourceTexture| {
+                match texture {
+                    SourceTexture::External(ext_image) => {
+                        match ext_image.image_type {
+                            ExternalImageType::Texture2DHandle => {
+                                ImageBufferKind::Texture2D
+                            }
+                            ExternalImageType::Texture2DArrayHandle => {
+                                ImageBufferKind::Texture2DArray
+                            }
+                            ExternalImageType::TextureRectHandle => {
+                                ImageBufferKind::TextureRect
+                            }
+                            ExternalImageType::TextureExternalHandle => {
+                                ImageBufferKind::TextureExternal
+                            }
+                            ExternalImageType::ExternalBuffer => {
+                                // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
+                                // It should go through the non-external case.
+                                panic!("Unexpected non-texture handle type");
+                            }
                         }
                     }
-                    PrimitiveKind::Rectangle => {
-                        let needs_clipping = prim_metadata.clip_task_id.is_some();
-                        let kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::Rectangle(needs_clipping),
-                        );
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(base_instance.build(0, 0, 0));
-                    }
-                    PrimitiveKind::Line => {
-                        let kind =
-                            BatchKind::Transformable(transform_kind, TransformBatchKind::Line);
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(base_instance.build(0, 0, 0));
-                    }
-                    PrimitiveKind::Image => {
-                        let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
+                    _ => ImageBufferKind::Texture2DArray,
+                }
+            };
 
-                        let (color_texture_id, uv_address) = resolve_image(
-                            image_cpu.image_key,
-                            image_cpu.image_rendering,
-                            image_cpu.tile_offset,
-                            ctx.resource_cache,
-                            gpu_cache,
-                            deferred_resolves,
-                        );
+            // All yuv textures should be the same type.
+            let buffer_kind = get_buffer_kind(textures.colors[0]);
+            assert!(
+                textures.colors[1 .. image_yuv_cpu.format.get_plane_num()]
+                    .iter()
+                    .all(|&tid| buffer_kind == get_buffer_kind(tid))
+            );
 
-                        if color_texture_id == SourceTexture::Invalid {
-                            warn!("Warnings: skip a PrimitiveKind::Image at {:?}.\n", item_bounding_rect);
-                            return;
-                        }
+            let kind = BatchKind::Transformable(
+                transform_kind,
+                TransformBatchKind::YuvImage(
+                    buffer_kind,
+                    image_yuv_cpu.format,
+                    image_yuv_cpu.color_space,
+                ),
+            );
+            let key = BatchKey::new(kind, blend_mode, textures);
+            let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
 
-                        let batch_kind = match color_texture_id {
-                            SourceTexture::External(ext_image) => {
-                                match ext_image.image_type {
-                                    ExternalImageType::Texture2DHandle => {
-                                        TransformBatchKind::Image(ImageBufferKind::Texture2D)
-                                    }
-                                    ExternalImageType::Texture2DArrayHandle => {
-                                        TransformBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                    }
-                                    ExternalImageType::TextureRectHandle => {
-                                        TransformBatchKind::Image(ImageBufferKind::TextureRect)
-                                    }
-                                    ExternalImageType::TextureExternalHandle => {
-                                        TransformBatchKind::Image(ImageBufferKind::TextureExternal)
-                                    }
-                                    ExternalImageType::ExternalBuffer => {
-                                        // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
-                                        // It should go through the non-external case.
-                                        panic!(
-                                            "Non-texture handle type should be handled in other way"
-                                        );
-                                    }
-                                }
-                            }
-                            _ => TransformBatchKind::Image(ImageBufferKind::Texture2DArray),
-                        };
-
-                        let textures = BatchTextures {
-                            colors: [
-                                color_texture_id,
-                                SourceTexture::Invalid,
-                                SourceTexture::Invalid,
-                            ],
-                        };
+            batch.push(base_instance.build(
+                uv_rect_addresses[0],
+                uv_rect_addresses[1],
+                uv_rect_addresses[2],
+            ));
+        }
+    }
+}
 
-                        let key = BatchKey::new(
-                            BatchKind::Transformable(transform_kind, batch_kind),
-                            blend_mode,
-                            textures,
-                        );
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
-                    }
-                    PrimitiveKind::TextRun => {
-                        let text_cpu =
-                            &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-
-                        let font = text_cpu.get_font(ctx.device_pixel_ratio);
+impl PicturePrimitive {
+    fn add_to_batch(
+        &self,
+        task_id: RenderTaskId,
+        ctx: &RenderTargetContext,
+        gpu_cache: &mut GpuCache,
+        render_tasks: &RenderTaskTree,
+        deferred_resolves: &mut Vec<DeferredResolve>,
+        batch_list: &mut BatchList,
+        glyph_fetch_buffer: &mut Vec<GlyphFetchResult>,
+    ) {
+        let task_address = render_tasks.get_task_address(task_id);
 
-                        ctx.resource_cache.fetch_glyphs(
-                            font,
-                            &text_cpu.glyph_keys,
-                            glyph_fetch_buffer,
-                            gpu_cache,
-                            |texture_id, glyph_format, glyphs| {
-                                debug_assert_ne!(texture_id, SourceTexture::Invalid);
+        // Even though most of the time a splitter isn't used or needed,
+        // they are cheap to construct so we will always pass one down.
+        let mut splitter = BspSplitter::new();
 
-                                let textures = BatchTextures {
-                                    colors: [
-                                        texture_id,
-                                        SourceTexture::Invalid,
-                                        SourceTexture::Invalid,
-                                    ],
-                                };
-
-                                let kind = BatchKind::Transformable(
-                                    transform_kind,
-                                    TransformBatchKind::TextRun(glyph_format),
-                                );
-
-                                let key = BatchKey::new(kind, blend_mode, textures);
-                                let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+        // Add each run in this picture to the batch.
+        for run in &self.runs {
+            let clip_node = &ctx.clip_scroll_tree.nodes[&run.clip_and_scroll.clip_node_id()];
+            let clip_id = clip_node.node_data_index;
 
-                                for glyph in glyphs {
-                                    batch.push(base_instance.build(
-                                        glyph.index_in_text_run,
-                                        glyph.uv_rect_address.as_int(),
-                                        0,
-                                    ));
-                                }
-                            },
-                        );
-                    }
-                    PrimitiveKind::Picture => {
-                        let picture =
-                            &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
-                        let cache_task_id = picture.render_task_id.expect("no render task!");
-                        let cache_task_address = render_tasks.get_task_address(cache_task_id);
-                        let textures = BatchTextures::render_target_cache();
-                        let kind = BatchKind::Brush(
-                            BrushBatchKind::Image(picture.target_kind()),
-                        );
-                        let key = BatchKey::new(kind, blend_mode, textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        let image_kind = match picture.kind {
-                            PictureKind::TextShadow { .. } => {
-                                BrushImageKind::Simple
-                            }
-                            PictureKind::BoxShadow { radii_kind, .. } => {
-                                match radii_kind {
-                                    BorderRadiusKind::Uniform => {
-                                        BrushImageKind::Mirror
-                                    }
-                                    BorderRadiusKind::NonUniform => {
-                                        BrushImageKind::NinePatch
-                                    }
-                                }
-                            }
-                        };
-                        let instance = BrushInstance {
-                            picture_address: task_address,
-                            prim_address: prim_cache_address,
-                            clip_id,
-                            scroll_id,
-                            clip_task_address,
-                            z,
-                            flags: 0,
-                            user_data0: cache_task_address.0 as i32,
-                            user_data1: image_kind as i32,
-                        };
-                        batch.push(PrimitiveInstance::from(instance));
-                    }
-                    PrimitiveKind::AlignedGradient => {
-                        let gradient_cpu =
-                            &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
-                        let kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::AlignedGradient,
-                        );
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        for part_index in 0 .. (gradient_cpu.stops_count - 1) {
-                            batch.push(base_instance.build(part_index as i32, 0, 0));
-                        }
-                    }
-                    PrimitiveKind::AngleGradient => {
-                        let kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::AngleGradient,
-                        );
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(base_instance.build(0, 0, 0));
-                    }
-                    PrimitiveKind::RadialGradient => {
-                        let kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::RadialGradient,
-                        );
-                        let key = BatchKey::new(kind, blend_mode, no_textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
-                        batch.push(base_instance.build(0, 0, 0));
-                    }
-                    PrimitiveKind::YuvImage => {
-                        let mut textures = BatchTextures::no_texture();
-                        let mut uv_rect_addresses = [0; 3];
-                        let image_yuv_cpu =
-                            &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
+            let scroll_node = &ctx.clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
+            let scroll_id = scroll_node.node_data_index;
 
-                        //yuv channel
-                        let channel_count = image_yuv_cpu.format.get_plane_num();
-                        debug_assert!(channel_count <= 3);
-                        for channel in 0 .. channel_count {
-                            let image_key = image_yuv_cpu.yuv_key[channel];
+            run.add_to_batch(
+                clip_id,
+                scroll_id,
+                batch_list,
+                ctx,
+                gpu_cache,
+                render_tasks,
+                task_id,
+                task_address,
+                deferred_resolves,
+                glyph_fetch_buffer,
+                &mut splitter,
+            );
+        }
 
-                            let (texture, address) = resolve_image(
-                                image_key,
-                                image_yuv_cpu.image_rendering,
-                                None,
-                                ctx.resource_cache,
-                                gpu_cache,
-                                deferred_resolves,
-                            );
-
-                            if texture == SourceTexture::Invalid {
-                                warn!("Warnings: skip a PrimitiveKind::YuvImage at {:?}.\n", item_bounding_rect);
-                                return;
-                            }
-
-                            textures.colors[channel] = texture;
-                            uv_rect_addresses[channel] = address.as_int(gpu_cache);
-                        }
-
-                        let get_buffer_kind = |texture: SourceTexture| {
-                            match texture {
-                                SourceTexture::External(ext_image) => {
-                                    match ext_image.image_type {
-                                        ExternalImageType::Texture2DHandle => {
-                                            ImageBufferKind::Texture2D
-                                        }
-                                        ExternalImageType::Texture2DArrayHandle => {
-                                            ImageBufferKind::Texture2DArray
-                                        }
-                                        ExternalImageType::TextureRectHandle => {
-                                            ImageBufferKind::TextureRect
-                                        }
-                                        ExternalImageType::TextureExternalHandle => {
-                                            ImageBufferKind::TextureExternal
-                                        }
-                                        ExternalImageType::ExternalBuffer => {
-                                            // The ExternalImageType::ExternalBuffer should be handled by resource_cache.
-                                            // It should go through the non-external case.
-                                            panic!("Unexpected non-texture handle type");
-                                        }
-                                    }
-                                }
-                                _ => ImageBufferKind::Texture2DArray,
-                            }
-                        };
+        // Flush the accumulated plane splits onto the task tree.
+        // 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 prim_index = PrimitiveIndex(poly.anchor);
+            debug!("process sorted poly {:?} {:?}", prim_index, poly.points);
+            let pp = &poly.points;
+            let gpu_blocks = [
+                [pp[0].x as f32, pp[0].y as f32, pp[0].z as f32, pp[1].x as f32].into(),
+                [pp[1].y as f32, pp[1].z as f32, pp[2].x as f32, pp[2].y as f32].into(),
+                [pp[2].z as f32, pp[3].x as f32, pp[3].y as f32, pp[3].z as f32].into(),
+            ];
+            let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
+            let key = BatchKey::new(
+                BatchKind::SplitComposite,
+                BlendMode::PremultipliedAlpha,
+                BatchTextures::no_texture(),
+            );
+            let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
+            let pic = &ctx.prim_store.cpu_pictures[pic_metadata.cpu_prim_index.0];
+            let batch = batch_list.get_suitable_batch(key, pic_metadata.screen_rect.as_ref().expect("bug"));
+            let source_task_address = render_tasks.get_task_address(pic.render_task_id.expect("bug"));
+            let gpu_address = gpu_handle.as_int(gpu_cache);
 
-                        // All yuv textures should be the same type.
-                        let buffer_kind = get_buffer_kind(textures.colors[0]);
-                        assert!(
-                            textures.colors[1 .. image_yuv_cpu.format.get_plane_num()]
-                                .iter()
-                                .all(|&tid| buffer_kind == get_buffer_kind(tid))
-                        );
-
-                        let kind = BatchKind::Transformable(
-                            transform_kind,
-                            TransformBatchKind::YuvImage(
-                                buffer_kind,
-                                image_yuv_cpu.format,
-                                image_yuv_cpu.color_space,
-                            ),
-                        );
-                        let key = BatchKey::new(kind, blend_mode, textures);
-                        let batch = batch_list.get_suitable_batch(key, item_bounding_rect);
+            let instance = CompositePrimitiveInstance::new(
+                task_address,
+                source_task_address,
+                RenderTaskAddress(0),
+                gpu_address,
+                0,
+                prim_index.0 as i32,
+                0,
+                0,
+            );
 
-                        batch.push(base_instance.build(
-                            uv_rect_addresses[0],
-                            uv_rect_addresses[1],
-                            uv_rect_addresses[2],
-                        ));
-                    }
-                }
-            }
-            AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_handle, z) => {
-                let key = BatchKey::new(
-                    BatchKind::SplitComposite,
-                    BlendMode::PremultipliedAlpha,
-                    BatchTextures::no_texture(),
-                );
-                let stacking_context = &ctx.stacking_context_store[sc_index.0];
-                let batch = batch_list.get_suitable_batch(key, &stacking_context.screen_bounds);
-                let source_task_address = render_tasks.get_task_address(task_id);
-                let gpu_address = gpu_handle.as_int(gpu_cache);
-
-                let instance = CompositePrimitiveInstance::new(
-                    task_address,
-                    source_task_address,
-                    RenderTaskAddress(0),
-                    gpu_address,
-                    0,
-                    z,
-                    0,
-                    0,
-                );
-
-                batch.push(PrimitiveInstance::from(instance));
-            }
+            batch.push(PrimitiveInstance::from(instance));
         }
     }
 }
 
 impl AlphaBatcher {
     fn new() -> AlphaBatcher {
         AlphaBatcher {
             tasks: Vec::new(),
@@ -806,30 +990,26 @@ impl AlphaBatcher {
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         for task_id in &self.tasks {
             let task_id = *task_id;
             let task = render_tasks.get(task_id).as_alpha_batch();
-            let task_address = render_tasks.get_task_address(task_id);
-
-            for item in &task.items {
-                item.add_to_batch(
-                    &mut self.batch_list,
-                    ctx,
-                    gpu_cache,
-                    render_tasks,
-                    task_id,
-                    task_address,
-                    deferred_resolves,
-                    &mut self.glyph_fetch_buffer,
-                );
-            }
+            let pic = &ctx.prim_store.cpu_pictures[ctx.prim_store.cpu_metadata[task.prim_index.0].cpu_prim_index.0];
+            pic.add_to_batch(
+                task_id,
+                ctx,
+                gpu_cache,
+                render_tasks,
+                deferred_resolves,
+                &mut self.batch_list,
+                &mut self.glyph_fetch_buffer
+            );
         }
 
         self.batch_list.finalize();
     }
 
     pub fn is_empty(&self) -> bool {
         self.batch_list.opaque_batch_list.batches.is_empty() &&
             self.batch_list.alpha_batch_list.batches.is_empty()
@@ -957,20 +1137,20 @@ impl ClipBatcher {
                 }
             }
         }
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub device_pixel_ratio: f32,
-    pub stacking_context_store: &'a [StackingContext],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
     pub node_data: &'a [ClipScrollNodeData],
+    pub clip_scroll_tree: &'a ClipScrollTree,
 }
 
 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
     // cache allocator requires.
     allocator: GuillotineAllocator,
@@ -1226,17 +1406,17 @@ impl RenderTarget for ColorRenderTarget 
             RenderTaskKind::Picture(ref task_info) => {
                 let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Picture => {
                         let prim = &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
 
                         let task_index = render_tasks.get_task_address(task_id);
 
-                        for run in &prim.prim_runs {
+                        for run in &prim.runs {
                             for i in 0 .. run.count {
                                 let sub_prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
                                 let sub_metadata = ctx.prim_store.get_metadata(sub_prim_index);
                                 let sub_prim_address =
                                     gpu_cache.get_address(&sub_metadata.gpu_location);
                                 let instance = SimplePrimitiveInstance::new(
                                     sub_prim_address,
@@ -1395,17 +1575,17 @@ impl RenderTarget for AlphaRenderTarget 
                 let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Picture => {
                         let prim = &ctx.prim_store.cpu_pictures[prim_metadata.cpu_prim_index.0];
 
                         let task_index = render_tasks.get_task_address(task_id);
 
-                        for run in &prim.prim_runs {
+                        for run in &prim.runs {
                             for i in 0 .. run.count {
                                 let sub_prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
                                 let sub_metadata = ctx.prim_store.get_metadata(sub_prim_index);
                                 let sub_prim_address =
                                     gpu_cache.get_address(&sub_metadata.gpu_location);
 
                                 match sub_metadata.prim_kind {
@@ -1725,121 +1905,16 @@ impl OpaquePrimitiveBatch {
     fn new(key: BatchKey) -> OpaquePrimitiveBatch {
         OpaquePrimitiveBatch {
             key,
             instances: Vec::new(),
         }
     }
 }
 
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub struct StackingContextIndex(pub usize);
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
-pub enum ContextIsolation {
-    /// No isolation - the content is mixed up with everything else.
-    None,
-    /// Items are isolated and drawn into a separate render target.
-    /// Child contexts are exposed.
-    Items,
-    /// All the content inside is isolated and drawn into a separate target.
-    Full,
-}
-
-#[derive(Debug)]
-pub struct StackingContext {
-    pub pipeline_id: PipelineId,
-
-    /// Offset in the parent reference frame to the origin of this stacking
-    /// context's coordinate system.
-    pub reference_frame_offset: LayerVector2D,
-
-    /// The `ClipId` of the owning reference frame.
-    pub reference_frame_id: ClipId,
-
-    /// Screen space bounding rectangle for this stacking context,
-    /// calculated based on the size and position of all its children.
-    pub screen_bounds: DeviceIntRect,
-
-    /// Local bounding rectangle of this stacking context,
-    /// computed as the union of all contained items that are not
-    /// `ContextIsolation::Items` on their own
-    pub isolated_items_bounds: LayerRect,
-
-    pub composite_ops: CompositeOps,
-
-    /// Type of the isolation of the content.
-    pub isolation: ContextIsolation,
-
-    /// Set for the root stacking context of a display list or an iframe. Used for determining
-    /// when to isolate a mix-blend-mode composite.
-    pub is_page_root: bool,
-
-    /// Set to true if this is the root stacking context for a pipeline.
-    pub is_pipeline_root: bool,
-
-    /// Whether or not this stacking context has any visible components, calculated
-    /// based on the size and position of all children and how they are clipped.
-    pub is_visible: bool,
-
-    /// Current stacking context visibility of backface.
-    pub is_backface_visible: bool,
-
-    /// Allow subpixel AA for text runs on this stacking context.
-    /// This is a temporary hack while we don't support subpixel AA
-    /// on transparent stacking contexts.
-    pub allow_subpixel_aa: bool,
-
-    /// Indicate that if any pritimive contained in this stacking context.
-    pub has_any_primitive: bool,
-
-    /// Union of all stacking context bounds of all children.
-    pub children_sc_bounds: LayerRect,
-}
-
-impl StackingContext {
-    pub fn new(
-        pipeline_id: PipelineId,
-        reference_frame_offset: LayerVector2D,
-        is_page_root: bool,
-        is_pipeline_root: bool,
-        reference_frame_id: ClipId,
-        transform_style: TransformStyle,
-        composite_ops: CompositeOps,
-        is_backface_visible: bool,
-    ) -> StackingContext {
-        let isolation = match transform_style {
-            TransformStyle::Flat => ContextIsolation::None,
-            TransformStyle::Preserve3D => ContextIsolation::Items,
-        };
-        let allow_subpixel_aa = composite_ops.count() == 0 &&
-                                isolation == ContextIsolation::None;
-        StackingContext {
-            pipeline_id,
-            reference_frame_offset,
-            reference_frame_id,
-            screen_bounds: DeviceIntRect::zero(),
-            isolated_items_bounds: LayerRect::zero(),
-            composite_ops,
-            isolation,
-            is_page_root,
-            is_pipeline_root,
-            is_visible: false,
-            is_backface_visible,
-            allow_subpixel_aa,
-            has_any_primitive: false,
-            children_sc_bounds: LayerRect::zero(),
-        }
-    }
-
-    pub fn can_contribute_to_scene(&self) -> bool {
-        !self.composite_ops.will_make_invisible()
-    }
-}
-
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<FilterOp>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
@@ -1850,25 +1925,16 @@ impl CompositeOps {
             filters,
             mix_blend_mode: mix_blend_mode,
         }
     }
 
     pub fn count(&self) -> usize {
         self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
     }
-
-    pub fn will_make_invisible(&self) -> bool {
-        for op in &self.filters {
-            if op == &FilterOp::Opacity(PropertyBinding::Value(0.0)) {
-                return true;
-            }
-        }
-        false
-    }
 }
 
 /// A rendering-oriented representation of frame::Frame built by the render backend
 /// and presented to the renderer.
 pub struct Frame {
     pub window_size: DeviceUintSize,
     pub background_color: Option<ColorF>,
     pub device_pixel_ratio: f32,
@@ -1951,8 +2017,36 @@ impl BlurTask {
                 instances.push(BlurInstance {
                     region: *region,
                     ..instance
                 });
             }
         }
     }
 }
+
+/// Construct a polygon from stacking context boundaries.
+/// `anchor` here is an index that's going to be preserved in all the
+/// splits of the polygon.
+fn make_polygon(
+    rect: LayerRect,
+    transform: &LayerToWorldTransform,
+    anchor: usize,
+) -> Polygon<f64, WorldPixel> {
+    let mat = TypedTransform3D::row_major(
+        transform.m11 as f64,
+        transform.m12 as f64,
+        transform.m13 as f64,
+        transform.m14 as f64,
+        transform.m21 as f64,
+        transform.m22 as f64,
+        transform.m23 as f64,
+        transform.m24 as f64,
+        transform.m31 as f64,
+        transform.m32 as f64,
+        transform.m33 as f64,
+        transform.m34 as f64,
+        transform.m41 as f64,
+        transform.m42 as f64,
+        transform.m43 as f64,
+        transform.m44 as f64);
+    Polygon::from_transformed_rect(rect.cast().unwrap(), mat, anchor)
+}
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -466,17 +466,17 @@ pub enum MixBlendMode {
 #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
     Blur(f32),
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
-    Opacity(PropertyBinding<f32>),
+    Opacity(PropertyBinding<f32>, f32),
     Saturate(f32),
     Sepia(f32),
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
     pub pipeline_id: PipelineId,
 }
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1239,30 +1239,30 @@ pub extern "C" fn wr_dp_push_stacking_co
     let mut filters : Vec<FilterOp> = c_filters.iter().map(|c_filter| {
         match c_filter.filter_type {
             WrFilterOpType::Blur => FilterOp::Blur(c_filter.argument),
             WrFilterOpType::Brightness => FilterOp::Brightness(c_filter.argument),
             WrFilterOpType::Contrast => FilterOp::Contrast(c_filter.argument),
             WrFilterOpType::Grayscale => FilterOp::Grayscale(c_filter.argument),
             WrFilterOpType::HueRotate => FilterOp::HueRotate(c_filter.argument),
             WrFilterOpType::Invert => FilterOp::Invert(c_filter.argument),
-            WrFilterOpType::Opacity => FilterOp::Opacity(PropertyBinding::Value(c_filter.argument)),
+            WrFilterOpType::Opacity => FilterOp::Opacity(PropertyBinding::Value(c_filter.argument), c_filter.argument),
             WrFilterOpType::Saturate => FilterOp::Saturate(c_filter.argument),
             WrFilterOpType::Sepia => FilterOp::Sepia(c_filter.argument),
         }
     }).collect();
 
     let opacity = unsafe { opacity.as_ref() };
     if let Some(opacity) = opacity {
         if *opacity < 1.0 {
-            filters.push(FilterOp::Opacity(PropertyBinding::Value(*opacity)));
+            filters.push(FilterOp::Opacity(PropertyBinding::Value(*opacity), *opacity));
         }
     } else {
         if animation_id > 0 {
-            filters.push(FilterOp::Opacity(PropertyBinding::Binding(PropertyBindingKey::new(animation_id))));
+            filters.push(FilterOp::Opacity(PropertyBinding::Binding(PropertyBindingKey::new(animation_id)), 1.0));
         }
     }
 
     let transform = unsafe { transform.as_ref() };
     let transform_binding = match animation_id {
         0 => match transform {
             Some(transform) => Some(PropertyBinding::Value(transform.clone())),
             None => None,