Bug 1501001 - Update webrender to 838ce479e6ef8eed44d68e5d283649d0963152b6. r=kats
authorWR Updater Bot <graphics-team@mozilla.staktrace.com>
Tue, 23 Oct 2018 15:07:22 +0000
changeset 490934 999d001b268d988a86776befda2eadcae93fbfe3
parent 490933 6447311d0412b8c648376318e8791fcd72bc2787
child 490935 fbbe71ee84680831bc452626545c58b57763886a
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerskats
bugs1501001
milestone65.0a1
Bug 1501001 - Update webrender to 838ce479e6ef8eed44d68e5d283649d0963152b6. r=kats Differential Revision: https://phabricator.services.mozilla.com/D9537
gfx/webrender/src/batch.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,40 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize, LineStyle};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering};
-use api::{YuvColorSpace, YuvFormat, WorldPixel, WorldRect, ColorDepth};
+use api::{YuvColorSpace, YuvFormat, WorldRect, ColorDepth};
 use clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
-use euclid::vec3;
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
 use gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders};
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
-use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
-use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
+use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
 use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BrushSegment, BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use tiling::{RenderTargetContext};
-use util::{MatrixHelpers, TransformedRectKind};
+use util::{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(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -475,130 +473,29 @@ impl AlphaBatchBuilder {
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
-        // 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();
-
         // Add each run in this picture to the batch.
-        for (plane_split_anchor, prim_instance) in pic.prim_instances.iter().enumerate() {
+        for prim_instance in &pic.prim_instances {
             self.add_prim_to_batch(
                 prim_instance,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
-                &mut splitter,
                 prim_headers,
                 transforms,
                 root_spatial_node_index,
-                plane_split_anchor,
-            );
-        }
-
-        // 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_instance = &pic.prim_instances[poly.anchor];
-            let prim_index = prim_instance.prim_index;
-            let prim = &ctx.prim_store.primitives[prim_index.0];
-            if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
-                println!("\t\tsplit polygon {:?}", poly.points);
-            }
-            let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
-            let transform_id = transforms.get_id(
-                prim_instance.spatial_node_index,
-                ROOT_SPATIAL_NODE_INDEX,
-                ctx.clip_scroll_tree,
-            );
-
-            let clip_task_address = prim_instance
-                .clip_task_id
-                .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
-
-            let prim_header = PrimitiveHeader {
-                local_rect: prim.local_rect,
-                local_clip_rect: prim_instance.combined_local_clip_rect,
-                task_address,
-                specific_prim_address: GpuCacheAddress::invalid(),
-                clip_task_address,
-                transform_id,
-            };
-
-            let pic_index = match prim.details {
-                PrimitiveDetails::Brush(ref brush) => {
-                    match brush.kind {
-                        BrushKind::Picture { pic_index, .. } => pic_index,
-                        _ => unreachable!(),
-                    }
-                }
-                PrimitiveDetails::TextRun(..) => {
-                    unreachable!();
-                }
-            };
-            let pic = &ctx.prim_store.pictures[pic_index.0];
-
-            let (uv_rect_address, _) = pic
-                .raster_config
-                .as_ref()
-                .expect("BUG: no raster config")
-                .surface
-                .as_ref()
-                .expect("BUG: no surface")
-                .resolve(
-                    render_tasks,
-                    ctx.resource_cache,
-                    gpu_cache,
-                );
-
-            let prim_header_index = prim_headers.push(&prim_header, [
-                uv_rect_address.as_int(),
-                0,
-                0,
-            ]);
-
-            let mut local_points = [
-                transform.transform_point3d(&poly.points[0].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[1].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[2].cast()).unwrap(),
-                transform.transform_point3d(&poly.points[3].cast()).unwrap(),
-            ];
-            let gpu_blocks = [
-                [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
-                [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
-            ];
-
-            let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
-            let key = BatchKey::new(
-                BatchKind::SplitComposite,
-                BlendMode::PremultipliedAlpha,
-                BatchTextures::no_texture(),
-            );
-
-            let gpu_address = gpu_cache.get_address(&gpu_handle);
-
-            let instance = SplitCompositeInstance::new(
-                prim_header_index,
-                gpu_address,
-                prim_headers.z_generator.next(),
-            );
-
-            self.batch_list.push_single_instance(
-                key,
-                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
-                prim_instance.prim_index,
-                PrimitiveInstanceData::from(instance),
             );
         }
     }
 
     // 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.
@@ -606,21 +503,19 @@ impl AlphaBatchBuilder {
         &mut self,
         prim_instance: &PrimitiveInstance,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        splitter: &mut BspSplitter<f64, WorldPixel>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
-        plane_split_anchor: usize,
     ) {
         let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
 
         if prim_instance.clipped_world_rect.is_none() {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
@@ -664,17 +559,18 @@ impl AlphaBatchBuilder {
         let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
         let specified_blend_mode = prim.get_blend_mode();
 
         let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
             prim_instance.clip_task_id.is_some() ||
-            transform_kind == TransformedRectKind::Complex {
+            transform_kind == TransformedRectKind::Complex
+        {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
         let prim_header = PrimitiveHeader {
             local_rect: prim.local_rect,
             local_clip_rect: prim_instance.combined_local_clip_rect,
@@ -687,70 +583,96 @@ impl AlphaBatchBuilder {
         if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
             println!("\ttask target {:?}", self.target_rect);
             println!("\t{:?}", prim_header);
         }
 
         match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
                 match brush.kind {
-                    BrushKind::Picture { pic_index, .. } => {
+                    BrushKind::Picture { pic_index } => {
                         let picture = &ctx.prim_store.pictures[pic_index.0];
+                        match picture.context_3d {
+                            // Convert all children of the 3D hierarchy root into batches.
+                            Picture3DContext::In { root_data: Some(ref list), .. } => {
+                                let z = prim_headers.z_generator.next();
+                                for child in list {
+                                    let prim_instance = &picture.prim_instances[child.anchor];
+                                    let pic_primitive = &ctx.prim_store.primitives[prim_instance.prim_index.0];
+
+                                    let clip_task_address = prim_instance
+                                        .clip_task_id
+                                        .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
-                        // 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 picture.is_in_3d_context {
-                            // Push into parent plane splitter.
-                            debug_assert!(picture.raster_config.is_some());
-                            let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
+                                    let prim_header = PrimitiveHeader {
+                                        local_rect: pic_primitive.local_rect,
+                                        local_clip_rect: prim_instance.combined_local_clip_rect,
+                                        task_address,
+                                        specific_prim_address: GpuCacheAddress::invalid(),
+                                        clip_task_address,
+                                        transform_id: child.transform_id,
+                                    };
 
-                            // Apply the local clip rect here, before splitting. This is
-                            // because the local clip rect can't be applied in the vertex
-                            // shader for split composites, since we are drawing polygons
-                            // rather that rectangles. The interpolation still works correctly
-                            // since we determine the UVs by doing a bilerp with a factor
-                            // from the original local rect.
-                            let local_rect = prim
-                                .local_rect
-                                .intersection(&prim_instance.combined_local_clip_rect);
+						            let pic_index = match pic_primitive.details {
+						            	PrimitiveDetails::Brush(ref brush) => {
+						                    match brush.kind {
+						                        BrushKind::Picture { pic_index, .. } => pic_index,
+						                        _ => unreachable!(),
+						                    }
+						                }
+						                PrimitiveDetails::TextRun(..) => {
+						                    unreachable!();
+						                }
+						            };
+						            let pic = &ctx.prim_store.pictures[pic_index.0];
 
-                            if let Some(local_rect) = local_rect {
-                                match transform.transform_kind() {
-                                    TransformedRectKind::AxisAligned => {
-                                        let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
-                                        let polygon = Polygon::from_transformed_rect_with_inverse(
-                                            local_rect.cast(),
-                                            &transform.cast(),
-                                            &inv_transform.cast(),
-                                            plane_split_anchor,
-                                        ).unwrap();
-                                        splitter.add(polygon);
-                                    }
-                                    TransformedRectKind::Complex => {
-                                        let mut clipper = Clipper::new();
-                                        let matrix = transform.cast();
-                                        let results = clipper.clip_transformed(
-                                            Polygon::from_rect(
-                                                local_rect.cast(),
-                                                plane_split_anchor,
-                                            ),
-                                            &matrix,
-                                            Some(bounding_rect.to_f64()),
+                                    let (uv_rect_address, _) = pic
+                                        .raster_config
+                                        .as_ref()
+                                        .expect("BUG: no raster config")
+                                        .surface
+                                        .as_ref()
+                                        .expect("BUG: no surface")
+                                        .resolve(
+                                            render_tasks,
+                                            ctx.resource_cache,
+                                            gpu_cache,
                                         );
-                                        if let Ok(results) = results {
-                                            for poly in results {
-                                                splitter.add(poly);
-                                            }
-                                        }
-                                    }
+
+                                    let prim_header_index = prim_headers.push(&prim_header, [
+                                        uv_rect_address.as_int(),
+                                        0,
+                                        0,
+                                    ]);
+
+                                    let key = BatchKey::new(
+                                        BatchKind::SplitComposite,
+                                        BlendMode::PremultipliedAlpha,
+                                        BatchTextures::no_texture(),
+                                    );
+
+                                    let instance = SplitCompositeInstance::new(
+                                        prim_header_index,
+                                        child.gpu_address,
+                                        z,
+                                    );
+
+                                    self.batch_list.push_single_instance(
+                                        key,
+                                        &prim_instance.clipped_world_rect.as_ref().expect("bug"),
+                                        prim_instance.prim_index,
+                                        PrimitiveInstanceData::from(instance),
+                                    );
                                 }
                             }
-
-                            return;
+                            // Ignore the 3D pictures that are not in the root of preserve-3D
+                            // hierarchy, since we process them with the root.
+                            Picture3DContext::In { root_data: None, .. } => return,
+                            // Proceed for non-3D pictures.
+                            Picture3DContext::Out => ()
                         }
 
                         match picture.raster_config {
                             Some(ref raster_config) => {
                                 let surface = raster_config.surface
                                                            .as_ref()
                                                            .expect("bug: surface must be allocated by now");
                                 match raster_config.composite_mode {
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,29 +9,29 @@ use api::{DisplayItemRef, ExtendMode, Ex
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
-use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
+use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
-use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
+use picture::{Picture3DContext, PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
-use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveStore};
+use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive, PictureIndex};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{StickyFrameInfo};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
@@ -818,21 +818,17 @@ impl<'a> DisplayListFlattener<'a> {
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
     ) -> PrimitiveInstance {
-        let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
-
-        let prim_key = PrimitiveKey::new(
-            info.is_backface_visible && stacking_context.is_backface_visible,
-        );
+        let prim_key = PrimitiveKey::new(info.is_backface_visible);
 
         let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
 
         let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
             container,
         );
@@ -873,17 +869,17 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         prim_instance: PrimitiveInstance,
     ) {
         // Add primitive to the top-most stacking context on the stack.
         if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
             println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
         let stacking_context = self.sc_stack.last_mut().unwrap();
-        stacking_context.normal_primitives.push(prim_instance);
+        stacking_context.primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
@@ -948,293 +944,294 @@ impl<'a> DisplayListFlattener<'a> {
             Some(pipeline_id)
         } else {
             None
         };
 
         // 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,
+        let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() {
+            Some(sc) => {
+                // Cut the sequence of flat children before starting a child stacking context,
+                // so that the relative order between them and our current SC is preserved.
+                let extra_instance = sc.cut_flat_item_sequence(
+                    &mut self.picture_id_generator,
+                    &mut self.prim_store,
+                );
+                (sc.is_3d(), extra_instance)
+            },
+            None => (false, None),
         };
 
+        if let Some(instance) = extra_3d_instance {
+            self.add_primitive_instance_to_3d_root(instance);
+        }
+
         // If this is preserve-3d *or* the parent is, then this stacking
         // context is participating in the 3d rendering context. In that
         // case, hoist the picture up to the 3d rendering context
         // container, so that it's rendered as a sibling with other
         // elements in this context.
         let participating_in_3d_context =
-            composite_ops.count() == 0 &&
-            (parent_transform_style == TransformStyle::Preserve3D ||
-             transform_style == TransformStyle::Preserve3D);
+            composite_ops.is_empty() &&
+            (parent_is_3d || transform_style == TransformStyle::Preserve3D);
 
-        // If this is participating in a 3d context *and* the
-        // parent was not a 3d context, then this must be the
-        // element that establishes a new 3d context.
-        let establishes_3d_context =
-            participating_in_3d_context &&
-            parent_transform_style == TransformStyle::Flat;
+        let context_3d = if participating_in_3d_context {
+            // Find the spatial node index of the containing block, which
+            // defines the context of backface-visibility.
+            let ancestor_context = self.sc_stack
+                .iter()
+                .rfind(|sc| !sc.is_3d());
+            Picture3DContext::In {
+                root_data: if parent_is_3d {
+                    None
+                } else {
+                    Some(Vec::new())
+                },
+                ancestor_index: match ancestor_context {
+                    Some(sc) => sc.spatial_node_index,
+                    None => ROOT_SPATIAL_NODE_INDEX,
+                },
+            }
+        } else {
+            Picture3DContext::Out
+        };
 
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
         let should_isolate = clipping_node.is_some();
 
-        // preserve-3d's semantics are to hoist all your children to be your siblings
-        // when doing backface-visibility checking, so we need to grab the backface-visibility
-        // of the lowest ancestor which *doesn't* preserve-3d, and AND it in with ours.
-        //
-        // No this isn't obvious or clear, it's just what we worked out over a day of testing.
-        // There's probably a bug in here, but I couldn't find it with the examples and tests
-        // at my disposal!
-        let ancestor_is_backface_visible =
-            self.sc_stack
-                .iter()
-                .rfind(|sc| sc.transform_style == TransformStyle::Flat)
-                .map(|sc| sc.is_backface_visible)
-                .unwrap_or(is_backface_visible);
-
-        let is_backface_visible = is_backface_visible && ancestor_is_backface_visible;
+        let prim_key = PrimitiveKey::new(is_backface_visible);
+        let primitive_data_handle = self.resources.prim_interner.intern(&prim_key);
 
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         self.sc_stack.push(FlattenedStackingContext {
-            preserve3d_primitives: Vec::new(),
-            normal_primitives: Vec::new(),
+            primitives: Vec::new(),
             pipeline_id,
-            is_backface_visible,
+            primitive_data_handle,
             requested_raster_space,
             spatial_node_index,
             clip_chain_id,
             frame_output_pipeline_id,
             composite_ops,
             should_isolate,
             transform_style,
-            participating_in_3d_context,
-            establishes_3d_context,
+            context_3d,
         });
     }
 
     pub fn pop_stacking_context(&mut self) {
         let stacking_context = self.sc_stack.pop().unwrap();
 
         // 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 = LayoutRect::max_rect();
 
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = if stacking_context.should_isolate {
-            Some(PictureCompositeMode::Blit)
-        } else {
-            None
-        };
-
-        // Force an intermediate surface if the stacking context
-        // has a clip node. In the future, we may decide during
-        // prepare step to skip the intermediate surface if the
-        // clip node doesn't affect the stacking context rect.
-        if stacking_context.participating_in_3d_context {
+        let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = match stacking_context.context_3d {
             // 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);
-        }
-
-        let prim_key = PrimitiveKey::new(true);
-        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+            Picture3DContext::In { ancestor_index, .. } => (
+                Picture3DContext::In { root_data: None, ancestor_index },
+                Some(PictureCompositeMode::Blit),
+                None,
+            ),
+            Picture3DContext::Out => (
+                Picture3DContext::Out,
+                if stacking_context.should_isolate {
+                    // Add a dummy composite filter if the SC has to be isolated.
+                    Some(PictureCompositeMode::Blit)
+                } else {
+                    // By default, this picture will be collapsed into
+                    // the owning target.
+                    None
+                },
+                stacking_context.frame_output_pipeline_id
+            ),
+        };
 
         // Add picture for this actual stacking context contents to render into.
         let leaf_picture = PicturePrimitive::new_image(
             self.picture_id_generator.next(),
-            composite_mode,
-            stacking_context.participating_in_3d_context,
+            leaf_composite_mode,
+            leaf_context_3d,
             stacking_context.pipeline_id,
-            stacking_context.frame_output_pipeline_id,
+            leaf_output_pipeline_id,
             true,
             stacking_context.requested_raster_space,
-            stacking_context.normal_primitives,
+            stacking_context.primitives,
         );
         let leaf_pic_index = self.prim_store.create_picture(leaf_picture);
 
         // Create a brush primitive that draws this picture.
         let leaf_prim = BrushPrimitive::new_picture(leaf_pic_index);
 
         // Add the brush to the parent picture.
         let leaf_prim_index = self.prim_store.add_primitive(
             &LayoutRect::zero(),
             &max_clip,
             PrimitiveContainer::Brush(leaf_prim),
         );
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
-        let mut current_prim_index = leaf_prim_index;
+
+        if cfg!(debug_assertions) && Some(leaf_prim_index) == self.prim_store.chase_id {
+            println!("\tis a leaf primitive for a stacking context");
+        }
+
         let mut current_pic_index = leaf_pic_index;
+        let mut cur_instance = PrimitiveInstance::new(
+            leaf_prim_index,
+            stacking_context.primitive_data_handle,
+            stacking_context.clip_chain_id,
+            stacking_context.spatial_node_index,
+        );
+
+        // If establishing a 3d context, the `cur_instance` represents
+        // a picture with all the *trailing* immediate children elements.
+        // We append this to the preserve-3D picture set and make a container picture of them.
+        if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
+            prims.push(cur_instance.clone());
+
+            // This is the acttual picture representing our 3D hierarchy root.
+            let container_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                None,
+                Picture3DContext::In {
+                    root_data: Some(Vec::new()),
+                    ancestor_index,
+                },
+                stacking_context.pipeline_id,
+                stacking_context.frame_output_pipeline_id,
+                true,
+                stacking_context.requested_raster_space,
+                prims,
+            );
+
+            current_pic_index = self.prim_store.create_picture(container_picture);
+            let container_prim = BrushPrimitive::new_picture(current_pic_index);
+
+            cur_instance.prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(container_prim),
+            );
+        }
 
         // For each filter, create a new image with that composite mode.
         for filter in &stacking_context.composite_ops.filters {
             let filter = filter.sanitize();
 
             let filter_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::Filter(filter)),
-                false,
+                Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
-                vec![
-                    PrimitiveInstance::new(
-                        current_prim_index,
-                        prim_data_handle,
-                        stacking_context.clip_chain_id,
-                        stacking_context.spatial_node_index,
-                    ),
-                ],
+                vec![cur_instance.clone()],
             );
             let filter_pic_index = self.prim_store.create_picture(filter_picture);
             current_pic_index = filter_pic_index;
 
             let filter_prim = BrushPrimitive::new_picture(filter_pic_index);
 
-            current_prim_index = self.prim_store.add_primitive(
+            cur_instance.prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 PrimitiveContainer::Brush(filter_prim),
             );
 
+            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+                println!("\tis a composite picture for a stacking context with {:?}", filter);
+            }
+
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
         // Same for mix-blend-mode.
         if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
             let blend_picture = PicturePrimitive::new_image(
                 self.picture_id_generator.next(),
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
+                Picture3DContext::Out,
                 stacking_context.pipeline_id,
                 None,
                 true,
                 stacking_context.requested_raster_space,
-                vec![
-                    PrimitiveInstance::new(
-                        current_prim_index,
-                        prim_data_handle,
-                        stacking_context.clip_chain_id,
-                        stacking_context.spatial_node_index,
-                    ),
-                ],
+                vec![cur_instance.clone()],
             );
             let blend_pic_index = self.prim_store.create_picture(blend_picture);
             current_pic_index = blend_pic_index;
 
             let blend_prim = BrushPrimitive::new_picture(blend_pic_index);
 
-            current_prim_index = self.prim_store.add_primitive(
+            cur_instance.prim_index = self.prim_store.add_primitive(
                 &LayoutRect::zero(),
                 &max_clip,
                 PrimitiveContainer::Brush(blend_prim),
             );
-        }
 
-        if stacking_context.establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let mut prims = vec![
-                PrimitiveInstance::new(
-                    current_prim_index,
-                    prim_data_handle,
-                    stacking_context.clip_chain_id,
-                    stacking_context.spatial_node_index,
-                ),
-            ];
-            prims.extend(stacking_context.preserve3d_primitives);
-
-            let container_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                None,
-                false,
-                stacking_context.pipeline_id,
-                None,
-                true,
-                stacking_context.requested_raster_space,
-                prims,
-            );
-            let container_pic_index = self.prim_store.create_picture(container_picture);
-            current_pic_index = container_pic_index;
-
-            let container_prim = BrushPrimitive::new_picture(container_pic_index);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                PrimitiveContainer::Brush(container_prim),
-            );
-        } else {
-            debug_assert!(stacking_context.preserve3d_primitives.is_empty());
+            if cfg!(debug_assertions) && Some(cur_instance.prim_index) == self.prim_store.chase_id {
+                println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode);
+            }
         }
 
-        if self.sc_stack.is_empty() {
-            // This must be the root stacking context
-            self.root_pic_index = current_pic_index;
-            return;
-        }
-
-        let sc_count = self.sc_stack.len();
-        let prim_instance = PrimitiveInstance::new(
-            current_prim_index,
-            prim_data_handle,
-            stacking_context.clip_chain_id,
-            stacking_context.spatial_node_index,
-        );
+        let has_mix_blend_on_secondary_framebuffer =
+            stacking_context.composite_ops.mix_blend_mode.is_some() &&
+            self.sc_stack.len() > 2;
 
-        if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
-            // If we're in a 3D context, we will parent the picture
-            // to the first stacking context we find that is a
-            // 3D rendering context container. This follows the spec
-            // by hoisting these items out into the same 3D context
-            // for plane splitting.
-            let parent_index = self.sc_stack
-                .iter()
-                .rposition(|sc| sc.establishes_3d_context)
-                .unwrap();
-
-            let parent_stacking_context = &mut self.sc_stack[parent_index];
-            parent_stacking_context.preserve3d_primitives.push(prim_instance);
+        // The primitive instance for the remainder of flat children of this SC
+        // if it's a part of 3D hierarchy but not the root of it.
+        let trailing_children_instance = match self.sc_stack.last_mut() {
+            // Preserve3D path (only relevant if there are no filters/mix-blend modes)
+            Some(ref parent_sc) if parent_sc.is_3d() => {
+                Some(cur_instance)
+            }
+            // Regular parenting path
+            Some(ref mut parent_sc) => {
+                // 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.
+                // If not already isolated for some other reason,
+                // make this picture as isolated.
+                if has_mix_blend_on_secondary_framebuffer {
+                    parent_sc.should_isolate = true;
+                }
+                parent_sc.primitives.push(cur_instance);
+                None
+            }
+            // This must be the root stacking context
+            None => {
+                self.root_pic_index = current_pic_index;
+                None
+            }
+        };
 
-        } else {
-            let parent_stacking_context = self.sc_stack.last_mut().unwrap();
-
-            // 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.
-            // If not already isolated for some other reason,
-            // make this picture as isolated.
-            if stacking_context.composite_ops.mix_blend_mode.is_some() &&
-               sc_count > 2 {
-                parent_stacking_context.should_isolate = true;
-            }
-
-            parent_stacking_context.normal_primitives.push(prim_instance);
-        };
+        // finally, if there any outstanding 3D primitive instances,
+        // find the 3D hierarchy root and add them there.
+        if let Some(instance) = trailing_children_instance {
+            self.add_primitive_instance_to_3d_root(instance);
+        }
 
         assert!(
             self.pending_shadow_items.is_empty(),
             "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
@@ -1494,17 +1491,17 @@ impl<'a> DisplayListFlattener<'a> {
                         // Create a picture that the shadow primitives will be added to. If the
                         // blur radius is 0, the code in Picture::prepare_for_render will
                         // detect this and mark the picture to be drawn directly into the
                         // parent picture, which avoids an intermediate surface and blur.
                         let blur_filter = FilterOp::Blur(std_deviation).sanitize();
                         let mut shadow_pic = PicturePrimitive::new_image(
                             self.picture_id_generator.next(),
                             Some(PictureCompositeMode::Filter(blur_filter)),
-                            false,
+                            Picture3DContext::Out,
                             pipeline_id,
                             None,
                             is_passthrough,
                             raster_space,
                             prims,
                         );
 
                         // Create the primitive to draw the shadow picture into the scene.
@@ -2117,30 +2114,42 @@ impl<'a> DisplayListFlattener<'a> {
             self.id_to_index_mapper.get_spatial_node_index(info.scroll_node_id),
             self.id_to_index_mapper.get_clip_chain_id(&info.clip_node_id())
         )
     }
 
     pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
+
+    pub fn add_primitive_instance_to_3d_root(&mut self, instance: PrimitiveInstance) {
+        // find the 3D root and append to the children list
+        for sc in self.sc_stack.iter_mut().rev() {
+            match sc.context_3d {
+                Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
+                    prims.push(instance);
+                    break;
+                }
+                Picture3DContext::In { .. } => {}
+                Picture3DContext::Out => panic!("Unable to find 3D root"),
+            }
+        }
+    }
 }
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
-    /// The list of un-transformed content being
-    /// added to this stacking context.
-    normal_primitives: Vec<PrimitiveInstance>,
+    /// The list of primitive instances added to this stacking context.
+    primitives: Vec<PrimitiveInstance>,
 
-    /// The list of preserve-3d primitives that
-    /// are being hoisted to this stacking context
-    /// (implies establishes_3d_context).
-    preserve3d_primitives: Vec<PrimitiveInstance>,
+    /// The interned key for all the primitive instances associated with this
+    /// SC (but not its children);
+    primitive_data_handle: PrimitiveDataHandle,
 
     /// Whether or not the caller wants this drawn in
     /// screen space (quality) or local space (performance)
     requested_raster_space: RasterSpace,
 
     /// The positioning node for this stacking context
     spatial_node_index: SpatialNodeIndex,
 
@@ -2157,29 +2166,73 @@ struct FlattenedStackingContext {
 
     /// If true, this stacking context should be
     /// isolated by forcing an off-screen surface.
     should_isolate: bool,
 
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
-    /// If true, visible when backface is visible.
-    is_backface_visible: bool,
-
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
-    /// If true, this stacking context establishes a new
-    /// 3d rendering context.
-    establishes_3d_context: bool,
+    /// Defines the relationship to a preserve-3D hiearachy.
+    context_3d: Picture3DContext<PrimitiveInstance>,
+}
+
+impl FlattenedStackingContext {
+    /// Return true if the stacking context has a valid preserve-3d property
+    pub fn is_3d(&self) -> bool {
+        self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
+    }
 
-    /// If true, this stacking context is part of a
-    /// surrounding 3d rendering context.
-    participating_in_3d_context: bool,
+    /// For a Preserve3D context, cut the sequence of the immediate flat children
+    /// recorded so far and generate a picture from them.
+    pub fn cut_flat_item_sequence(
+        &mut self,
+        picture_id_generator: &mut PictureIdGenerator,
+        prim_store: &mut PrimitiveStore,
+    ) -> Option<PrimitiveInstance> {
+        if !self.is_3d() || self.primitives.is_empty() {
+            return None
+        }
+        let flat_items_context_3d = match self.context_3d {
+            Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In {
+                root_data: None,
+                ancestor_index,
+            },
+            Picture3DContext::Out => panic!("Unexpected out of 3D context"),
+        };
+
+        let container_picture = PicturePrimitive::new_image(
+            picture_id_generator.next(),
+            Some(PictureCompositeMode::Blit),
+            flat_items_context_3d,
+            self.pipeline_id,
+            None,
+            true,
+            self.requested_raster_space,
+            mem::replace(&mut self.primitives, Vec::new()),
+        );
+
+        let pic_index = prim_store.create_picture(container_picture);
+        let container_prim = BrushPrimitive::new_picture(pic_index);
+        let cut_prim_index = prim_store.add_primitive(
+            &LayoutRect::zero(),
+            &LayoutRect::max_rect(),
+            PrimitiveContainer::Brush(container_prim),
+        );
+
+        Some(PrimitiveInstance::new(
+            cut_prim_index,
+            self.primitive_data_handle,
+            self.clip_chain_id,
+            self.spatial_node_index,
+        ))
+    }
 }
 
 /// A primitive that is added while a shadow context is
 /// active is stored as a pending primitive and only
 /// added to pictures during pop_all_shadows.
 struct PendingPrimitive {
     clip_and_scroll: ScrollNodeAndClipChain,
     info: LayoutPrimitiveInfo,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -6,17 +6,17 @@ use api::{ColorF, DeviceIntPoint, Device
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode, PictureRect};
 use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
 use clip::{ClipDataStore, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
-use internal_types::{FastHashMap};
+use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureCompositeMode, PictureSurface, RasterConfig};
 use prim_store::{PrimitiveIndex, PrimitiveStore, SpaceMapper, PictureIndex};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{FrameResources, FrameId};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use segment::SegmentBuilder;
@@ -82,38 +82,50 @@ pub struct FrameBuildingState<'a> {
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub special_render_passes: &'a mut SpecialRenderPasses,
     pub transforms: &'a mut TransformPalette,
     pub resources: &'a mut FrameResources,
     pub segment_builder: SegmentBuilder,
 }
 
+/// Immutable context of a picture when processing children.
+#[derive(Debug)]
 pub struct PictureContext {
     pub pic_index: PictureIndex,
     pub pipeline_id: PipelineId,
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
     pub is_passthrough: bool,
     pub raster_space: RasterSpace,
+    pub local_spatial_node_index: SpatialNodeIndex,
+    pub surface_spatial_node_index: SpatialNodeIndex,
+    pub raster_spatial_node_index: SpatialNodeIndex,
 }
 
-#[derive(Debug)]
+/// Mutable state of a picture that gets modified when
+/// the children are processed.
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
     pub is_cacheable: bool,
     pub local_rect_changed: bool,
+    /// Union rectangle of all the items in this picture.
+    pub rect: PictureRect,
     pub map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel>,
     pub map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel>,
     pub map_pic_to_raster: SpaceMapper<PicturePixel, RasterPixel>,
     pub map_raster_to_world: SpaceMapper<RasterPixel, WorldPixel>,
-    pub surface_spatial_node_index: SpatialNodeIndex,
-    pub raster_spatial_node_index: SpatialNodeIndex,
+    /// Mapping from local space to the containing block, which is the root for
+    /// plane splitting and affects backface visibility.
+    pub map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel>,
+    /// If the plane splitter, the primitives get added to it insted of
+    /// batching into their parent pictures.
+    pub plane_splitter: Option<PlaneSplitter>,
 }
 
 pub struct PrimitiveContext<'a> {
     pub spatial_node: &'a SpatialNode,
     pub spatial_node_index: SpatialNodeIndex,
 }
 
 impl<'a> PrimitiveContext<'a> {
@@ -230,44 +242,43 @@ impl FrameBuilder {
         let (pic_context, mut pic_state, mut instances) = self
             .prim_store
             .pictures[self.root_pic_index.0]
             .take_context(
                 self.root_pic_index,
                 &prim_context,
                 root_spatial_node_index,
                 root_spatial_node_index,
+                root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
                 false,
             )
             .unwrap();
 
-        let mut pic_rect = PictureRect::zero();
-
         self.prim_store.prepare_primitives(
             &mut instances,
             &pic_context,
             &mut pic_state,
             &frame_context,
             &mut frame_state,
-            &mut pic_rect,
         );
 
+        let pic_rect = Some(pic_state.rect);
         let pic = &mut self.prim_store.pictures[self.root_pic_index.0];
         pic.restore_context(
             instances,
             pic_context,
             pic_state,
-            Some(pic_rect),
+            pic_rect,
             &mut frame_state,
         );
 
-        let pic_state = pic.take_state();
+        let (pic_state, _) = pic.take_state_and_context();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,18 +1,19 @@
 /* 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::{DebugCommand, DeviceUintRect, DocumentId, ExternalImageData, ExternalImageId};
-use api::{ImageFormat, NotificationRequest};
+use api::{ImageFormat, WorldPixel, NotificationRequest};
 use device::TextureFilter;
 use renderer::PipelineInfo;
 use gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
+use plane_split::BspSplitter;
 use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
 
@@ -20,16 +21,19 @@ use std::sync::Arc;
 use capture::{CaptureConfig, ExternalCaptureImage};
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 use tiling;
 
 pub type FastHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
 pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
 
+/// A concrete plane splitter type used in WebRender.
+pub type PlaneSplitter = BspSplitter<f64, WorldPixel>;
+
 /// An ID for a texture that is owned by the `texture_cache` module.
 ///
 /// This can include atlases or standalone textures allocated via the texture
 /// cache (e.g.  if an image is too large to be added to an atlas). The texture
 /// cache manages the allocation and freeing of these IDs, and the rendering
 /// thread maintains a map from cache texture ID to native texture.
 ///
 /// We never reuse IDs, so we use a u64 here to be safe.
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -3,23 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint};
 use api::{DeviceIntRect, DeviceIntSize, DevicePoint, LayoutRect, PictureToRasterTransform};
 use api::{DevicePixelScale, PictureIntPoint, PictureIntRect, PictureIntSize, RasterRect, RasterSpace};
 use api::{PicturePixel, RasterPixel, WorldPixel};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
-use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
-use euclid::TypedScale;
+use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
+use euclid::{TypedScale, vec3};
+use internal_types::PlaneSplitter;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use frame_builder::{PictureContext, PrimitiveContext};
-use gpu_cache::{GpuCacheHandle};
-use gpu_types::UvRectKind;
-use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, get_raster_rects};
+use gpu_cache::{GpuCacheAddress, GpuCacheHandle};
+use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
+use plane_split::{Clipper, Polygon, Splitter};
+use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper};
+use prim_store::{get_raster_rects};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
 /*
@@ -148,21 +151,47 @@ pub struct PictureCacheKey {
 
     // Ensure that if the overall size of the picture
     // changes, the cache key will not match. This can
     // happen, for example, during zooming or changes
     // in device-pixel-ratio.
     unclipped_size: DeviceIntSize,
 }
 
-#[derive(Debug)]
+/// Enum value describing the place of a picture in a 3D context.
+#[derive(Clone, Debug)]
+pub enum Picture3DContext<C> {
+    /// The picture is not a part of 3D context sub-hierarchy.
+    Out,
+    /// The picture is a part of 3D context.
+    In {
+        /// Additional data per child for the case of this a root of 3D hierarchy.
+        root_data: Option<Vec<C>>,
+        /// The spatial node index of an "ancestor" element, i.e. one
+        /// that establishes the transformed element’s containing block.
+        ///
+        /// See CSS spec draft for more details:
+        /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
+        ancestor_index: SpatialNodeIndex,
+    },
+}
+
+/// Information about a preserve-3D hierarchy child that has been plane-split
+/// and ordered according to the view direction.
+#[derive(Clone, Debug)]
+pub struct OrderedPictureChild {
+    pub anchor: usize,
+    pub transform_id: TransformPaletteId,
+    pub gpu_address: GpuCacheAddress,
+}
+
 pub struct PicturePrimitive {
     // List of primitive runs that make up this picture.
     pub prim_instances: Vec<PrimitiveInstance>,
-    pub state: Option<PictureState>,
+    pub state: Option<(PictureState, PictureContext)>,
 
     // The pipeline that the primitives on this picture belong to.
     pub pipeline_id: PipelineId,
 
     // If true, apply the local clip rect to primitive drawn
     // in this picture.
     pub apply_local_clip_rect: bool,
 
@@ -176,18 +205,18 @@ pub struct PicturePrimitive {
     /// How this picture should be composited.
     /// If None, don't composite - just draw directly on parent surface.
     pub requested_composite_mode: Option<PictureCompositeMode>,
     /// Requested rasterization space for this picture. It is
     /// a performance hint only.
     pub requested_raster_space: RasterSpace,
 
     pub raster_config: Option<RasterConfig>,
-    // If true, this picture is part of a 3D context.
-    pub is_in_3d_context: bool,
+    pub context_3d: Picture3DContext<OrderedPictureChild>,
+
     // If requested as a frame output (for rendering
     // pages to a texture), this is the pipeline this
     // picture is the root of.
     pub frame_output_pipeline_id: Option<PipelineId>,
     // An optional cache handle for storing extra data
     // in the GPU cache, depending on the type of
     // picture.
     pub extra_gpu_data_handle: GpuCacheHandle,
@@ -211,43 +240,44 @@ impl PicturePrimitive {
             }
             _ => true,
         }
     }
 
     pub fn new_image(
         id: PictureId,
         requested_composite_mode: Option<PictureCompositeMode>,
-        is_in_3d_context: bool,
+        context_3d: Picture3DContext<OrderedPictureChild>,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
         prim_instances: Vec<PrimitiveInstance>,
     ) -> Self {
         PicturePrimitive {
             prim_instances,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
-            is_in_3d_context,
+            context_3d,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             pipeline_id,
             id,
             requested_raster_space,
         }
     }
 
     pub fn take_context(
         &mut self,
         pic_index: PictureIndex,
         prim_context: &PrimitiveContext,
+        parent_spatial_node_index: SpatialNodeIndex,
         surface_spatial_node_index: SpatialNodeIndex,
         raster_spatial_node_index: SpatialNodeIndex,
         parent_allows_subpixel_aa: bool,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
         is_chased: bool,
     ) -> Option<(PictureContext, PictureState, Vec<PrimitiveInstance>)> {
         if !self.resolve_scene_properties(frame_context.scene_properties) {
@@ -299,17 +329,17 @@ impl PicturePrimitive {
         let raster_spatial_node_index = if establishes_raster_root {
             surface_spatial_node_index
         } else {
             raster_spatial_node_index
         };
 
         if has_surface {
             frame_state.clip_store
-                       .push_surface(surface_spatial_node_index);
+                .push_surface(surface_spatial_node_index);
         }
 
         let map_pic_to_world = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             surface_spatial_node_index,
             frame_context.world_rect,
             frame_context.clip_scroll_tree,
         );
@@ -323,35 +353,53 @@ impl PicturePrimitive {
         );
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             surface_spatial_node_index,
             raster_spatial_node_index,
             frame_context,
         );
 
+        let (containing_block_index, plane_splitter) = match self.context_3d {
+            Picture3DContext::Out => {
+                (parent_spatial_node_index, None)
+            }
+            Picture3DContext::In { root_data: Some(_), ancestor_index } => {
+                (ancestor_index, Some(PlaneSplitter::new()))
+            }
+            Picture3DContext::In { root_data: None, ancestor_index } => {
+                (ancestor_index, None)
+            }
+        };
+
+        let map_local_to_containing_block = SpaceMapper::new(
+            containing_block_index,
+            LayoutRect::zero(), // bounds aren't going to be used for this mapping
+        );
+
         self.raster_config = actual_composite_mode.map(|composite_mode| {
             RasterConfig {
                 composite_mode,
                 surface: None,
                 raster_spatial_node_index,
             }
         });
 
         let state = PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
             is_cacheable: true,
             local_rect_changed: false,
-            raster_spatial_node_index,
-            surface_spatial_node_index,
+            rect: PictureRect::zero(),
             map_local_to_pic,
             map_pic_to_world,
             map_pic_to_raster,
             map_raster_to_world,
+            map_local_to_containing_block,
+            plane_splitter,
         };
 
         // Disallow subpixel AA if an intermediate surface is needed.
         // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
         let allow_subpixel_aa = parent_allows_subpixel_aa &&
             self.raster_config.is_none();
 
         let inflation_factor = match self.raster_config {
@@ -368,34 +416,34 @@ impl PicturePrimitive {
         let context = PictureContext {
             pic_index,
             pipeline_id: self.pipeline_id,
             apply_local_clip_rect: self.apply_local_clip_rect,
             inflation_factor,
             allow_subpixel_aa,
             is_passthrough: self.raster_config.is_none(),
             raster_space,
+            local_spatial_node_index: prim_context.spatial_node_index,
+            raster_spatial_node_index,
+            surface_spatial_node_index,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
     pub fn restore_context(
         &mut self,
         prim_instances: Vec<PrimitiveInstance>,
         context: PictureContext,
         state: PictureState,
         local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
     ) -> (LayoutRect, Option<ClipNodeCollector>) {
-        self.prim_instances = prim_instances;
-        self.state = Some(state);
-
         let local_rect = match local_rect {
             Some(local_rect) => {
                 let local_content_rect = LayoutRect::from_untyped(&local_rect.to_untyped());
 
                 match self.raster_config {
                     Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)), .. }) => {
                         let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                         local_content_rect.inflate(inflate_size, inflate_size)
@@ -426,358 +474,475 @@ impl PicturePrimitive {
         };
 
         let clip_node_collector = if context.is_passthrough {
             None
         } else {
             Some(frame_state.clip_store.pop_surface())
         };
 
+        self.prim_instances = prim_instances;
+        self.state = Some((state, context));
+
         (local_rect, clip_node_collector)
     }
 
-    pub fn take_state(&mut self) -> PictureState {
+    pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
         self.state.take().expect("bug: no state present!")
     }
 
+    /// Add a primitive instance to the plane splitter. The function would generate
+    /// an appropriate polygon, clip it against the frustum, and register with the
+    /// given plane splitter.
+    pub fn add_split_plane(
+        splitter: &mut PlaneSplitter,
+        transforms: &TransformPalette,
+        prim_instance: &PrimitiveInstance,
+        original_local_rect: LayoutRect,
+        plane_split_anchor: usize,
+    ) -> bool {
+        let transform = transforms
+            .get_world_transform(prim_instance.spatial_node_index);
+        let matrix = transform.cast();
+
+        // Apply the local clip rect here, before splitting. This is
+        // because the local clip rect can't be applied in the vertex
+        // shader for split composites, since we are drawing polygons
+        // rather that rectangles. The interpolation still works correctly
+        // since we determine the UVs by doing a bilerp with a factor
+        // from the original local rect.
+        let local_rect = match original_local_rect
+            .intersection(&prim_instance.combined_local_clip_rect)
+        {
+            Some(rect) => rect.cast(),
+            None => return false,
+        };
+
+        match transform.transform_kind() {
+            TransformedRectKind::AxisAligned => {
+                let inv_transform = transforms
+                    .get_world_inv_transform(prim_instance.spatial_node_index);
+                let polygon = Polygon::from_transformed_rect_with_inverse(
+                    local_rect,
+                    &matrix,
+                    &inv_transform.cast(),
+                    plane_split_anchor,
+                ).unwrap();
+                splitter.add(polygon);
+            }
+            TransformedRectKind::Complex => {
+                let mut clipper = Clipper::new();
+                let results = clipper.clip_transformed(
+                    Polygon::from_rect(
+                        local_rect,
+                        plane_split_anchor,
+                    ),
+                    &matrix,
+                    prim_instance.clipped_world_rect.map(|r| r.to_f64()),
+                );
+                if let Ok(results) = results {
+                    for poly in results {
+                        splitter.add(poly);
+                    }
+                }
+            }
+        }
+
+        true
+    }
+
+    pub fn resolve_split_planes(
+        &mut self,
+        splitter: &mut PlaneSplitter,
+        frame_state: &mut FrameBuildingState,
+        clip_scroll_tree: &ClipScrollTree,
+    ) {
+        let ordered = match self.context_3d {
+            Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
+            _ => panic!("Expected to find 3D context root"),
+        };
+        ordered.clear();
+
+        // Process the accumulated split planes and order them for rendering.
+        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
+        for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
+            let spatial_node_index = self.prim_instances[poly.anchor].spatial_node_index;
+
+            let transform = frame_state.transforms.get_world_inv_transform(spatial_node_index);
+            let transform_id = frame_state.transforms.get_id(
+                spatial_node_index,
+                ROOT_SPATIAL_NODE_INDEX,
+                clip_scroll_tree,
+            );
+
+            let local_points = [
+                transform.transform_point3d(&poly.points[0].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[1].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[2].cast()).unwrap(),
+                transform.transform_point3d(&poly.points[3].cast()).unwrap(),
+            ];
+            let gpu_blocks = [
+                [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
+                [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
+            ];
+            let gpu_handle = frame_state.gpu_cache.push_per_frame_blocks(&gpu_blocks);
+            let gpu_address = frame_state.gpu_cache.get_address(&gpu_handle);
+
+            ordered.push(OrderedPictureChild {
+                anchor: poly.anchor,
+                transform_id,
+                gpu_address,
+            });
+        }
+    }
+
     pub fn prepare_for_render(
         &mut self,
         pic_index: PictureIndex,
         prim_instance: &PrimitiveInstance,
         prim_local_rect: &LayoutRect,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
-        let mut pic_state_for_children = self.take_state();
+        let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
+
+        if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
+            self.resolve_split_planes(
+                splitter,
+                frame_state,
+                frame_context.clip_scroll_tree,
+            );
+        }
+
+        let raster_config = match self.raster_config {
+            Some(ref mut raster_config) => raster_config,
+            None => {
+                pic_state.tasks.extend(pic_state_for_children.tasks);
+                return true
+            }
+        };
+
+        let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
+            prim_instance.spatial_node_index,
+            raster_config.raster_spatial_node_index,
+            frame_context,
+        );
+
+        let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
 
-        match self.raster_config {
-            Some(ref mut raster_config) => {
-                let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
-                    prim_instance.spatial_node_index,
-                    raster_config.raster_spatial_node_index,
-                    frame_context,
+        let (clipped, unclipped, transform) = match get_raster_rects(
+            pic_rect,
+            &map_pic_to_raster,
+            &map_raster_to_world,
+            prim_instance.clipped_world_rect.expect("bug1"),
+            frame_context.device_pixel_scale,
+        ) {
+            Some(info) => info,
+            None => return false,
+        };
+
+        // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
+        //           to store the same type of data. The exception is the filter
+        //           with a ColorMatrix, which stores the color matrix here. It's
+        //           probably worth tidying this code up to be a bit more consistent.
+        //           Perhaps store the color matrix after the common data, even though
+        //           it's not used by that shader.
+
+        match raster_config.composite_mode {
+            PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+
+                // The clipped field is the part of the picture that is visible
+                // on screen. The unclipped field is the screen-space rect of
+                // the complete picture, if no screen / clip-chain was applied
+                // (this includes the extra space for blur region). To ensure
+                // that we draw a large enough part of the picture to get correct
+                // blur results, inflate that clipped area by the blur range, and
+                // then intersect with the total screen rect, to minimize the
+                // allocation size.
+                let device_rect = clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&unclipped.to_i32())
+                    .unwrap();
+
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &device_rect,
+                    frame_context.device_pixel_scale,
                 );
 
-                let pic_rect = PictureRect::from_untyped(&prim_local_rect.to_untyped());
+                // If we are drawing a blur that has primitives or clips that contain
+                // a complex coordinate system, don't bother caching them (for now).
+                // It's likely that they are animating and caching may not help here
+                // anyway. In the future we should relax this a bit, so that we can
+                // cache tasks with complex coordinate systems if we detect the
+                // relevant transforms haven't changed from frame to frame.
+                let surface = if pic_state_for_children.has_non_root_coord_system ||
+                                 !pic_state_for_children.is_cacheable {
+                    let picture_task = RenderTask::new_picture(
+                        RenderTaskLocation::Dynamic(None, device_rect.size),
+                        unclipped.size,
+                        pic_index,
+                        device_rect.origin,
+                        pic_state_for_children.tasks,
+                        uv_rect_kind,
+                        pic_context.raster_spatial_node_index,
+                    );
 
-                let (clipped, unclipped, transform) = match get_raster_rects(
-                    pic_rect,
-                    &map_pic_to_raster,
-                    &map_raster_to_world,
-                    prim_instance.clipped_world_rect.expect("bug1"),
-                    frame_context.device_pixel_scale,
-                ) {
-                    Some(info) => info,
-                    None => return false,
-                };
+                    let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
-                //           to store the same type of data. The exception is the filter
-                //           with a ColorMatrix, which stores the color matrix here. It's
-                //           probably worth tidying this code up to be a bit more consistent.
-                //           Perhaps store the color matrix after the common data, even though
-                //           it's not used by that shader.
+                    let blur_render_task = RenderTask::new_blur(
+                        blur_std_deviation,
+                        picture_task_id,
+                        frame_state.render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                    );
 
-                match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
-                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                        let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                    let render_task_id = frame_state.render_tasks.add(blur_render_task);
+
+                    pic_state.tasks.push(render_task_id);
 
-                        // The clipped field is the part of the picture that is visible
-                        // on screen. The unclipped field is the screen-space rect of
-                        // the complete picture, if no screen / clip-chain was applied
-                        // (this includes the extra space for blur region). To ensure
-                        // that we draw a large enough part of the picture to get correct
-                        // blur results, inflate that clipped area by the blur range, and
-                        // then intersect with the total screen rect, to minimize the
-                        // allocation size.
-                        let device_rect = clipped
-                            .inflate(blur_range, blur_range)
-                            .intersection(&unclipped.to_i32())
-                            .unwrap();
+                    PictureSurface::RenderTask(render_task_id)
+                } else {
+                    // Get the relative clipped rect within the overall prim rect, that
+                    // forms part of the cache key.
+                    let pic_relative_render_rect = PictureIntRect::new(
+                        PictureIntPoint::new(
+                            device_rect.origin.x - unclipped.origin.x as i32,
+                            device_rect.origin.y - unclipped.origin.y as i32,
+                        ),
+                        PictureIntSize::new(
+                            device_rect.size.width,
+                            device_rect.size.height,
+                        ),
+                    );
 
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &device_rect,
-                            frame_context.device_pixel_scale,
-                        );
+                    // Request a render task that will cache the output in the
+                    // texture cache.
+                    let cache_item = frame_state.resource_cache.request_render_task(
+                        RenderTaskCacheKey {
+                            size: device_rect.size,
+                            kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
+                                scene_id: frame_context.scene_id,
+                                picture_id: self.id,
+                                unclipped_size: unclipped.size.to_i32(),
+                                pic_relative_render_rect,
+                            }),
+                        },
+                        frame_state.gpu_cache,
+                        frame_state.render_tasks,
+                        None,
+                        false,
+                        |render_tasks| {
+                            let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
-                        // If we are drawing a blur that has primitives or clips that contain
-                        // a complex coordinate system, don't bother caching them (for now).
-                        // It's likely that they are animating and caching may not help here
-                        // anyway. In the future we should relax this a bit, so that we can
-                        // cache tasks with complex coordinate systems if we detect the
-                        // relevant transforms haven't changed from frame to frame.
-                        let surface = if pic_state_for_children.has_non_root_coord_system ||
-                                         !pic_state_for_children.is_cacheable {
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
                                 pic_index,
                                 device_rect.origin,
-                                pic_state_for_children.tasks,
+                                child_tasks,
                                 uv_rect_kind,
-                                pic_state_for_children.raster_spatial_node_index,
+                                pic_context.raster_spatial_node_index,
                             );
 
-                            let picture_task_id = frame_state.render_tasks.add(picture_task);
+                            let picture_task_id = render_tasks.add(picture_task);
 
                             let blur_render_task = RenderTask::new_blur(
                                 blur_std_deviation,
                                 picture_task_id,
-                                frame_state.render_tasks,
+                                render_tasks,
                                 RenderTargetKind::Color,
                                 ClearMode::Transparent,
                             );
 
-                            let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                            let render_task_id = render_tasks.add(blur_render_task);
 
                             pic_state.tasks.push(render_task_id);
 
-                            PictureSurface::RenderTask(render_task_id)
-                        } else {
-                            // Get the relative clipped rect within the overall prim rect, that
-                            // forms part of the cache key.
-                            let pic_relative_render_rect = PictureIntRect::new(
-                                PictureIntPoint::new(
-                                    device_rect.origin.x - unclipped.origin.x as i32,
-                                    device_rect.origin.y - unclipped.origin.y as i32,
-                                ),
-                                PictureIntSize::new(
-                                    device_rect.size.width,
-                                    device_rect.size.height,
-                                ),
-                            );
+                            render_task_id
+                        }
+                    );
+
+                    PictureSurface::TextureCache(cache_item)
+                };
+
+                raster_config.surface = Some(surface);
+            }
+            PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
-                            // Request a render task that will cache the output in the
-                            // texture cache.
-                            let cache_item = frame_state.resource_cache.request_render_task(
-                                RenderTaskCacheKey {
-                                    size: device_rect.size,
-                                    kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
-                                        scene_id: frame_context.scene_id,
-                                        picture_id: self.id,
-                                        unclipped_size: unclipped.size.to_i32(),
-                                        pic_relative_render_rect,
-                                    }),
-                                },
-                                frame_state.gpu_cache,
-                                frame_state.render_tasks,
-                                None,
-                                false,
-                                |render_tasks| {
-                                    let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
+                // The clipped field is the part of the picture that is visible
+                // on screen. The unclipped field is the screen-space rect of
+                // the complete picture, if no screen / clip-chain was applied
+                // (this includes the extra space for blur region). To ensure
+                // that we draw a large enough part of the picture to get correct
+                // blur results, inflate that clipped area by the blur range, and
+                // then intersect with the total screen rect, to minimize the
+                // allocation size.
+                let device_rect = clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&unclipped.to_i32())
+                    .unwrap();
 
-                                    let picture_task = RenderTask::new_picture(
-                                        RenderTaskLocation::Dynamic(None, device_rect.size),
-                                        unclipped.size,
-                                        pic_index,
-                                        device_rect.origin,
-                                        child_tasks,
-                                        uv_rect_kind,
-                                        pic_state_for_children.raster_spatial_node_index,
-                                    );
-
-                                    let picture_task_id = render_tasks.add(picture_task);
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &device_rect,
+                    frame_context.device_pixel_scale,
+                );
 
-                                    let blur_render_task = RenderTask::new_blur(
-                                        blur_std_deviation,
-                                        picture_task_id,
-                                        render_tasks,
-                                        RenderTargetKind::Color,
-                                        ClearMode::Transparent,
-                                    );
+                let mut picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, device_rect.size),
+                    unclipped.size,
+                    pic_index,
+                    device_rect.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
+                picture_task.mark_for_saving();
 
-                                    let render_task_id = render_tasks.add(blur_render_task);
-
-                                    pic_state.tasks.push(render_task_id);
+                let picture_task_id = frame_state.render_tasks.add(picture_task);
 
-                                    render_task_id
-                                }
-                            );
+                let blur_render_task = RenderTask::new_blur(
+                    blur_std_deviation.round(),
+                    picture_task_id,
+                    frame_state.render_tasks,
+                    RenderTargetKind::Color,
+                    ClearMode::Transparent,
+                );
 
-                            PictureSurface::TextureCache(cache_item)
-                        };
+                self.secondary_render_task_id = Some(picture_task_id);
 
-                        raster_config.surface = Some(surface);
-                    }
-                    PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
-                        let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
-                        let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+                let render_task_id = frame_state.render_tasks.add(blur_render_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
 
-                        // The clipped field is the part of the picture that is visible
-                        // on screen. The unclipped field is the screen-space rect of
-                        // the complete picture, if no screen / clip-chain was applied
-                        // (this includes the extra space for blur region). To ensure
-                        // that we draw a large enough part of the picture to get correct
-                        // blur results, inflate that clipped area by the blur range, and
-                        // then intersect with the total screen rect, to minimize the
-                        // allocation size.
-                        let device_rect = clipped
-                            .inflate(blur_range, blur_range)
-                            .intersection(&unclipped.to_i32())
-                            .unwrap();
-
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &device_rect,
-                            frame_context.device_pixel_scale,
-                        );
+                // If the local rect of the contents changed, force the cache handle
+                // to be invalidated so that the primitive data below will get
+                // uploaded to the GPU this frame. This can occur during property
+                // animation.
+                if pic_state.local_rect_changed {
+                    frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
+                }
 
-                        let mut picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, device_rect.size),
-                            unclipped.size,
-                            pic_index,
-                            device_rect.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
-                        picture_task.mark_for_saving();
+                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                    // TODO(gw): This is very hacky code below! It stores an extra
+                    //           brush primitive below for the special case of a
+                    //           drop-shadow where we need a different local
+                    //           rect for the shadow. To tidy this up in future,
+                    //           we could consider abstracting the code in prim_store.rs
+                    //           that writes a brush primitive header.
 
-                        let picture_task_id = frame_state.render_tasks.add(picture_task);
+                    // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
+                    //  [brush specific data]
+                    //  [segment_rect, segment data]
+                    let shadow_rect = prim_local_rect.translate(&offset);
 
-                        let blur_render_task = RenderTask::new_blur(
-                            blur_std_deviation.round(),
-                            picture_task_id,
-                            frame_state.render_tasks,
-                            RenderTargetKind::Color,
-                            ClearMode::Transparent,
-                        );
+                    // ImageBrush colors
+                    request.push(color.premultiplied());
+                    request.push(PremultipliedColorF::WHITE);
+                    request.push([
+                        prim_local_rect.size.width,
+                        prim_local_rect.size.height,
+                        0.0,
+                        0.0,
+                    ]);
 
-                        self.secondary_render_task_id = Some(picture_task_id);
-
-                        let render_task_id = frame_state.render_tasks.add(blur_render_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-
-                        // If the local rect of the contents changed, force the cache handle
-                        // to be invalidated so that the primitive data below will get
-                        // uploaded to the GPU this frame. This can occur during property
-                        // animation.
-                        if pic_state.local_rect_changed {
-                            frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
-                        }
+                    // segment rect / extra data
+                    request.push(shadow_rect);
+                    request.push([0.0, 0.0, 0.0, 0.0]);
+                }
+            }
+            PictureCompositeMode::MixBlend(..) => {
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                            // TODO(gw): This is very hacky code below! It stores an extra
-                            //           brush primitive below for the special case of a
-                            //           drop-shadow where we need a different local
-                            //           rect for the shadow. To tidy this up in future,
-                            //           we could consider abstracting the code in prim_store.rs
-                            //           that writes a brush primitive header.
-
-                            // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
-                            //  [brush specific data]
-                            //  [segment_rect, segment data]
-                            let shadow_rect = prim_local_rect.translate(&offset);
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                            // ImageBrush colors
-                            request.push(color.premultiplied());
-                            request.push(PremultipliedColorF::WHITE);
-                            request.push([
-                                prim_local_rect.size.width,
-                                prim_local_rect.size.height,
-                                0.0,
-                                0.0,
-                            ]);
+                let readback_task_id = frame_state.render_tasks.add(
+                    RenderTask::new_readback(clipped)
+                );
+
+                self.secondary_render_task_id = Some(readback_task_id);
+                pic_state.tasks.push(readback_task_id);
 
-                            // segment rect / extra data
-                            request.push(shadow_rect);
-                            request.push([0.0, 0.0, 0.0, 0.0]);
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+            }
+            PictureCompositeMode::Filter(filter) => {
+                if let FilterOp::ColorMatrix(m) = filter {
+                    if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                        for i in 0..5 {
+                            request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
-                    PictureCompositeMode::MixBlend(..) => {
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
+                }
 
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        let readback_task_id = frame_state.render_tasks.add(
-                            RenderTask::new_readback(clipped)
-                        );
-
-                        self.secondary_render_task_id = Some(readback_task_id);
-                        pic_state.tasks.push(readback_task_id);
-
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                    PictureCompositeMode::Filter(filter) => {
-                        if let FilterOp::ColorMatrix(m) = filter {
-                            if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                                for i in 0..5 {
-                                    request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
-                                }
-                            }
-                        }
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
-
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
+            }
+            PictureCompositeMode::Blit => {
+                let uv_rect_kind = calculate_uv_rect_kind(
+                    &pic_rect,
+                    &transform,
+                    &clipped,
+                    frame_context.device_pixel_scale,
+                );
 
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                    PictureCompositeMode::Blit => {
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &clipped,
-                            frame_context.device_pixel_scale,
-                        );
+                let picture_task = RenderTask::new_picture(
+                    RenderTaskLocation::Dynamic(None, clipped.size),
+                    unclipped.size,
+                    pic_index,
+                    clipped.origin,
+                    pic_state_for_children.tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                );
 
-                        let picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, clipped.size),
-                            unclipped.size,
-                            pic_index,
-                            clipped.origin,
-                            pic_state_for_children.tasks,
-                            uv_rect_kind,
-                            pic_state_for_children.raster_spatial_node_index,
-                        );
-
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-                        pic_state.tasks.push(render_task_id);
-                        raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
-                    }
-                }
-            }
-            None => {
-                pic_state.tasks.extend(pic_state_for_children.tasks);
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                pic_state.tasks.push(render_task_id);
+                raster_config.surface = Some(PictureSurface::RenderTask(render_task_id));
             }
         }
 
         true
     }
 }
 
 // Calculate a single screen-space UV for a picture.
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -25,18 +25,19 @@ use intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, to_cache_size};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
-use std::{cmp, fmt, mem, usize};
-use util::{ScaleOffset, MatrixHelpers, pack_as_float, project_rect, raster_rect_to_device_pixels};
+use std::{cmp, fmt, mem, ops, usize};
+use util::{ScaleOffset, MatrixHelpers};
+use util::{pack_as_float, project_rect, raster_rect_to_device_pixels};
 use smallvec::SmallVec;
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
@@ -72,16 +73,32 @@ impl PrimitiveOpacity {
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha >= 1.0,
         }
     }
 }
 
+#[derive(Debug, Copy, Clone)]
+pub enum VisibleFace {
+    Front,
+    Back,
+}
+
+impl ops::Not for VisibleFace {
+    type Output = Self;
+    fn not(self) -> Self {
+        match self {
+            VisibleFace::Front => VisibleFace::Back,
+            VisibleFace::Back => VisibleFace::Front,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub enum CoordinateSpaceMapping<F, T> {
     Local,
     ScaleOffset(ScaleOffset),
     Transform(TypedTransform3D<f32, F, T>),
 }
 
 #[derive(Debug)]
@@ -194,16 +211,30 @@ impl<F, T> SpaceMapper<F, T> where F: fm
                     None => {
                         warn!("parent relative transform can't transform the primitive rect for {:?}", rect);
                         None
                     }
                 }
             }
         }
     }
+
+    pub fn visible_face(&self) -> VisibleFace {
+        match self.kind {
+            CoordinateSpaceMapping::Local => VisibleFace::Front,
+            CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front,
+            CoordinateSpaceMapping::Transform(ref transform) => {
+                if transform.is_backface_visible() {
+                    VisibleFace::Back
+                } else {
+                    VisibleFace::Front
+                }
+            }
+        }
+    }
 }
 
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
@@ -358,17 +389,16 @@ pub enum BorderSource {
     Image(ImageRequest),
     Border {
         segments: SmallVec<[BorderSegmentInfo; 8]>,
         border: NormalBorder,
         widths: LayoutSideOffsets,
     },
 }
 
-#[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
@@ -616,17 +646,16 @@ impl BrushSegment {
 
 pub type BrushSegmentVec = SmallVec<[BrushSegment; 8]>;
 
 #[derive(Debug)]
 pub struct BrushSegmentDescriptor {
     pub segments: BrushSegmentVec,
 }
 
-#[derive(Debug)]
 pub struct BrushPrimitive {
     pub kind: BrushKind,
     pub segment_desc: Option<BrushSegmentDescriptor>,
 }
 
 impl BrushPrimitive {
     pub fn new(
         kind: BrushKind,
@@ -1363,17 +1392,16 @@ impl ClipData {
             &self.bottom_left,
             &self.bottom_right,
         ] {
             corner.write(request);
         }
     }
 }
 
-#[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitive),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
@@ -1499,17 +1527,17 @@ pub enum PrimitiveDetails {
 }
 
 pub struct Primitive {
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
     pub details: PrimitiveDetails,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct PrimitiveInstance {
     /// Index into the prim store containing information about
     /// the specific primitive. This will be removed once all
     /// primitive data is interned.
     pub prim_index: PrimitiveIndex,
 
     /// Handle to the common interned data for this primitive.
     pub prim_data_handle: PrimitiveDataHandle,
@@ -1748,34 +1776,35 @@ impl PrimitiveStore {
         &mut self,
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
+        plane_split_anchor: usize,
         is_chased: bool,
-        current_pic_rect: &mut PictureRect,
     ) -> bool {
         // If we have dependencies, we need to prepare them first, in order
         // to know the actual rect of this primitive.
         // For example, scrolling may affect the location of an item in
         // local space, which may force us to render this item on a larger
         // picture target, if being composited.
         let pic_info = {
             match self.primitives[prim_instance.prim_index.0].details {
                 PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture { pic_index, .. }, .. }) => {
                     let pic = &mut self.pictures[pic_index.0];
 
                     match pic.take_context(
                         pic_index,
                         prim_context,
-                        pic_state.surface_spatial_node_index,
-                        pic_state.raster_spatial_node_index,
+                        pic_context.local_spatial_node_index,
+                        pic_context.surface_spatial_node_index,
+                        pic_context.raster_spatial_node_index,
                         pic_context.allow_subpixel_aa,
                         frame_state,
                         frame_context,
                         is_chased,
                     ) {
                         Some(info) => Some(info),
                         None => return false,
                     }
@@ -1789,31 +1818,29 @@ impl PrimitiveStore {
 
         let (is_passthrough, clip_node_collector) = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_instances)) => {
                 // Mark whether this picture has a complex coordinate system.
                 let is_passthrough = pic_context_for_children.is_passthrough;
                 pic_state_for_children.has_non_root_coord_system |=
                     prim_context.spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
-                let mut pic_rect = PictureRect::zero();
                 self.prepare_primitives(
                     &mut prim_instances,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
-                    &mut pic_rect,
                 );
 
                 let pic_rect = if is_passthrough {
-                    *current_pic_rect = current_pic_rect.union(&pic_rect);
+                    pic_state.rect = pic_state.rect.union(&pic_state_for_children.rect);
                     None
                 } else {
-                    Some(pic_rect)
+                    Some(pic_state_for_children.rect)
                 };
 
                 if !pic_state_for_children.is_cacheable {
                     pic_state.is_cacheable = false;
                 }
 
                 // Restore the dependencies (borrow check dance)
                 let (new_local_rect, clip_node_collector) = self
@@ -1920,18 +1947,20 @@ impl PrimitiveStore {
             let pic_rect = match pic_state.map_local_to_pic
                                           .map(&prim.local_rect) {
                 Some(pic_rect) => pic_rect,
                 None => return false,
             };
 
             // Check if the clip bounding rect (in pic space) is visible on screen
             // This includes both the prim bounding rect + local prim clip rect!
-            let world_rect = match pic_state.map_pic_to_world
-                                            .map(&clip_chain.pic_clip_rect) {
+            let world_rect = match pic_state
+                .map_pic_to_world
+                .map(&clip_chain.pic_clip_rect)
+            {
                 Some(world_rect) => world_rect,
                 None => {
                     return false;
                 }
             };
 
             let clipped_world_rect = match world_rect.intersection(&frame_context.world_rect) {
                 Some(rect) => rect,
@@ -1941,106 +1970,124 @@ impl PrimitiveStore {
             };
 
             prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim.update_clip_task(
                 prim_instance,
                 prim_context,
                 clipped_world_rect,
-                pic_state.raster_spatial_node_index,
+                pic_context.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
                 is_chased,
                 &clip_node_collector,
             );
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\tconsidered visible and ready with local rect {:?}", local_rect);
             }
 
-            *current_pic_rect = current_pic_rect.union(&pic_rect);
+            pic_state.rect = pic_state.rect.union(&pic_rect);
         }
 
         prim.prepare_prim_for_render_inner(
             prim_instance,
             prim_context,
             pic_context,
             pic_state,
             &mut self.pictures,
             frame_context,
             frame_state,
             display_list,
+            plane_split_anchor,
             is_chased,
         );
 
         true
     }
 
     pub fn prepare_primitives(
         &mut self,
-        prim_instances: &mut Vec<PrimitiveInstance>,
+        prim_instances: &mut [PrimitiveInstance],
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
-        current_pic_rect: &mut PictureRect,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        for prim_instance in prim_instances {
+        for (plane_split_anchor, prim_instance) in prim_instances.iter_mut().enumerate() {
             prim_instance.clipped_world_rect = None;
 
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
             if is_chased {
                 println!("\tpreparing prim {:?} in pipeline {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
-            let is_backface_visible = frame_state
+            // Do some basic checks first, that can early out
+            // without even knowing the local rect.
+            if !frame_state
                 .resources
                 .prim_data_store[prim_instance.prim_data_handle]
-                .is_backface_visible;
+                .is_backface_visible
+            {
+                pic_state.map_local_to_containing_block.set_target_spatial_node(
+                    prim_instance.spatial_node_index,
+                    frame_context.clip_scroll_tree,
+                );
+
+                match pic_state.map_local_to_containing_block.visible_face() {
+                    VisibleFace::Back => {
+                        if cfg!(debug_assertions) && is_chased {
+                            println!("\tculled for not having visible back faces, transform {:?}",
+                                pic_state.map_local_to_containing_block);
+                        }
+                        continue;
+                    }
+                    VisibleFace::Front => {
+                        if cfg!(debug_assertions) && is_chased {
+                            println!("\tprim {:?} is not culled for visible face {:?}, transform {:?}",
+                                prim_index,
+                                pic_state.map_local_to_containing_block.visible_face(),
+                                pic_state.map_local_to_containing_block);
+                            println!("\tpicture context {:?}", pic_context);
+                        }
+                    }
+                }
+            }
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
                 .spatial_nodes[prim_instance.spatial_node_index.0];
 
+            if !spatial_node.invertible {
+                if cfg!(debug_assertions) && is_chased {
+                    println!("\tculled for the scroll node transform being invertible");
+                }
+                continue;
+            }
+
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
                 spatial_node,
                 prim_instance.spatial_node_index,
             );
 
-            // Do some basic checks first, that can early out
-            // without even knowing the local rect.
-            if !is_backface_visible && spatial_node.world_content_transform.is_backface_visible() {
-                if cfg!(debug_assertions) && is_chased {
-                    println!("\tculled for not having visible back faces");
-                }
-                continue;
-            }
-
-            if !spatial_node.invertible {
-                if cfg!(debug_assertions) && is_chased {
-                    println!("\tculled for the scroll node transform being invertible");
-                }
-                continue;
-            }
-
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
             pic_state.map_local_to_pic.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
@@ -2049,18 +2096,18 @@ impl PrimitiveStore {
             if self.prepare_prim_for_render(
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 display_list,
+                plane_split_anchor,
                 is_chased,
-                current_pic_rect,
             ) {
                 frame_state.profile_counters.visible_primitives.inc();
             }
         }
     }
 }
 
 fn build_gradient_stops_request(
@@ -2456,16 +2503,17 @@ impl Primitive {
         prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         pictures: &mut [PicturePrimitive],
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
+        plane_split_anchor: usize,
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
@@ -2965,24 +3013,34 @@ impl Primitive {
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
                     BrushKind::Picture { pic_index, .. } => {
                         let pic = &mut pictures[pic_index.0];
-                        if !pic.prepare_for_render(
+                        if pic.prepare_for_render(
                             pic_index,
                             prim_instance,
                             &self.local_rect,
                             pic_state,
                             frame_context,
                             frame_state,
                         ) {
+                            if let Some(ref mut splitter) = pic_state.plane_splitter {
+                                PicturePrimitive::add_split_plane(
+                                    splitter,
+                                    frame_state.transforms,
+                                    prim_instance,
+                                    self.local_rect,
+                                    plane_split_anchor,
+                                );
+                            }
+                        } else {
                             prim_instance.clipped_world_rect = None;
                         }
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1050,18 +1050,18 @@ pub struct CompositeOps {
 impl CompositeOps {
     pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> Self {
         CompositeOps {
             filters,
             mix_blend_mode,
         }
     }
 
-    pub fn count(&self) -> usize {
-        self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
+    pub fn is_empty(&self) -> bool {
+        self.filters.is_empty() && self.mix_blend_mode.is_none()
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -567,17 +567,20 @@ impl<Src, Dst> FastTransform<Src, Dst> {
                 FastTransform::with_transform(transform.pre_translate(other_offset.to_3d()))
         }
     }
 
     #[inline(always)]
     pub fn is_backface_visible(&self) -> bool {
         match *self {
             FastTransform::Offset(..) => false,
-            FastTransform::Transform { ref transform, .. } => transform.is_backface_visible(),
+            FastTransform::Transform { inverse: None, .. } => false,
+            //TODO: fix this properly by taking "det|M33| * det|M34| > 0"
+            // see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23014
+            FastTransform::Transform { inverse: Some(ref inverse), .. } => inverse.m33 < 0.0,
         }
     }
 
     #[inline(always)]
     pub fn transform_point2d(&self, point: &TypedPoint2D<f32, Src>) -> Option<TypedPoint2D<f32, Dst>> {
         match *self {
             FastTransform::Offset(offset) => {
                 let new_point = *point + offset;
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-9464adaf2634f8d11408b915323d8006474f02e2
+838ce479e6ef8eed44d68e5d283649d0963152b6