Bug 1341968 - Update webrender to commit 501e3d79c8a3019762bd8bd2d00eecf7811a84de. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 27 Feb 2017 10:36:49 -0500
changeset 490126 50e20d329d5e08bd2796d11b6973c53e5836a9bc
parent 490125 991f5724e58f47884ebbdb5c438b53ee1226ab4e
child 490127 10a27c4b6bbd72cfbe7aafe4c6d7d3bb75b4abef
push id47003
push userkgupta@mozilla.com
push dateMon, 27 Feb 2017 17:43:51 +0000
reviewersjrmuizel
bugs1341968
milestone54.0a1
Bug 1341968 - Update webrender to commit 501e3d79c8a3019762bd8bd2d00eecf7811a84de. r?jrmuizel MozReview-Commit-ID: D5fpoHDBLIQ
gfx/doc/README.webrender
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/freelist.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/types.rs
gfx/webrender_traits/src/units.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: edc74274d28b1fa1229a1d1ea05027f57172b992
+Latest Commit: 501e3d79c8a3019762bd8bd2d00eecf7811a84de
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1626,17 +1626,22 @@ impl Device {
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
-        assert!(data.len() as u32 == bpp * row_length * height);
+        // Take the stride into account for all rows, except the last one.
+        let len = bpp * row_length * (height - 1)
+                + width * bpp;
+
+        assert!(data.len() as u32 >= len);
+        let data = &data[0..len as usize];
 
         if let Some(..) = stride {
             gl::pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture_id);
         self.update_image_for_2d_texture(texture_id.target,
                                          x0 as gl::GLint,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -4,36 +4,38 @@
 
 use app_units::Au;
 use fnv::FnvHasher;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, AxisDirection};
 use internal_types::{LowLevelFilterOp};
 use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use clip_scroll_node::ClipScrollNode;
+use clip_scroll_tree::{ClipScrollTree, ScrollStates};
+use profiler::TextureCacheProfileCounters;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
-use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
 use webrender_traits::{AuxiliaryLists, ClipRegion, ColorF, DisplayItem, Epoch, FilterOp};
-use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform};
+use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, LayoutTransform, TileOffset};
 use webrender_traits::{MixBlendMode, PipelineId, ScrollEventPhase, ScrollLayerId, ScrollLayerState};
 use webrender_traits::{ScrollLocation, ScrollPolicy, ServoScrollRootId, SpecificDisplayItem};
-use webrender_traits::{StackingContext, WorldPoint};
+use webrender_traits::{StackingContext, WorldPoint, ImageDisplayItem, DeviceUintSize};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: &'a mut FrameBuilder,
+    resource_cache: &'a mut ResourceCache,
 }
 
 // TODO: doc
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
     pub pipeline_auxiliary_lists: AuxiliaryListsMap,
     id: FrameId,
@@ -221,17 +223,17 @@ impl Frame {
     pub fn tick_scrolling_bounce_animations(&mut self) {
         self.clip_scroll_tree.tick_scrolling_bounce_animations();
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.clip_scroll_tree.discard_frame_state_for_pipeline(pipeline_id);
     }
 
-    pub fn create(&mut self, scene: &Scene) {
+    pub fn create(&mut self, scene: &Scene, resource_cache: &mut ResourceCache) {
         let root_pipeline_id = match scene.root_pipeline_id {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return,
         };
 
         let root_pipeline = match scene.pipeline_map.get(&root_pipeline_id) {
             Some(root_pipeline) => root_pipeline,
             None => return,
@@ -271,16 +273,17 @@ impl Frame {
         let mut frame_builder = FrameBuilder::new(root_pipeline.viewport_size,
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext {
                 scene: scene,
                 builder: &mut frame_builder,
+                resource_cache: resource_cache
             };
 
             let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
             let reference_frame_id = self.clip_scroll_tree.root_reference_frame_id();
             let topmost_scroll_layer_id = self.clip_scroll_tree.topmost_scroll_layer_id();
             debug_assert!(reference_frame_id != topmost_scroll_layer_id);
 
             let viewport_rect = LayerRect::new(LayerPoint::zero(), root_pipeline.viewport_size);
@@ -542,23 +545,32 @@ impl Frame {
                          level: i32) {
         while let Some(item) = traversal.next() {
             match item.item {
                 SpecificDisplayItem::WebGL(ref info) => {
                     context.builder.add_webgl_rectangle(item.rect,
                                                         &item.clip, info.context_id);
                 }
                 SpecificDisplayItem::Image(ref info) => {
-                    context.builder.add_image(item.rect,
-                                              &item.clip,
-                                              &info.stretch_size,
-                                              &info.tile_spacing,
-                                              None,
-                                              info.image_key,
-                                              info.image_rendering);
+                    let image = context.resource_cache.get_image_properties(info.image_key);
+                    if let Some(tile_size) = image.tiling {
+                        // The image resource is tiled. We have to generate an image primitive
+                        // for each tile.
+                        let image_size = DeviceUintSize::new(image.descriptor.width, image.descriptor.height);
+                        self.decompose_tiled_image(context, &item, info, image_size, tile_size as u32);
+                    } else {
+                        context.builder.add_image(item.rect,
+                                                  &item.clip,
+                                                  &info.stretch_size,
+                                                  &info.tile_spacing,
+                                                  None,
+                                                  info.image_key,
+                                                  info.image_rendering,
+                                                  None);
+                    }
                 }
                 SpecificDisplayItem::YuvImage(ref info) => {
                     context.builder.add_yuv_image(item.rect,
                                                   &item.clip,
                                                   info.y_image_key,
                                                   info.u_image_key,
                                                   info.v_image_key,
                                                   info.color_space);
@@ -641,39 +653,230 @@ impl Frame {
                                         layer_relative_transform);
                 }
                 SpecificDisplayItem::PopStackingContext |
                 SpecificDisplayItem::PopScrollLayer => return,
             }
         }
     }
 
+    fn decompose_tiled_image(&mut self,
+                             context: &mut FlattenContext,
+                             item: &DisplayItem,
+                             info: &ImageDisplayItem,
+                             image_size: DeviceUintSize,
+                             tile_size: u32) {
+        // The image resource is tiled. We have to generate an image primitive
+        // for each tile.
+        // We need to do this because the image is broken up into smaller tiles in the texture
+        // cache and the image shader is not able to work with this type of sparse representation.
+
+        // The tiling logic works as follows:
+        //
+        //  ###################-+  -+
+        //  #    |    |    |//# |   | image size
+        //  #    |    |    |//# |   |
+        //  #----+----+----+--#-+   |  -+ 
+        //  #    |    |    |//# |   |   | regular tile size
+        //  #    |    |    |//# |   |   |
+        //  #----+----+----+--#-+   |  -+-+
+        //  #////|////|////|//# |   |     | "leftover" height
+        //  ################### |  -+  ---+
+        //  #----+----+----+----+
+        //
+        // In the ascii diagram above, a large image is plit into tiles of almost regular size.
+        // The tiles on the right and bottom edges (hatched in the diagram) are smaller than
+        // the regular tiles and are handled separately in the code see leftover_width/height.
+        // each generated image primitive corresponds to a tile in the texture cache, with the
+        // assumption that the smaller tiles with leftover sizes are sized to fit their own
+        // irregular size in the texture cache.
+
+        // TODO(nical) supporting tiled repeated images isn't implemented yet.
+        // One way to implement this is to have another level of decomposition on top of this one,
+        // and generate a set of image primitive per repetition just like we have a primitive
+        // per tile here.
+        //
+        // For the case where we don't tile along an axis, we can still perform the repetition in
+        // the shader (for this particular axis), and it is worth special-casing for this to avoid
+        // generating many primitives.
+        // This can happen with very tall and thin images used as a repeating background.
+        // Apparently web authors do that...
+
+        let mut stretch_size = info.stretch_size;
+
+        let mut repeat_x = false;
+        let mut repeat_y = false;
+
+        if stretch_size.width < item.rect.size.width {
+            if image_size.width < tile_size {
+                // we don't actually tile in this dimmension so repeating can be done in the shader.
+                repeat_x = true;
+            } else {
+                println!("Unimplemented! repeating a tiled image (x axis)");
+                stretch_size.width = item.rect.size.width;
+            }
+        }
+
+        if stretch_size.height < item.rect.size.height {
+                // we don't actually tile in this dimmension so repeating can be done in the shader.
+            if image_size.height < tile_size {
+                repeat_y = true;
+            } else {
+                println!("Unimplemented! repeating a tiled image (y axis)");
+                stretch_size.height = item.rect.size.height;
+            }
+        }
+
+        let tile_size_f32 = tile_size as f32;
+
+        // Note: this rounds down so it excludes the partially filled tiles on the right and
+        // bottom edges (we handle them separately below).
+        let num_tiles_x = (image_size.width / tile_size) as u16;
+        let num_tiles_y = (image_size.height / tile_size) as u16;
+
+        // Ratio between (image space) tile size and image size.
+        let img_dw = tile_size_f32 / (image_size.width as f32);
+        let img_dh = tile_size_f32 / (image_size.height as f32);
+
+        // Strected size of the tile in layout space.
+        let stretched_tile_size = LayerSize::new(
+            img_dw * stretch_size.width,
+            img_dh * stretch_size.height
+        );
+
+        // The size in pixels of the tiles on the right and bottom edges, smaller
+        // than the regular tile size if the image is not a multiple of the tile size.
+        // Zero means the image size is a multiple of the tile size.
+        let leftover = DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
+
+        for ty in 0..num_tiles_y {
+            for tx in 0..num_tiles_x {
+                self.add_tile_primitive(context, item, info,
+                                        TileOffset::new(tx, ty),
+                                        stretched_tile_size,
+                                        1.0, 1.0,
+                                        repeat_x, repeat_y);
+            }
+            if leftover.width != 0 {
+                // Tiles on the right edge that are smaller than the tile size.
+                self.add_tile_primitive(context, item, info,
+                                        TileOffset::new(num_tiles_x, ty),
+                                        stretched_tile_size,
+                                        (leftover.width as f32) / tile_size_f32,
+                                        1.0,
+                                        repeat_x, repeat_y);
+            }
+        }
+
+        if leftover.height != 0 {
+            for tx in 0..num_tiles_x {
+                // Tiles on the bottom edge that are smaller than the tile size.
+                self.add_tile_primitive(context, item, info,
+                                        TileOffset::new(tx, num_tiles_y),
+                                        stretched_tile_size,
+                                        1.0,
+                                        (leftover.height as f32) / tile_size_f32,
+                                        repeat_x, repeat_y);
+            }
+
+            if leftover.width != 0 {
+                // Finally, the bottom-right tile with a "leftover" size.
+                self.add_tile_primitive(context, item, info,
+                                        TileOffset::new(num_tiles_x, num_tiles_y),
+                                        stretched_tile_size,
+                                        (leftover.width as f32) / tile_size_f32,
+                                        (leftover.height as f32) / tile_size_f32,
+                                        repeat_x, repeat_y);
+            }
+        }
+    }
+
+    fn add_tile_primitive(&mut self,
+                          context: &mut FlattenContext,
+                          item: &DisplayItem,
+                          info: &ImageDisplayItem,
+                          tile_offset: TileOffset,
+                          stretched_tile_size: LayerSize,
+                          tile_ratio_width: f32,
+                          tile_ratio_height: f32,
+                          repeat_x: bool,
+                          repeat_y: bool) {
+        // If the the image is tiled along a given axis, we can't have the shader compute
+        // the image repetition pattern. In this case we base the primitive's rectangle size
+        // on the stretched tile size which effectively cancels the repetion (and repetition
+        // has to be emulated by generating more primitives).
+        // If the image is not tiling along this axis, we can perform the repetition in the
+        // shader. in this case we use the item's size in the primitive (on that particular
+        // axis).
+        // See the repeat_x/y code below.
+
+        let stretched_size = LayerSize::new(
+            stretched_tile_size.width * tile_ratio_width,
+            stretched_tile_size.height * tile_ratio_height,
+        );
+
+        let mut prim_rect = LayerRect::new(
+            item.rect.origin + LayerPoint::new(
+                tile_offset.x as f32 * stretched_tile_size.width,
+                tile_offset.y as f32 * stretched_tile_size.height,
+            ),
+            stretched_size,
+        );
+
+        if repeat_x {
+            assert_eq!(tile_offset.x, 0);
+            prim_rect.size.width = item.rect.size.width;
+        }
+
+        if repeat_y {
+            assert_eq!(tile_offset.y, 0);
+            prim_rect.size.height = item.rect.size.height;
+        }
+
+        // Fix up the primitive's rect if it overflows the original item rect.
+        if let Some(prim_rect) = prim_rect.intersection(&item.rect) {
+            context.builder.add_image(prim_rect,
+                                      &item.clip,
+                                      &stretched_size,
+                                      &info.tile_spacing,
+                                      None,
+                                      info.image_key,
+                                      info.image_rendering,
+                                      Some(tile_offset));
+        }
+    }
+
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  auxiliary_lists_map: &AuxiliaryListsMap,
-                 device_pixel_ratio: f32)
+                 device_pixel_ratio: f32,
+                 texture_cache_profile: &mut TextureCacheProfileCounters)
                  -> RendererFrame {
         self.clip_scroll_tree.update_all_node_transforms();
         let frame = self.build_frame(resource_cache,
                                      auxiliary_lists_map,
-                                     device_pixel_ratio);
+                                     device_pixel_ratio,
+                                     texture_cache_profile);
         resource_cache.expire_old_resources(self.id);
         frame
     }
 
     fn build_frame(&mut self,
                    resource_cache: &mut ResourceCache,
                    auxiliary_lists_map: &AuxiliaryListsMap,
-                   device_pixel_ratio: f32) -> RendererFrame {
+                   device_pixel_ratio: f32,
+                   texture_cache_profile: &mut TextureCacheProfileCounters)
+                   -> RendererFrame {
         let mut frame_builder = self.frame_builder.take();
         let frame = frame_builder.as_mut().map(|builder|
             builder.build(resource_cache,
                           self.id,
                           &self.clip_scroll_tree,
                           auxiliary_lists_map,
-                          device_pixel_ratio)
+                          device_pixel_ratio,
+                          texture_cache_profile)
         );
         self.frame_builder = frame_builder;
 
         let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
         RendererFrame::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
     }
 }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -9,31 +9,31 @@ use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
 use mask_cache::{ClipSource, MaskCacheInfo};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
 use prim_store::{TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
-use profiler::FrameProfileCounters;
+use profiler::{FrameProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
 use render_task::RenderTaskLocation;
 use resource_cache::ResourceCache;
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, ScrollLayer};
 use tiling::{ScrollLayerIndex, StackingContext, StackingContextIndex};
 use util::{self, pack_as_float, rect_from_points_f, subtract_rect, TransformedRect};
 use util::{RectHelpers, TransformedRectKind};
 use webrender_traits::{as_scroll_parent_rect, BorderDetails, BorderDisplayItem, BorderSide, BorderStyle};
 use webrender_traits::{BoxShadowClipMode, ClipRegion, ColorF, device_length, DeviceIntPoint};
-use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintSize, ExtendMode, FontKey};
+use webrender_traits::{DeviceIntRect, DeviceIntSize, DeviceUintSize, ExtendMode, FontKey, TileOffset};
 use webrender_traits::{FontRenderMode, GlyphOptions, ImageKey, ImageRendering, ItemRange};
 use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
 use webrender_traits::{RepeatMode, ScrollLayerId, ScrollLayerPixel, WebGLContextId, YuvColorSpace};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
@@ -412,17 +412,18 @@ impl FrameBuilder {
 
                 for segment in segments {
                     self.add_image(segment.geom_rect,
                                    clip_region,
                                    &segment.stretch_size,
                                    &segment.tile_spacing,
                                    Some(segment.sub_rect),
                                    border.image_key,
-                                   ImageRendering::Auto);
+                                   ImageRendering::Auto,
+                                   None);
                 }
             }
             BorderDetails::Normal(ref border) => {
                 let radius = &border.radius;
                 let left = &border.left;
                 let right = &border.right;
                 let top = &border.top;
                 let bottom = &border.bottom;
@@ -710,18 +711,23 @@ impl FrameBuilder {
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
         // The local space box shadow rect. It is the element rect
         // translated by the box shadow offset and inflated by the
         // box shadow spread.
+        let inflate_amount = match clip_mode {
+            BoxShadowClipMode::Outset | BoxShadowClipMode::None => spread_radius,
+            BoxShadowClipMode::Inset => -spread_radius,
+        };
+
         let bs_rect = box_bounds.translate(box_offset)
-                                .inflate(spread_radius, spread_radius);
+                                .inflate(inflate_amount, inflate_amount);
 
         // Get the outer rectangle, based on the blur radius.
         let outside_edge_size = 2.0 * blur_radius;
         let inside_edge_size = outside_edge_size.max(border_radius);
         let edge_size = outside_edge_size + inside_edge_size;
         let outer_rect = bs_rect.inflate(outside_edge_size, outside_edge_size);
 
         // Box shadows are often used for things like text underline and other
@@ -828,20 +834,22 @@ impl FrameBuilder {
 
     pub fn add_image(&mut self,
                      rect: LayerRect,
                      clip_region: &ClipRegion,
                      stretch_size: &LayerSize,
                      tile_spacing: &LayerSize,
                      sub_rect: Option<TexelRect>,
                      image_key: ImageKey,
-                     image_rendering: ImageRendering) {
+                     image_rendering: ImageRendering,
+                     tile: Option<TileOffset>) {
         let prim_cpu = ImagePrimitiveCpu {
             kind: ImagePrimitiveKind::Image(image_key,
                                             image_rendering,
+                                            tile,
                                             *tile_spacing),
             color_texture_id: SourceTexture::Invalid,
             resource_address: GpuStoreAddress(0),
             sub_rect: sub_rect,
         };
 
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: *stretch_size,
@@ -1079,17 +1087,19 @@ impl FrameBuilder {
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &ClipScrollTree,
                  auxiliary_lists_map: &AuxiliaryListsMap,
-                 device_pixel_ratio: f32) -> Frame {
+                 device_pixel_ratio: f32,
+                 texture_cache_profile: &mut TextureCacheProfileCounters)
+                 -> Frame {
         profile_scope!("build");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters.total_primitives.set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(frame_id);
 
         let screen_rect = DeviceIntRect::new(
@@ -1116,17 +1126,17 @@ impl FrameBuilder {
                                                       device_pixel_ratio);
 
         let (main_render_task, static_render_task_count) = self.build_render_task();
         let mut render_tasks = RenderTaskCollection::new(static_render_task_count);
 
         let mut required_pass_count = 0;
         main_render_task.max_depth(0, &mut required_pass_count);
 
-        resource_cache.block_until_all_resources_added();
+        resource_cache.block_until_all_resources_added(texture_cache_profile);
 
         for scroll_layer in self.scroll_layer_store.iter() {
             if let Some(ref clip_info) = scroll_layer.clip_cache_info {
                 self.prim_store.resolve_clip_cache(clip_info, resource_cache);
             }
         }
 
         let deferred_resolves = self.prim_store.resolve_primitives(resource_cache,
@@ -1356,17 +1366,17 @@ impl<'a> LayerRectCalculationAndCullingP
         clip_info.update(&scroll_layer.clip_source,
                          &packed_layer.transform,
                          &mut self.frame_builder.prim_store.gpu_data32,
                          self.device_pixel_ratio,
                          auxiliary_lists);
 
         if let Some(mask) = scroll_layer.clip_source.image_mask() {
             // We don't add the image mask for resolution, because layer masks are resolved later.
-            self.resource_cache.request_image(mask.image, ImageRendering::Auto);
+            self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
         }
     }
 
     fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
         self.stacking_context_stack.push(stacking_context_index);
 
         // Reset bounding rect to zero. We will calculate it as we collect primitives
         // from various scroll layers. In handle_pop_stacking_context , we use this to
--- a/gfx/webrender/src/freelist.rs
+++ b/gfx/webrender/src/freelist.rs
@@ -10,41 +10,63 @@ pub struct FreeListItemId(u32);
 impl FreeListItemId {
     #[inline]
     pub fn new(value: u32) -> FreeListItemId {
         FreeListItemId(value)
     }
 
     #[inline]
     pub fn value(&self) -> u32 {
-        let FreeListItemId(value) = *self;
-        value
+        self.0
     }
 }
 
 pub trait FreeListItem {
+    fn take(&mut self) -> Self;
     fn next_free_id(&self) -> Option<FreeListItemId>;
     fn set_next_free_id(&mut self, id: Option<FreeListItemId>);
 }
 
+struct FreeListIter<'a, T: 'a> {
+    items: &'a [T],
+    cur_index: Option<FreeListItemId>,
+}
+
+impl<'a, T: FreeListItem> Iterator for FreeListIter<'a, T> {
+    type Item = FreeListItemId;
+    fn next(&mut self) -> Option<Self::Item> {
+        self.cur_index.map(|free_id| {
+            self.cur_index = self.items[free_id.0 as usize].next_free_id();
+            free_id
+        })
+    }
+}
+
 pub struct FreeList<T> {
     items: Vec<T>,
     first_free_index: Option<FreeListItemId>,
     alloc_count: usize,
 }
 
 impl<T: FreeListItem> FreeList<T> {
     pub fn new() -> FreeList<T> {
         FreeList {
             items: Vec::new(),
             first_free_index: None,
             alloc_count: 0,
         }
     }
 
+    fn free_iter(&self) -> FreeListIter<T> {
+        FreeListIter {
+            items: &self.items,
+            cur_index: self.first_free_index,
+        }
+    }
+
     pub fn insert(&mut self, item: T) -> FreeListItemId {
         self.alloc_count += 1;
         match self.first_free_index {
             Some(free_index) => {
                 let FreeListItemId(index) = free_index;
                 let free_item = &mut self.items[index as usize];
                 self.first_free_index = free_item.next_free_id();
                 *free_item = item;
@@ -53,69 +75,49 @@ impl<T: FreeListItem> FreeList<T> {
             None => {
                 let item_id = FreeListItemId(self.items.len() as u32);
                 self.items.push(item);
                 item_id
             }
         }
     }
 
-    #[allow(dead_code)]
-    fn assert_not_in_free_list(&self, id: FreeListItemId) {
-        let FreeListItemId(id) = id;
-        let mut next_free_id = self.first_free_index;
-
-        while let Some(free_id) = next_free_id {
-            let FreeListItemId(index) = free_id;
-            assert!(index != id);
-            let free_item = &self.items[index as usize];
-            next_free_id = free_item.next_free_id();
-        }
-    }
-
     pub fn get(&self, id: FreeListItemId) -> &T {
-        //self.assert_not_in_free_list(id);
-
-        let FreeListItemId(index) = id;
-        &self.items[index as usize]
+        debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
+        &self.items[id.0 as usize]
     }
 
     pub fn get_mut(&mut self, id: FreeListItemId) -> &mut T {
-        //self.assert_not_in_free_list(id);
-
-        let FreeListItemId(index) = id;
-        &mut self.items[index as usize]
+        debug_assert_eq!(self.free_iter().find(|&fid| fid==id), None);
+        &mut self.items[id.0 as usize]
     }
 
     #[allow(dead_code)]
     pub fn len(&self) -> usize {
         self.alloc_count
     }
 
-    // TODO(gw): Actually free items from the texture cache!!
-    #[allow(dead_code)]
-    pub fn free(&mut self, id: FreeListItemId) {
+    pub fn free(&mut self, id: FreeListItemId) -> T {
         self.alloc_count -= 1;
         let FreeListItemId(index) = id;
         let item = &mut self.items[index as usize];
+        let data = item.take();
         item.set_next_free_id(self.first_free_index);
         self.first_free_index = Some(id);
+        data
     }
 
     pub fn for_each_item<F>(&mut self, f: F) where F: Fn(&mut T) {
-        let mut free_ids = HashSet::new();
-
-        let mut next_free_id = self.first_free_index;
-        while let Some(free_id) = next_free_id {
-            free_ids.insert(free_id);
-            let FreeListItemId(index) = free_id;
-            let free_item = &self.items[index as usize];
-            next_free_id = free_item.next_free_id();
-        }
+        //TODO: this could be done much faster. Instead of gathering the free
+        // indices into a set, we could re-order the free list to be ascending.
+        // That is an one-time operation with at most O(nf^2), where
+        //    nf = number of elements in the free list
+        // Then this code would just walk both `items` and the ascending free
+        // list, essentially skipping the free indices for free.
+        let free_ids: HashSet<_> = self.free_iter().collect();
 
         for (index, mut item) in self.items.iter_mut().enumerate() {
-            let id = FreeListItemId(index as u32);
-            if !free_ids.contains(&id) {
+            if !free_ids.contains(&FreeListItemId(index as u32)) {
                 f(&mut item);
             }
         }
     }
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -382,16 +382,17 @@ pub enum TextureUpdateOp {
     },
     Update {
         page_pos_x: u32,    // the texture page position which we want to upload
         page_pos_y: u32,
         width: u32,
         height: u32,
         data: Arc<Vec<u8>>,
         stride: Option<u32>,
+        offset: u32,
     },
     UpdateForExternalBuffer {
         rect: DeviceUintRect,
         id: ExternalImageId,
         stride: Option<u32>,
     },
     Grow {
         width: u32,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -15,17 +15,17 @@ use std::usize;
 use util::TransformedRect;
 use webrender_traits::{AuxiliaryLists, ColorF, ImageKey, ImageRendering, YuvColorSpace};
 use webrender_traits::{ClipRegion, ComplexClipRegion, ItemRange, GlyphKey};
 use webrender_traits::{FontKey, FontRenderMode, WebGLContextId};
 use webrender_traits::{device_length, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceRect, DevicePoint, DeviceSize};
 use webrender_traits::{LayerRect, LayerSize, LayerPoint};
 use webrender_traits::{LayerToWorldTransform, GlyphInstance, GlyphOptions};
-use webrender_traits::{ExtendMode, GradientStop};
+use webrender_traits::{ExtendMode, GradientStop, TileOffset};
 
 pub const CLIP_DATA_GPU_SIZE: usize = 5;
 pub const MASK_DATA_GPU_SIZE: usize = 1;
 
 /// Stores two coordinates in texel space. The coordinates
 /// are stored in texel coordinates because the texture atlas
 /// may grow. Storing them as texel coords and normalizing
 /// the UVs in the vertex shader means nothing needs to be
@@ -131,17 +131,17 @@ pub struct PrimitiveMetadata {
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
 #[derive(Debug)]
 pub enum ImagePrimitiveKind {
-    Image(ImageKey, ImageRendering, LayerSize),
+    Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
     WebGL(WebGLContextId),
 }
 
 #[derive(Debug)]
 pub struct ImagePrimitiveCpu {
     pub kind: ImagePrimitiveKind,
     pub color_texture_id: SourceTexture,
     pub resource_address: GpuStoreAddress,
@@ -833,17 +833,17 @@ impl PrimitiveStore {
 
         PrimitiveIndex(prim_index)
     }
 
     fn resolve_clip_cache_internal(gpu_data32: &mut VertexDataStore<GpuBlock32>,
                                    clip_info: &MaskCacheInfo,
                                    resource_cache: &ResourceCache) {
         if let Some((ref mask, gpu_address)) = clip_info.image {
-            let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto);
+            let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None);
             let mask_data = gpu_data32.get_slice_mut(gpu_address, MASK_DATA_GPU_SIZE);
             mask_data[0] = GpuBlock32::from(ImageMaskData {
                 uv_rect: DeviceRect::new(cache_item.uv0,
                                          DeviceSize::new(cache_item.uv1.x - cache_item.uv0.x,
                                                          cache_item.uv1.y - cache_item.uv0.y)),
                 local_rect: mask.rect,
             });
         }
@@ -894,17 +894,17 @@ impl PrimitiveStore {
                     });
 
                     text.color_texture_id = texture_id;
                 }
                 PrimitiveKind::Image => {
                     let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
 
                     let (texture_id, cache_item) = match image_cpu.kind {
-                        ImagePrimitiveKind::Image(image_key, image_rendering, _) => {
+                        ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, _) => {
                             // Check if an external image that needs to be resolved
                             // by the render thread.
                             let image_properties = resource_cache.get_image_properties(image_key);
 
                             match image_properties.external_id {
                                 Some(external_id) => {
                                     // This is an external texture - we will add it to
                                     // the deferred resolves list to be patched by
@@ -912,17 +912,17 @@ impl PrimitiveStore {
                                     deferred_resolves.push(DeferredResolve {
                                         resource_address: image_cpu.resource_address,
                                         image_properties: image_properties,
                                     });
 
                                     (SourceTexture::External(external_id), None)
                                 }
                                 None => {
-                                    let cache_item = resource_cache.get_cached_image(image_key, image_rendering);
+                                    let cache_item = resource_cache.get_cached_image(image_key, image_rendering, tile_offset);
                                     (cache_item.texture_id, Some(cache_item))
                                 }
                             }
                         }
                         ImagePrimitiveKind::WebGL(context_id) => {
                             let cache_item = resource_cache.get_webgl_texture(&context_id);
                             (cache_item.texture_id, Some(cache_item))
                         }
@@ -947,31 +947,31 @@ impl PrimitiveStore {
                 }
                 PrimitiveKind::YuvImage => {
                     let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
                     let image_gpu: &mut YuvImagePrimitiveGpu = unsafe {
                         mem::transmute(self.gpu_data64.get_mut(metadata.gpu_prim_index))
                     };
 
                     if image_cpu.y_texture_id == SourceTexture::Invalid {
-                        let y_cache_item = resource_cache.get_cached_image(image_cpu.y_key, ImageRendering::Auto);
+                        let y_cache_item = resource_cache.get_cached_image(image_cpu.y_key, ImageRendering::Auto, None);
                         image_cpu.y_texture_id = y_cache_item.texture_id;
                         image_gpu.y_uv0 = y_cache_item.uv0;
                         image_gpu.y_uv1 = y_cache_item.uv1;
                     }
 
                     if image_cpu.u_texture_id == SourceTexture::Invalid {
-                        let u_cache_item = resource_cache.get_cached_image(image_cpu.u_key, ImageRendering::Auto);
+                        let u_cache_item = resource_cache.get_cached_image(image_cpu.u_key, ImageRendering::Auto, None);
                         image_cpu.u_texture_id = u_cache_item.texture_id;
                         image_gpu.u_uv0 = u_cache_item.uv0;
                         image_gpu.u_uv1 = u_cache_item.uv1;
                     }
 
                     if image_cpu.v_texture_id == SourceTexture::Invalid {
-                        let v_cache_item = resource_cache.get_cached_image(image_cpu.v_key, ImageRendering::Auto);
+                        let v_cache_item = resource_cache.get_cached_image(image_cpu.v_key, ImageRendering::Auto, None);
                         image_cpu.v_texture_id = v_cache_item.texture_id;
                         image_gpu.v_uv0 = v_cache_item.uv0;
                         image_gpu.v_uv1 = v_cache_item.uv1;
                     }
                 }
             }
         }
 
@@ -1039,17 +1039,17 @@ impl PrimitiveStore {
 
         if let Some(ref mut clip_info) = metadata.clip_cache_info {
             clip_info.update(&metadata.clip_source,
                              layer_transform,
                              &mut self.gpu_data32,
                              device_pixel_ratio,
                              auxiliary_lists);
             if let &ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }) = metadata.clip_source.as_ref() {
-                resource_cache.request_image(mask.image, ImageRendering::Auto);
+                resource_cache.request_image(mask.image, ImageRendering::Auto, None);
                 prim_needs_resolve = true;
             }
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
             PrimitiveKind::Border  => {}
             PrimitiveKind::BoxShadow => {
@@ -1164,18 +1164,18 @@ impl PrimitiveStore {
                                               text.render_mode,
                                               text.glyph_options);
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
 
                 prim_needs_resolve = true;
                 match image_cpu.kind {
-                    ImagePrimitiveKind::Image(image_key, image_rendering, tile_spacing) => {
-                        resource_cache.request_image(image_key, image_rendering);
+                    ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => {
+                        resource_cache.request_image(image_key, image_rendering, tile_offset);
 
                         // TODO(gw): This doesn't actually need to be calculated each frame.
                         // It's cheap enough that it's not worth introducing a cache for images
                         // right now, but if we introduce a cache for images for some other
                         // reason then we might as well cache this with it.
                         let image_properties = resource_cache.get_image_properties(image_key);
                         metadata.is_opaque = image_properties.descriptor.is_opaque &&
                                              tile_spacing.width == 0.0 &&
@@ -1183,19 +1183,19 @@ impl PrimitiveStore {
                     }
                     ImagePrimitiveKind::WebGL(..) => {}
                 }
             }
             PrimitiveKind::YuvImage => {
                 let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
                 prim_needs_resolve = true;
 
-                resource_cache.request_image(image_cpu.y_key, ImageRendering::Auto);
-                resource_cache.request_image(image_cpu.u_key, ImageRendering::Auto);
-                resource_cache.request_image(image_cpu.v_key, ImageRendering::Auto);
+                resource_cache.request_image(image_cpu.y_key, ImageRendering::Auto, None);
+                resource_cache.request_image(image_cpu.u_key, ImageRendering::Auto, None);
+                resource_cache.request_image(image_cpu.v_key, ImageRendering::Auto, None);
 
                 // TODO(nical): Currently assuming no tile_spacing for yuv images.
                 metadata.is_opaque = true;
             }
             PrimitiveKind::AlignedGradient => {
                 let gradient = &mut self.cpu_gradients[metadata.cpu_prim_index.0];
                 if gradient.cache_dirty {
                     let src_stops = auxiliary_lists.gradient_stops(&gradient.stops_range);
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -252,28 +252,47 @@ impl FrameProfileCounters {
             visible_primitives: IntProfileCounter::new("Visible Primitives"),
             passes: IntProfileCounter::new("Passes"),
             targets: IntProfileCounter::new("Render Targets"),
         }
     }
 }
 
 #[derive(Clone)]
+pub struct TextureCacheProfileCounters {
+    pub pages_a8: ResourceProfileCounter,
+    pub pages_rgb8: ResourceProfileCounter,
+    pub pages_rgba8: ResourceProfileCounter,
+}
+
+impl TextureCacheProfileCounters {
+    pub fn new() -> TextureCacheProfileCounters {
+        TextureCacheProfileCounters {
+            pages_a8: ResourceProfileCounter::new("Texture A8 cached pages"),
+            pages_rgb8: ResourceProfileCounter::new("Texture RGB8 cached pages"),
+            pages_rgba8: ResourceProfileCounter::new("Texture RGBA8 cached pages"),
+        }
+    }
+}
+
+#[derive(Clone)]
 pub struct BackendProfileCounters {
     pub font_templates: ResourceProfileCounter,
     pub image_templates: ResourceProfileCounter,
     pub total_time: TimeProfileCounter,
+    pub texture_cache: TextureCacheProfileCounters,
 }
 
 impl BackendProfileCounters {
     pub fn new() -> BackendProfileCounters {
         BackendProfileCounters {
             font_templates: ResourceProfileCounter::new("Font Templates"),
             image_templates: ResourceProfileCounter::new("Image Templates"),
             total_time: TimeProfileCounter::new("Backend CPU Time", false),
+            texture_cache: TextureCacheProfileCounters::new(),
         }
     }
 
     pub fn reset(&mut self) {
         self.total_time.reset();
     }
 }
 
@@ -637,16 +656,22 @@ impl Profiler {
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.font_templates,
             &backend_profile.image_templates,
         ], debug_renderer, true);
 
         self.draw_counters(&[
+            &backend_profile.texture_cache.pages_a8,
+            &backend_profile.texture_cache.pages_rgb8,
+            &backend_profile.texture_cache.pages_rgba8,
+        ], debug_renderer, true);
+
+        self.draw_counters(&[
             &renderer_profile.draw_calls,
             &renderer_profile.vertices,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.total_time,
             &renderer_timers.cpu_time,
             &renderer_timers.gpu_time,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -2,17 +2,17 @@
  * 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 byteorder::{LittleEndian, ReadBytesExt};
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use internal_types::{FontTemplate, GLContextHandleWrapper, GLContextWrapper};
 use internal_types::{SourceTexture, ResultMsg, RendererFrame};
-use profiler::BackendProfileCounters;
+use profiler::{BackendProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::collections::HashMap;
 use std::io::{Cursor, Read};
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
@@ -91,18 +91,17 @@ impl RenderBackend {
             current_bound_webgl_context_id: None,
             recorder: recorder,
             main_thread_dispatcher: main_thread_dispatcher,
             next_webgl_id: 0,
             vr_compositor_handler: vr_compositor_handler
         }
     }
 
-    pub fn run(&mut self) {
-        let mut profile_counters = BackendProfileCounters::new();
+    pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
 
         loop {
             let msg = self.api_rx.recv();
             profile_scope!("handle_msg");
             match msg {
                 Ok(msg) => {
                     if let Some(ref mut r) = self.recorder {
@@ -121,21 +120,21 @@ impl RenderBackend {
                         ApiMsg::GetGlyphDimensions(glyph_keys, tx) => {
                             let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
                             for glyph_key in &glyph_keys {
                                 let glyph_dim = self.resource_cache.get_glyph_dimensions(glyph_key);
                                 glyph_dimensions.push(glyph_dim);
                             };
                             tx.send(glyph_dimensions).unwrap();
                         }
-                        ApiMsg::AddImage(id, descriptor, data) => {
+                        ApiMsg::AddImage(id, descriptor, data, tiling) => {
                             if let ImageData::Raw(ref bytes) = data {
                                 profile_counters.image_templates.inc(bytes.len());
                             }
-                            self.resource_cache.add_image_template(id, descriptor, data);
+                            self.resource_cache.add_image_template(id, descriptor, data, tiling);
                         }
                         ApiMsg::UpdateImage(id, descriptor, bytes) => {
                             self.resource_cache.update_image_template(id, descriptor, bytes);
                         }
                         ApiMsg::DeleteImage(id) => {
                             self.resource_cache.delete_image_template(id);
                         }
                         ApiMsg::CloneApi(sender) => {
@@ -208,57 +207,66 @@ impl RenderBackend {
                             if self.scene.display_lists.get(&pipeline_id).is_none() {
                                 continue;
                             }
 
                             self.build_scene();
                         }
                         ApiMsg::Scroll(delta, cursor, move_phase) => {
                             profile_scope!("Scroll");
-                            let frame = profile_counters.total_time.profile(|| {
-                                if self.frame.scroll(delta, cursor, move_phase) {
-                                    Some(self.render())
-                                } else {
-                                    None
-                                }
-                            });
+                            let frame = {
+                                let counters = &mut profile_counters.texture_cache;
+                                profile_counters.total_time.profile(|| {
+                                    if self.frame.scroll(delta, cursor, move_phase) {
+                                        Some(self.render(counters))
+                                    } else {
+                                        None
+                                    }
+                                })
+                            };
 
                             match frame {
                                 Some(frame) => {
                                     self.publish_frame(frame, &mut profile_counters);
                                     self.notify_compositor_of_new_scroll_frame(true)
                                 }
                                 None => self.notify_compositor_of_new_scroll_frame(false),
                             }
                         }
                         ApiMsg::ScrollLayersWithScrollId(origin, pipeline_id, scroll_root_id) => {
                             profile_scope!("ScrollLayersWithScrollId");
-                            let frame = profile_counters.total_time.profile(|| {
-                                if self.frame.scroll_nodes(origin, pipeline_id, scroll_root_id) {
-                                    Some(self.render())
-                                } else {
-                                    None
-                                }
-                            });
+                            let frame = {
+                                let counters = &mut profile_counters.texture_cache;
+                                profile_counters.total_time.profile(|| {
+                                    if self.frame.scroll_nodes(origin, pipeline_id, scroll_root_id) {
+                                        Some(self.render(counters))
+                                    } else {
+                                        None
+                                    }
+                                })
+                            };
 
                             match frame {
                                 Some(frame) => {
                                     self.publish_frame(frame, &mut profile_counters);
                                     self.notify_compositor_of_new_scroll_frame(true)
                                 }
                                 None => self.notify_compositor_of_new_scroll_frame(false),
                             }
 
                         }
                         ApiMsg::TickScrollingBounce => {
                             profile_scope!("TickScrollingBounce");
-                            let frame = profile_counters.total_time.profile(|| {
-                                self.frame.tick_scrolling_bounce_animations();
-                                self.render()
-                            });
+                            let frame = {
+                                let counters = &mut profile_counters.texture_cache;
+                                profile_counters.total_time.profile(|| {
+                                    self.frame.tick_scrolling_bounce_animations();
+                                    self.render(counters)
+                                })
+                            };
 
                             self.publish_frame_and_notify_compositor(frame, &mut profile_counters);
                         }
                         ApiMsg::TranslatePointToLayerSpace(..) => {
                             panic!("unused api - remove from webrender_traits");
                         }
                         ApiMsg::GetScrollLayerState(tx) => {
                             profile_scope!("GetScrollLayerState");
@@ -341,19 +349,22 @@ impl RenderBackend {
                             //           are completed, optimize the internals of
                             //           animated properties to not require a full
                             //           rebuild of the frame!
                             if let Some(property_bindings) = property_bindings {
                                 self.scene.properties.set_properties(property_bindings);
                                 self.build_scene();
                             }
 
-                            let frame = profile_counters.total_time.profile(|| {
-                                self.render()
-                            });
+                            let frame = {
+                                let counters = &mut profile_counters.texture_cache;
+                                profile_counters.total_time.profile(|| {
+                                    self.render(counters)
+                                })
+                            };
                             if self.scene.root_pipeline_id.is_some() {
                                 self.publish_frame_and_notify_compositor(frame, &mut profile_counters);
                                 frame_counter += 1;
                             }
                         }
                         ApiMsg::ExternalEvent(evt) => {
                             let notifier = self.notifier.lock();
                             notifier.unwrap()
@@ -402,23 +413,26 @@ impl RenderBackend {
         // context at the start of a render frame should
         // incur minimal cost.
         for (_, webgl_context) in &self.webgl_contexts {
             webgl_context.make_current();
             webgl_context.apply_command(WebGLCommand::Flush);
             webgl_context.unbind();
         }
 
-        self.frame.create(&self.scene);
+        self.frame.create(&self.scene, &mut self.resource_cache);
     }
 
-    fn render(&mut self) -> RendererFrame {
+    fn render(&mut self,
+              texture_cache_profile: &mut TextureCacheProfileCounters)
+              -> RendererFrame {
         let frame = self.frame.build(&mut self.resource_cache,
                                      &self.scene.pipeline_auxiliary_lists,
-                                     self.device_pixel_ratio);
+                                     self.device_pixel_ratio,
+                                     texture_cache_profile);
 
         frame
     }
 
     fn publish_frame(&mut self,
                      frame: RendererFrame,
                      profile_counters: &mut BackendProfileCounters) {
         let pending_update = self.resource_cache.pending_updates();
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -62,23 +62,22 @@ pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
     pub opaque_items: Vec<AlphaRenderItem>,
     pub alpha_items: Vec<AlphaRenderItem>,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskSegment {
-    // This must match the SEGMENT_ values
-    // in clip_shared.glsl!
+    // This must match the SEGMENT_ values in clip_shared.glsl!
     All = 0,
-    Corner_TopLeft,
-    Corner_TopRight,
-    Corner_BottomLeft,
-    Corner_BottomRight,
+    TopLeftCorner,
+    TopRightCorner,
+    BottomLeftCorner,
+    BottomRightCorner,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskGeometryKind {
     Default,        // Draw the entire rect
     CornersOnly,    // Draw the corners (simple axis aligned mask)
     // TODO(gw): Add more types here (e.g. 4 rectangles outside the inner rect)
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -679,51 +679,42 @@ impl Renderer {
                                      &mut device,
                                      options.precache_shaders)
         };
 
         let device_max_size = device.max_texture_size();
         let max_texture_size = cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size));
 
         let mut texture_cache = TextureCache::new(max_texture_size);
+        let mut backend_profile_counters = BackendProfileCounters::new();
 
         let white_pixels: Vec<u8> = vec![
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
             0xff, 0xff, 0xff, 0xff,
         ];
         let mask_pixels: Vec<u8> = vec![
             0xff, 0xff,
             0xff, 0xff,
         ];
         // TODO: Ensure that the white texture can never get evicted when the cache supports LRU eviction!
         let white_image_id = texture_cache.new_item_id();
         texture_cache.insert(white_image_id,
-                             ImageDescriptor {
-                                width: 2,
-                                height: 2,
-                                stride: None,
-                                format: ImageFormat::RGBA8,
-                                is_opaque: false,
-                             },
+                             ImageDescriptor::new(2, 2, ImageFormat::RGBA8, false),
                              TextureFilter::Linear,
-                             ImageData::Raw(Arc::new(white_pixels)));
+                             ImageData::Raw(Arc::new(white_pixels)),
+                             &mut backend_profile_counters.texture_cache);
 
         let dummy_mask_image_id = texture_cache.new_item_id();
         texture_cache.insert(dummy_mask_image_id,
-                             ImageDescriptor {
-                                width: 2,
-                                height: 2,
-                                stride: None,
-                                format: ImageFormat::A8,
-                                is_opaque: false,
-                             },
+                             ImageDescriptor::new(2, 2, ImageFormat::A8, false),
                              TextureFilter::Linear,
-                             ImageData::Raw(Arc::new(mask_pixels)));
+                             ImageData::Raw(Arc::new(mask_pixels)),
+                             &mut backend_profile_counters.texture_cache);
 
         let dummy_cache_texture_id = device.create_texture_ids(1, TextureTarget::Array)[0];
         device.init_texture(dummy_cache_texture_id,
                             1,
                             1,
                             ImageFormat::RGBA8,
                             TextureFilter::Linear,
                             RenderTargetMode::LayerRenderTarget(1),
@@ -810,17 +801,17 @@ impl Renderer {
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
                                                  blob_image_renderer,
                                                  backend_vr_compositor);
-            backend.run();
+            backend.run(backend_profile_counters);
         })};
 
         let renderer = Renderer {
             result_rx: result_rx,
             device: device,
             current_frame: None,
             pending_texture_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
@@ -1118,23 +1109,23 @@ impl Renderer {
                         let texture_id = self.cache_texture_id_map[update.id.0];
                         self.device.resize_texture(texture_id,
                                                    width,
                                                    height,
                                                    format,
                                                    filter,
                                                    mode);
                     }
-                    TextureUpdateOp::Update { page_pos_x, page_pos_y, width, height, data, stride } => {
+                    TextureUpdateOp::Update { page_pos_x, page_pos_y, width, height, data, stride, offset } => {
                         let texture_id = self.cache_texture_id_map[update.id.0];
                         self.device.update_texture(texture_id,
                                                    page_pos_x,
                                                    page_pos_y,
                                                    width, height, stride,
-                                                   data.as_slice());
+                                                   &data[offset as usize..]);
                     }
                     TextureUpdateOp::UpdateForExternalBuffer { rect, id, stride } => {
                         let handler = self.external_image_handler
                                           .as_mut()
                                           .expect("Found external image, but no handler set!");
                         let device = &mut self.device;
                         let cached_id = self.cache_texture_id_map[update.id.0];
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -3,32 +3,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use device::TextureFilter;
 use fnv::FnvHasher;
 use frame::FrameId;
 use internal_types::{ExternalImageUpdateList, FontTemplate, SourceTexture, TextureUpdateList};
 use platform::font::{FontContext, RasterizedGlyph};
+use profiler::TextureCacheProfileCounters;
 use std::cell::RefCell;
 use std::collections::{HashMap, HashSet};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::{Arc, Barrier, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use thread_profiler::register_thread_with_profiler;
 use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering};
 use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use webrender_traits::{DevicePoint, DeviceIntSize, ImageDescriptor, ColorF};
-use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance};
+use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance, TileOffset, TileSize};
 use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError};
 use threadpool::ThreadPool;
 use euclid::Point2D;
 
 thread_local!(pub static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new()));
 
 type GlyphCache = ResourceClassCache<RenderedGlyphKey, Option<TextureCacheItemId>>;
 
@@ -87,29 +88,31 @@ impl RenderedGlyphKey {
             glyph_options: glyph_options,
         }
     }
 }
 
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_id: Option<ExternalImageId>,
+    pub tiling: Option<TileSize>,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
 
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
     epoch: Epoch,
+    tiling: Option<TileSize>,
 }
 
 struct CachedImageInfo {
     texture_cache_id: TextureCacheItemId,
     epoch: Epoch,
 }
 
 pub struct ResourceClassCache<K,V> {
@@ -172,16 +175,17 @@ impl<K,V> ResourceClassCache<K,V> where 
         }
     }
 }
 
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 struct ImageRequest {
     key: ImageKey,
     rendering: ImageRendering,
+    tile: Option<TileOffset>,
 }
 
 struct GlyphRasterJob {
     key: RenderedGlyphKey,
     result: Option<RasterizedGlyph>,
 }
 
 struct WebGLTexture {
@@ -254,27 +258,29 @@ impl ResourceCache {
             .send(GlyphCacheMsg::AddFont(font_key, template.clone()))
             .unwrap();
         self.font_templates.insert(font_key, template);
     }
 
     pub fn add_image_template(&mut self,
                               image_key: ImageKey,
                               descriptor: ImageDescriptor,
-                              data: ImageData) {
+                              data: ImageData,
+                              mut tiling: Option<TileSize>) {
         if descriptor.width > self.max_texture_size() || descriptor.height > self.max_texture_size() {
-            // TODO: we need to support handle this case gracefully, cf. issue #620.
-            println!("Warning: texture size ({} {}) larger than the maximum size",
-                     descriptor.width, descriptor.height);
+            // We aren't going to be able to upload a texture this big, so tile it, even
+            // if tiling was not requested.
+            tiling = Some(512);
         }
 
         let resource = ImageResource {
             descriptor: descriptor,
             data: data,
             epoch: Epoch(0),
+            tiling: tiling,
         };
 
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(&mut self,
                                  image_key: ImageKey,
                                  descriptor: ImageDescriptor,
@@ -296,16 +302,17 @@ impl ResourceCache {
                 Epoch(0)
             }
         };
 
         let resource = ImageResource {
             descriptor: descriptor,
             data: ImageData::new(bytes),
             epoch: next_epoch,
+            tiling: None,
         };
 
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.image_templates.remove(&image_key);
 
@@ -334,21 +341,26 @@ impl ResourceCache {
     pub fn update_webgl_texture(&mut self, id: WebGLContextId, texture_id: SourceTexture, size: DeviceIntSize) {
         let webgl_texture = self.webgl_textures.get_mut(&id).unwrap();
 
         // Update new texture id and size
         webgl_texture.id = texture_id;
         webgl_texture.size = size;
     }
 
-    pub fn request_image(&mut self, key: ImageKey, rendering: ImageRendering) {
+    pub fn request_image(&mut self,
+                         key: ImageKey,
+                         rendering: ImageRendering,
+                         tile: Option<TileOffset>) {
+
         debug_assert!(self.state == State::AddResources);
         let request = ImageRequest {
             key: key,
             rendering: rendering,
+            tile: tile,
         };
 
         let template = self.image_templates.get(&key).unwrap();
         if let ImageData::Blob(ref data) = template.data {
             if let Some(ref mut renderer) = self.blob_image_renderer {
                 let same_epoch = match self.cached_images.resources.get(&request) {
                     Some(entry) => entry.epoch == template.epoch,
                     None => false,
@@ -467,21 +479,23 @@ impl ResourceCache {
                 *entry.insert(dimensions)
             }
         }
     }
 
     #[inline]
     pub fn get_cached_image(&self,
                             image_key: ImageKey,
-                            image_rendering: ImageRendering) -> CacheItem {
+                            image_rendering: ImageRendering,
+                            tile: Option<TileOffset>) -> CacheItem {
         debug_assert!(self.state == State::QueryResources);
         let key = ImageRequest {
             key: image_key,
             rendering: image_rendering,
+            tile: tile,
         };
         let image_info = &self.cached_images.get(&key, self.current_frame_id);
         let item = self.texture_cache.get(image_info.texture_cache_id);
         CacheItem {
             texture_id: SourceTexture::TextureCache(item.texture_id),
             uv0: DevicePoint::new(item.pixel_rect.top_left.x as f32,
                                   item.pixel_rect.top_left.y as f32),
             uv1: DevicePoint::new(item.pixel_rect.bottom_right.x as f32,
@@ -496,16 +510,17 @@ impl ResourceCache {
             ImageData::ExternalHandle(id) => Some(id),
             // raw and externalBuffer are all use resource_cache.
             ImageData::Raw(..) | ImageData::ExternalBuffer(..) | ImageData::Blob(..) => None,
         };
 
         ImageProperties {
             descriptor: image_template.descriptor,
             external_id: external_id,
+            tiling: image_template.tiling,
         }
     }
 
     #[inline]
     pub fn get_webgl_texture(&self, context_id: &WebGLContextId) -> CacheItem {
         let webgl_texture = &self.webgl_textures[context_id];
         CacheItem {
             texture_id: webgl_texture.id,
@@ -524,17 +539,18 @@ impl ResourceCache {
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert!(self.state == State::Idle);
         self.state = State::AddResources;
         self.current_frame_id = frame_id;
         let glyph_cache = self.cached_glyphs.take().unwrap();
         self.glyph_cache_tx.send(GlyphCacheMsg::BeginFrame(frame_id, glyph_cache)).ok();
     }
 
-    pub fn block_until_all_resources_added(&mut self) {
+    pub fn block_until_all_resources_added(&mut self,
+                                           texture_cache_profile: &mut TextureCacheProfileCounters) {
         profile_scope!("block_until_all_resources_added");
 
         debug_assert!(self.state == State::AddResources);
         self.state = State::QueryResources;
 
         // Tell the glyph cache thread that all glyphs have been requested
         // and block, waiting for any pending glyphs to be rasterized. In the
         // future, we will expand this to have a timeout. If the glyph rasterizing
@@ -558,19 +574,21 @@ impl ResourceCache {
                                 let image_id = self.texture_cache.new_item_id();
                                 self.texture_cache.insert(image_id,
                                                           ImageDescriptor {
                                                               width: glyph.width,
                                                               height: glyph.height,
                                                               stride: None,
                                                               format: ImageFormat::RGBA8,
                                                               is_opaque: false,
+                                                              offset: 0,
                                                           },
                                                           TextureFilter::Linear,
-                                                          ImageData::Raw(Arc::new(glyph.bytes)));
+                                                          ImageData::Raw(Arc::new(glyph.bytes)),
+                                                          texture_cache_profile);
                                 Some(image_id)
                             } else {
                                 None
                             }
                         });
 
                         cache.insert(job.key, image_id, self.current_frame_id);
                     }
@@ -578,26 +596,28 @@ impl ResourceCache {
                     self.cached_glyphs = Some(cache);
                     break;
                 }
             }
         }
 
         let mut image_requests = mem::replace(&mut self.pending_image_requests, Vec::new());
         for request in image_requests.drain(..) {
-            self.finalize_image_request(request, None);
+            self.finalize_image_request(request, None, texture_cache_profile);
         }
 
         let mut blob_image_requests = mem::replace(&mut self.blob_image_requests, HashSet::new());
         if self.blob_image_renderer.is_some() {
             for request in blob_image_requests.drain() {
                 match self.blob_image_renderer.as_mut().unwrap()
                                                 .resolve_blob_image(request.key) {
                     Ok(image) => {
-                        self.finalize_image_request(request, Some(ImageData::new(image.data)));
+                        self.finalize_image_request(request,
+                                                    Some(ImageData::new(image.data)),
+                                                    texture_cache_profile);
                     }
                     // TODO(nical): I think that we should handle these somewhat gracefully,
                     // at least in the out-of-memory scenario.
                     Err(BlobImageError::Oom) => {
                         // This one should be recoverable-ish.
                         panic!("Failed to render a vector image (OOM)");
                     }
                     Err(BlobImageError::InvalidKey) => {
@@ -610,17 +630,20 @@ impl ResourceCache {
                     Err(BlobImageError::Other(msg)) => {
                         panic!("Vector image error {}", msg);
                     }
                 }
             }
         }
     }
 
-    fn finalize_image_request(&mut self, request: ImageRequest, image_data: Option<ImageData>) {
+    fn finalize_image_request(&mut self,
+                              request: ImageRequest,
+                              image_data: Option<ImageData>,
+                              texture_cache_profile: &mut TextureCacheProfileCounters) {
         let image_template = &self.image_templates[&request.key];
         let image_data = image_data.unwrap_or_else(||{
             image_template.data.clone()
         });
 
         match image_template.data {
             ImageData::ExternalHandle(..) => {
                 // external handle doesn't need to update the texture_cache.
@@ -645,20 +668,60 @@ impl ResourceCache {
                     Vacant(entry) => {
                         let image_id = self.texture_cache.new_item_id();
 
                         let filter = match request.rendering {
                             ImageRendering::Pixelated => TextureFilter::Nearest,
                             ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
                         };
 
-                        self.texture_cache.insert(image_id,
-                                                  image_template.descriptor,
-                                                  filter,
-                                                  image_data);
+                        if let Some(tile) = request.tile {
+                            let tile_size = image_template.tiling.unwrap() as u32;
+                            let image_descriptor = image_template.descriptor.clone();
+                            let stride = image_descriptor.compute_stride();
+                            let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
+
+                            // Storage for the tiles on the right and bottom edges is shrunk to
+                            // fit the image data (See decompose_tiled_image in frame.rs).
+                            let actual_width = if (tile.x as u32) < image_descriptor.width / tile_size {
+                                tile_size
+                            } else {
+                                image_descriptor.width % tile_size
+                            };
+
+                            let actual_height = if (tile.y as u32) < image_descriptor.height / tile_size {
+                                tile_size
+                            } else {
+                                image_descriptor.height % tile_size
+                            };
+
+                            let offset = image_descriptor.offset + tile.y as u32 * tile_size * stride
+                                                                 + tile.x as u32 * tile_size * bpp;
+
+                            let tile_descriptor = ImageDescriptor {
+                                width: actual_width,
+                                height: actual_height,
+                                stride: Some(stride),
+                                offset: offset,
+                                format: image_descriptor.format,
+                                is_opaque: image_descriptor.is_opaque,
+                            };
+
+                            self.texture_cache.insert(image_id,
+                                                      tile_descriptor,
+                                                      filter,
+                                                      image_data,
+                                                      texture_cache_profile);
+                        } else {
+                            self.texture_cache.insert(image_id,
+                                                      image_template.descriptor,
+                                                      filter,
+                                                      image_data,
+                                                      texture_cache_profile);
+                        }
 
                         entry.insert(CachedImageInfo {
                             texture_cache_id: image_id,
                             epoch: image_template.epoch,
                         });
                     }
                 }
             }
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -2,16 +2,17 @@
  * 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 device::TextureFilter;
 use fnv::FnvHasher;
 use freelist::{FreeList, FreeListItem, FreeListItemId};
 use internal_types::{TextureUpdate, TextureUpdateOp};
 use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, RectUv};
+use profiler::TextureCacheProfileCounters;
 use std::cmp::{self, Ordering};
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::hash::BuildHasherDefault;
 use std::mem;
 use std::slice::Iter;
 use time;
 use util;
@@ -89,65 +90,39 @@ impl TexturePage {
             let candidate_area = candidate_rect.size.width * candidate_rect.size.height;
             smallest_index_and_area = Some((candidate_index, candidate_area));
             break
         }
 
         smallest_index_and_area.map(|(index, _)| FreeListIndex(bin, index))
     }
 
+    /// Find a suitable rect in the free list. We choose the smallest such rect
+    /// in terms of area (Best-Area-Fit, BAF).
     fn find_index_of_best_rect(&self, requested_dimensions: &DeviceUintSize)
                                -> Option<FreeListIndex> {
-        match FreeListBin::for_size(requested_dimensions) {
-            FreeListBin::Large => {
-                self.find_index_of_best_rect_in_bin(FreeListBin::Large, requested_dimensions)
-            }
-            FreeListBin::Medium => {
-                match self.find_index_of_best_rect_in_bin(FreeListBin::Medium,
-                                                          requested_dimensions) {
-                    Some(index) => Some(index),
-                    None => {
-                        self.find_index_of_best_rect_in_bin(FreeListBin::Large,
-                                                            requested_dimensions)
-                    }
-                }
-            }
-            FreeListBin::Small => {
-                match self.find_index_of_best_rect_in_bin(FreeListBin::Small,
-                                                          requested_dimensions) {
-                    Some(index) => Some(index),
-                    None => {
-                        match self.find_index_of_best_rect_in_bin(FreeListBin::Medium,
-                                                                  requested_dimensions) {
-                            Some(index) => Some(index),
-                            None => {
-                                self.find_index_of_best_rect_in_bin(FreeListBin::Large,
-                                                                    requested_dimensions)
-                            }
-                        }
-                    }
+        let bin = FreeListBin::for_size(requested_dimensions);
+        for &target_bin in &[FreeListBin::Small, FreeListBin::Medium, FreeListBin::Large] {
+            if bin <= target_bin {
+                if let Some(index) = self.find_index_of_best_rect_in_bin(target_bin,
+                                                                         requested_dimensions) {
+                    return Some(index);
                 }
             }
         }
+        None
+    }
+
+    pub fn can_allocate(&self, requested_dimensions: &DeviceUintSize) -> bool {
+        self.find_index_of_best_rect(requested_dimensions).is_some()
     }
 
     pub fn allocate(&mut self,
                     requested_dimensions: &DeviceUintSize) -> Option<DeviceUintPoint> {
-        // First, try to find a suitable rect in the free list. We choose the smallest such rect
-        // in terms of area (Best-Area-Fit, BAF).
-        let mut index = self.find_index_of_best_rect(requested_dimensions);
-
-        // If one couldn't be found and we're dirty, coalesce rects and try again.
-        if index.is_none() && self.dirty {
-            self.coalesce();
-            index = self.find_index_of_best_rect(requested_dimensions)
-        }
-
-        // If a rect still can't be found, fail.
-        let index = match index {
+        let index = match self.find_index_of_best_rect(requested_dimensions) {
             None => return None,
             Some(index) => index,
         };
 
         // Remove the rect from the free list and decide how to guillotine it. We choose the split
         // that results in the single largest area (Min Area Split Rule, MINAS).
         let chosen_rect = self.free_list.remove(index);
         let candidate_free_rect_to_right =
@@ -194,17 +169,21 @@ impl TexturePage {
         // Bump the allocation counter.
         self.allocations += 1;
 
         // Return the result.
         Some(chosen_rect.origin)
     }
 
     #[inline(never)]
-    fn coalesce(&mut self) {
+    pub fn coalesce(&mut self) -> bool {
+        if !self.dirty {
+            return false
+        }
+
         // Iterate to a fixed point or until a timeout is reached.
         let deadline = time::precise_time_ns() + COALESCING_TIMEOUT;
         let mut free_list = mem::replace(&mut self.free_list, FreeRectList::new()).into_vec();
         let mut changed = false;
 
         // Combine rects that have the same width and are adjacent.
         let mut new_free_list = Vec::new();
         free_list.sort_by(|a, b| {
@@ -213,17 +192,17 @@ impl TexturePage {
                 ordering => ordering,
             }
         });
         for work_index in 0..free_list.len() {
             if work_index % COALESCING_TIMEOUT_CHECKING_INTERVAL == 0 &&
                     time::precise_time_ns() >= deadline {
                 self.free_list = FreeRectList::from_slice(&free_list[..]);
                 self.dirty = true;
-                return
+                return true
             }
 
             if free_list[work_index].size.width == 0 {
                 continue
             }
             for candidate_index in (work_index + 1)..free_list.len() {
                 if free_list[work_index].size.width != free_list[candidate_index].size.width ||
                         free_list[work_index].origin.x != free_list[candidate_index].origin.x {
@@ -250,17 +229,17 @@ impl TexturePage {
                 ordering => ordering,
             }
         });
         for work_index in 0..free_list.len() {
             if work_index % COALESCING_TIMEOUT_CHECKING_INTERVAL == 0 &&
                     time::precise_time_ns() >= deadline {
                 self.free_list = FreeRectList::from_slice(&free_list[..]);
                 self.dirty = true;
-                return
+                return true
             }
 
             if free_list[work_index].size.height == 0 {
                 continue
             }
             for candidate_index in (work_index + 1)..free_list.len() {
                 if free_list[work_index].size.height !=
                         free_list[candidate_index].size.height ||
@@ -275,17 +254,18 @@ impl TexturePage {
                     free_list[candidate_index].size.height = 0
                 }
             }
             new_free_list.push(free_list[work_index])
         }
         free_list = new_free_list;
 
         self.free_list = FreeRectList::from_slice(&free_list[..]);
-        self.dirty = changed
+        self.dirty = changed;
+        changed
     }
 
     pub fn clear(&mut self) {
         self.free_list = FreeRectList::new();
         self.free_list.push(&DeviceUintRect::new(
             DeviceUintPoint::zero(),
             self.texture_size));
         self.allocations = 0;
@@ -386,17 +366,17 @@ impl FreeRectList {
         self.small.extend(self.large.drain(..));
         self.small
     }
 }
 
 #[derive(Debug, Clone, Copy)]
 struct FreeListIndex(FreeListBin, usize);
 
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
 enum FreeListBin {
     Small,
     Medium,
     Large,
 }
 
 impl FreeListBin {
     pub fn for_size(size: &DeviceUintSize) -> FreeListBin {
@@ -423,16 +403,22 @@ pub struct TextureCacheItem {
     pub texture_size: DeviceUintSize,
 
     // The size of the allocated rectangle.
     pub allocated_rect: DeviceUintRect,
 }
 
 // Structure squat the width/height fields to maintain the free list information :)
 impl FreeListItem for TextureCacheItem {
+    fn take(&mut self) -> Self {
+        let data = self.clone();
+        self.texture_id = CacheTextureId(0);
+        data
+    }
+
     fn next_free_id(&self) -> Option<FreeListItemId> {
         if self.allocated_rect.size.width == 0 {
             debug_assert_eq!(self.allocated_rect.size.height, 0);
             None
         } else {
             debug_assert_eq!(self.allocated_rect.size.width, 1);
             Some(FreeListItemId::new(self.allocated_rect.size.height))
         }
@@ -575,36 +561,29 @@ impl TextureCache {
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         mem::replace(&mut self.pending_updates, TextureUpdateList::new())
     }
 
     // TODO(gw): This API is a bit ugly (having to allocate an ID and
     //           then use it). But it has to be that way for now due to
     //           how the raster_jobs code works.
     pub fn new_item_id(&mut self) -> TextureCacheItemId {
-        let new_item = TextureCacheItem {
-            pixel_rect: RectUv {
-                top_left: DeviceIntPoint::zero(),
-                top_right: DeviceIntPoint::zero(),
-                bottom_left: DeviceIntPoint::zero(),
-                bottom_right: DeviceIntPoint::zero(),
-            },
-            allocated_rect: DeviceUintRect::zero(),
-            texture_size: DeviceUintSize::zero(),
-            texture_id: CacheTextureId(0),
-        };
+        let new_item = TextureCacheItem::new(CacheTextureId(0),
+                                             DeviceUintRect::zero(),
+                                             &DeviceUintSize::zero());
         self.items.insert(new_item)
     }
 
     pub fn allocate(&mut self,
                     image_id: TextureCacheItemId,
                     requested_width: u32,
                     requested_height: u32,
                     format: ImageFormat,
-                    filter: TextureFilter)
+                    filter: TextureFilter,
+                    profile: &mut TextureCacheProfileCounters)
                     -> AllocationResult {
         let requested_size = DeviceUintSize::new(requested_width, requested_height);
 
         // TODO(gw): For now, anything that requests nearest filtering
         //           just fails to allocate in a texture page, and gets a standalone
         //           texture. This isn't ideal, as it causes lots of batch breaks,
         //           but is probably rare enough that it can be fixed up later (it's also
         //           fairly trivial to implement, just tedious).
@@ -619,95 +598,113 @@ impl TextureCache {
 
             return AllocationResult {
                 item: self.items.get(image_id).clone(),
                 kind: AllocationKind::Standalone,
             }
         }
 
         let mode = RenderTargetMode::SimpleRenderTarget;
-        let page_list = match format {
-            ImageFormat::A8 => &mut self.arena.pages_a8,
-            ImageFormat::RGBA8 => &mut self.arena.pages_rgba8,
-            ImageFormat::RGB8 => &mut self.arena.pages_rgb8,
+        let (page_list, page_profile) = match format {
+            ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
+            ImageFormat::RGBA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
+            ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
         assert!(requested_size.width < self.max_texture_size);
         assert!(requested_size.height < self.max_texture_size);
 
-        // Loop until an allocation succeeds, growing or adding new
-        // texture pages as required.
-        loop {
-            let location = page_list.last_mut().and_then(|last_page| {
-                last_page.allocate(&requested_size)
-            });
-
-            if let Some(location) = location {
-                let page = page_list.last_mut().unwrap();
-
-                let requested_rect = DeviceUintRect::new(location, requested_size);
-                let cache_item = TextureCacheItem::new(page.texture_id,
-                                                       requested_rect,
-                                                       &page.texture_size);
-                *self.items.get_mut(image_id) = cache_item;
-
-                return AllocationResult {
-                    item: self.items.get(image_id).clone(),
-                    kind: AllocationKind::TexturePage,
-                }
+        let mut page_id = None; //using ID here to please the borrow checker
+        for (i, page) in page_list.iter_mut().enumerate() {
+            if page.can_allocate(&requested_size) {
+                page_id = Some(i);
+                break;
             }
-
-            if !page_list.is_empty() && page_list.last().unwrap().can_grow(self.max_texture_size) {
-                let last_page = page_list.last_mut().unwrap();
-                // Grow the texture.
-                let new_width = cmp::min(last_page.texture_size.width * 2, self.max_texture_size);
-                let new_height = cmp::min(last_page.texture_size.height * 2, self.max_texture_size);
+            // try to coalesce it
+            if page.coalesce() && page.can_allocate(&requested_size) {
+                page_id = Some(i);
+                break;
+            }
+            if page.can_grow(self.max_texture_size) {
+                // try to grow it
+                let new_width = cmp::min(page.texture_size.width * 2, self.max_texture_size);
+                let new_height = cmp::min(page.texture_size.height * 2, self.max_texture_size);
                 let texture_size = DeviceUintSize::new(new_width, new_height);
                 self.pending_updates.push(TextureUpdate {
-                    id: last_page.texture_id,
+                    id: page.texture_id,
                     op: texture_grow_op(texture_size, format, mode),
                 });
-                last_page.grow(texture_size);
+
+                let extra_texels = new_width * new_height - page.texture_size.width * page.texture_size.height;
+                let extra_bytes = extra_texels * format.bytes_per_pixel().unwrap_or(0);
+                page_profile.inc(extra_bytes as usize);
+
+                page.grow(texture_size);
 
                 self.items.for_each_item(|item| {
-                    if item.texture_id == last_page.texture_id {
+                    if item.texture_id == page.texture_id {
                         item.texture_size = texture_size;
                     }
                 });
 
-                continue;
+                if page.can_allocate(&requested_size) {
+                    page_id = Some(i);
+                    break;
+                }
             }
+        }
 
-            // We need a new page.
-            let texture_size = initial_texture_size(self.max_texture_size);
-            let free_texture_levels_entry = self.free_texture_levels.entry(format);
-            let mut free_texture_levels = match free_texture_levels_entry {
-                Entry::Vacant(entry) => entry.insert(Vec::new()),
-                Entry::Occupied(entry) => entry.into_mut(),
-            };
-            if free_texture_levels.is_empty() {
-                let texture_id = self.cache_id_list.allocate();
+        let mut page = match page_id {
+            Some(index) => &mut page_list[index],
+            None => {
+                let init_texture_size = initial_texture_size(self.max_texture_size);
+                let texture_size = DeviceUintSize::new(cmp::max(requested_width, init_texture_size.width),
+                                                       cmp::max(requested_height, init_texture_size.height));
+                let extra_bytes = texture_size.width * texture_size.height * format.bytes_per_pixel().unwrap_or(0);
+                page_profile.inc(extra_bytes as usize);
+
+                let free_texture_levels_entry = self.free_texture_levels.entry(format);
+                let mut free_texture_levels = match free_texture_levels_entry {
+                    Entry::Vacant(entry) => entry.insert(Vec::new()),
+                    Entry::Occupied(entry) => entry.into_mut(),
+                };
+                if free_texture_levels.is_empty() {
+                    let texture_id = self.cache_id_list.allocate();
 
-                let update_op = TextureUpdate {
-                    id: texture_id,
-                    op: texture_create_op(texture_size, format, mode),
-                };
-                self.pending_updates.push(update_op);
+                    let update_op = TextureUpdate {
+                        id: texture_id,
+                        op: texture_create_op(texture_size, format, mode),
+                    };
+                    self.pending_updates.push(update_op);
+
+                    free_texture_levels.push(FreeTextureLevel {
+                        texture_id: texture_id,
+                    });
+                }
+                let free_texture_level = free_texture_levels.pop().unwrap();
+                let texture_id = free_texture_level.texture_id;
 
-                free_texture_levels.push(FreeTextureLevel {
-                    texture_id: texture_id,
-                });
-            }
-            let free_texture_level = free_texture_levels.pop().unwrap();
-            let texture_id = free_texture_level.texture_id;
+                let page = TexturePage::new(texture_id, texture_size);
+                page_list.push(page);
+                page_list.last_mut().unwrap()
+            },
+        };
 
-            let page = TexturePage::new(texture_id, texture_size);
-            page_list.push(page);
+        let location = page.allocate(&requested_size)
+                           .expect("All the checks have passed till now, there is no way back.");
+        let cache_item = TextureCacheItem::new(page.texture_id,
+                                               DeviceUintRect::new(location, requested_size),
+                                               &page.texture_size);
+        *self.items.get_mut(image_id) = cache_item.clone();
+
+        AllocationResult {
+            item: cache_item,
+            kind: AllocationKind::TexturePage,
         }
     }
 
     pub fn update(&mut self,
                   image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
                   data: ImageData) {
         let existing_item = self.items.get(image_id);
@@ -726,47 +723,50 @@ impl TextureCache {
             ImageData::Raw(bytes) => {
                 TextureUpdateOp::Update {
                     page_pos_x: existing_item.allocated_rect.origin.x,
                     page_pos_y: existing_item.allocated_rect.origin.y,
                     width: descriptor.width,
                     height: descriptor.height,
                     data: bytes,
                     stride: descriptor.stride,
+                    offset: descriptor.offset,
                 }
             }
         };
 
         let update_op = TextureUpdate {
             id: existing_item.texture_id,
             op: op,
         };
 
         self.pending_updates.push(update_op);
     }
 
     pub fn insert(&mut self,
                   image_id: TextureCacheItemId,
                   descriptor: ImageDescriptor,
                   filter: TextureFilter,
-                  data: ImageData) {
+                  data: ImageData,
+                  profile: &mut TextureCacheProfileCounters) {
         if let ImageData::Blob(..) = data {
             panic!("must rasterize the vector image before adding to the cache");
         }
 
         let width = descriptor.width;
         let height = descriptor.height;
         let format = descriptor.format;
         let stride = descriptor.stride;
 
         let result = self.allocate(image_id,
                                    width,
                                    height,
                                    format,
-                                   filter);
+                                   filter,
+                                   profile);
 
         match result.kind {
             AllocationKind::TexturePage => {
                 match data {
                     ImageData::ExternalHandle(..) => {
                         panic!("External handle should not go through texture_cache.");
                     }
                     ImageData::Blob(..) => {
@@ -777,16 +777,17 @@ impl TextureCache {
                             id: result.item.texture_id,
                             op: TextureUpdateOp::Update {
                                 page_pos_x: result.item.allocated_rect.origin.x,
                                 page_pos_y: result.item.allocated_rect.origin.y,
                                 width: result.item.allocated_rect.size.width,
                                 height: result.item.allocated_rect.size.height,
                                 data: bytes,
                                 stride: stride,
+                                offset: descriptor.offset,
                             },
                         };
 
                         self.pending_updates.push(update_op);
                     }
                     ImageData::ExternalBuffer(id) => {
                         let update_op = TextureUpdate {
                             id: result.item.texture_id,
@@ -826,33 +827,29 @@ impl TextureCache {
         }
     }
 
     pub fn get(&self, id: TextureCacheItemId) -> &TextureCacheItem {
         self.items.get(id)
     }
 
     pub fn free(&mut self, id: TextureCacheItemId) {
-        {
-            let item = self.items.get(id);
-            match self.arena.texture_page_for_id(item.texture_id) {
-                Some(texture_page) => texture_page.free(&item.allocated_rect),
-                None => {
-                    // This is a standalone texture allocation. Just push it back onto the free
-                    // list.
-                    self.pending_updates.push(TextureUpdate {
-                        id: item.texture_id,
-                        op: TextureUpdateOp::Free,
-                    });
-                    self.cache_id_list.free(item.texture_id);
-                }
+        let item = self.items.free(id);
+        match self.arena.texture_page_for_id(item.texture_id) {
+            Some(texture_page) => texture_page.free(&item.allocated_rect),
+            None => {
+                // This is a standalone texture allocation. Just push it back onto the free
+                // list.
+                self.pending_updates.push(TextureUpdate {
+                    id: item.texture_id,
+                    op: TextureUpdateOp::Free,
+                });
+                self.cache_id_list.free(item.texture_id);
             }
         }
-
-        self.items.free(id)
     }
 }
 
 fn texture_create_op(texture_size: DeviceUintSize, format: ImageFormat, mode: RenderTargetMode)
                      -> TextureUpdateOp {
     TextureUpdateOp::Create {
         width: texture_size.width,
         height: texture_size.height,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -803,41 +803,41 @@ impl ClipBatcher {
                             segment: MaskSegment::All as i32,
                             ..instance
                         });
                     }
                     MaskGeometryKind::CornersOnly => {
                         self.rectangles.extend(&[
                             CacheClipInstance {
                                 address: GpuStoreAddress(offset),
-                                segment: MaskSegment::Corner_TopLeft as i32,
+                                segment: MaskSegment::TopLeftCorner as i32,
                                 ..instance
                             },
                             CacheClipInstance {
                                 address: GpuStoreAddress(offset),
-                                segment: MaskSegment::Corner_TopRight as i32,
+                                segment: MaskSegment::TopRightCorner as i32,
                                 ..instance
                             },
                             CacheClipInstance {
                                 address: GpuStoreAddress(offset),
-                                segment: MaskSegment::Corner_BottomLeft as i32,
+                                segment: MaskSegment::BottomLeftCorner as i32,
                                 ..instance
                             },
                             CacheClipInstance {
                                 address: GpuStoreAddress(offset),
-                                segment: MaskSegment::Corner_BottomRight as i32,
+                                segment: MaskSegment::BottomRightCorner as i32,
                                 ..instance
                             },
                         ]);
                     }
                 }
             }
 
             if let Some((ref mask, address)) = info.image {
-                let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto);
+                let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None);
                 self.images.entry(cache_item.texture_id)
                            .or_insert(Vec::new())
                            .push(CacheClipInstance {
                     address: address,
                     ..instance
                 })
             }
         }
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -1,24 +1,24 @@
 [package]
 name = "webrender_bindings"
 version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
-webrender_traits = {path = "../webrender_traits", version = "0.19"}
+webrender_traits = {path = "../webrender_traits", version = "0.20.0"}
 euclid = "0.11"
 app_units = "0.4"
 gleam = "0.2"
 fnv="1.0"
 
 [dependencies.webrender]
 path = "../webrender"
-version = "0.19"
+version = "0.19.0"
 default-features = false
 features = ["codegen"]
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.2.2"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 kernel32-sys = "0.2"
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_traits"
-version = "0.19.0"
+version = "0.20.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["codegen"]
 nightly = ["euclid/unstable", "serde/unstable"]
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -4,18 +4,19 @@
 
 use byteorder::{LittleEndian, WriteBytesExt};
 use channel::{self, MsgSender, PayloadHelperMethods, PayloadSender};
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use {ApiMsg, ColorF, DisplayListBuilder, Epoch, ImageDescriptor};
 use {FontKey, IdNamespace, ImageKey, NativeFontHandle, PipelineId};
 use {RenderApiSender, ResourceId, ScrollEventPhase, ScrollLayerState, ScrollLocation, ServoScrollRootId};
-use {GlyphKey, GlyphDimensions, ImageData, WebGLContextId, WebGLCommand};
+use {GlyphKey, GlyphDimensions, ImageData, WebGLContextId, WebGLCommand, TileSize};
 use {DeviceIntSize, DynamicProperties, LayoutPoint, LayoutSize, WorldPoint, PropertyBindingKey, PropertyBindingId};
+use {BuiltDisplayList, AuxiliaryLists};
 use VRCompositorCommand;
 use ExternalEvent;
 use std::marker::PhantomData;
 
 impl RenderApiSender {
     pub fn new(api_sender: MsgSender<ApiMsg>,
                payload_sender: PayloadSender)
                -> RenderApiSender {
@@ -87,18 +88,19 @@ impl RenderApi {
         let new_id = self.next_unique_id();
         ImageKey::new(new_id.0, new_id.1)
     }
 
     /// Adds an image identified by the `ImageKey`.
     pub fn add_image(&self,
                      key: ImageKey,
                      descriptor: ImageDescriptor,
-                     data: ImageData) {
-        let msg = ApiMsg::AddImage(key, descriptor, data);
+                     data: ImageData,
+                     tiling: Option<TileSize>) {
+        let msg = ApiMsg::AddImage(key, descriptor, data, tiling);
         self.api_sender.send(msg).unwrap();
     }
 
     /// Updates a specific image.
     ///
     /// Currently doesn't support changing dimensions or format by updating.
     // TODO: Support changing dimensions (and format) during image update?
     pub fn update_image(&self,
@@ -151,20 +153,18 @@ impl RenderApi {
     ///                           id, this setting determines if frame state (such as scrolling
     ///                           position) should be preserved for this new display list.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn set_root_display_list(&self,
                                  background_color: Option<ColorF>,
                                  epoch: Epoch,
                                  viewport_size: LayoutSize,
-                                 builder: DisplayListBuilder,
+                                 (pipeline_id, display_list, auxiliary_lists): (PipelineId, BuiltDisplayList, AuxiliaryLists),
                                  preserve_frame_state: bool) {
-        let pipeline_id = builder.pipeline_id;
-        let (display_list, auxiliary_lists) = builder.finalize();
         let msg = ApiMsg::SetRootDisplayList(background_color,
                                              epoch,
                                              pipeline_id,
                                              viewport_size,
                                              display_list.descriptor().clone(),
                                              *auxiliary_lists.descriptor(),
                                              preserve_frame_state);
         self.api_sender.send(msg).unwrap();
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -3,28 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use std::mem;
 use std::slice;
 use {AuxiliaryLists, AuxiliaryListsDescriptor, BorderDisplayItem};
 use {BoxShadowClipMode, BoxShadowDisplayItem, BuiltDisplayList};
 use {BuiltDisplayListDescriptor, ClipRegion, ComplexClipRegion, ColorF};
-use {DisplayItem, DisplayListMode, ExtendMode, FilterOp, YuvColorSpace};
+use {DisplayItem, ExtendMode, FilterOp, YuvColorSpace};
 use {FontKey, GlyphInstance, GradientDisplayItem, RadialGradientDisplayItem, GradientStop, IframeDisplayItem};
 use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, ItemRange, MixBlendMode, PipelineId};
 use {PushScrollLayerItem, PushStackingContextDisplayItem, RectangleDisplayItem, ScrollLayerId};
 use {ScrollPolicy, ServoScrollRootId, SpecificDisplayItem, StackingContext, TextDisplayItem};
 use {WebGLContextId, WebGLDisplayItem, YuvImageDisplayItem};
 use {LayoutTransform, LayoutPoint, LayoutRect, LayoutSize};
 use {BorderDetails, BorderWidths, GlyphOptions, PropertyBinding};
 
 impl BuiltDisplayListDescriptor {
     pub fn size(&self) -> usize {
-        self.display_list_items_size + self.display_items_size
+        self.display_list_items_size
     }
 }
 
 impl BuiltDisplayList {
     pub fn from_data(data: Vec<u8>, descriptor: BuiltDisplayListDescriptor) -> BuiltDisplayList {
         BuiltDisplayList {
             data: data,
             descriptor: descriptor,
@@ -43,27 +43,25 @@ impl BuiltDisplayList {
         unsafe {
             convert_blob_to_pod(&self.data[0..self.descriptor.display_list_items_size])
         }
     }
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
-    pub mode: DisplayListMode,
     pub list: Vec<DisplayItem>,
     auxiliary_lists_builder: AuxiliaryListsBuilder,
     pub pipeline_id: PipelineId,
     next_scroll_layer_id: usize,
 }
 
 impl DisplayListBuilder {
     pub fn new(pipeline_id: PipelineId) -> DisplayListBuilder {
         DisplayListBuilder {
-            mode: DisplayListMode::Default,
             list: Vec::new(),
             auxiliary_lists_builder: AuxiliaryListsBuilder::new(),
             pipeline_id: pipeline_id,
             next_scroll_layer_id: 0,
         }
     }
 
     pub fn print_display_list(&mut self) {
@@ -360,26 +358,25 @@ impl DisplayListBuilder {
     pub fn new_clip_region(&mut self,
                            rect: &LayoutRect,
                            complex: Vec<ComplexClipRegion>,
                            image_mask: Option<ImageMask>)
                            -> ClipRegion {
         ClipRegion::new(rect, complex, image_mask, &mut self.auxiliary_lists_builder)
     }
 
-    pub fn finalize(self) -> (BuiltDisplayList, AuxiliaryLists) {
+    pub fn finalize(self) -> (PipelineId, BuiltDisplayList, AuxiliaryLists) {
         unsafe {
             let blob = convert_pod_to_blob(&self.list).to_vec();
             let display_list_items_size = blob.len();
 
-            (BuiltDisplayList {
+            (self.pipeline_id,
+             BuiltDisplayList {
                  descriptor: BuiltDisplayListDescriptor {
-                     mode: self.mode,
                      display_list_items_size: display_list_items_size,
-                     display_items_size: 0,
                  },
                  data: blob,
              },
              self.auxiliary_lists_builder.finalize())
         }
     }
 }
 
--- a/gfx/webrender_traits/src/types.rs
+++ b/gfx/webrender_traits/src/types.rs
@@ -19,24 +19,26 @@ use std::sync::Arc;
 #[cfg(target_os = "windows")] use dwrote::FontDescriptor;
 
 #[derive(Debug, Copy, Clone)]
 pub enum RendererKind {
     Native,
     OSMesa,
 }
 
+pub type TileSize = u16;
+
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>),
     AddNativeFont(FontKey, NativeFontHandle),
     /// Gets the glyph dimensions
     GetGlyphDimensions(Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
     /// Adds an image from the resource cache.
-    AddImage(ImageKey, ImageDescriptor, ImageData),
+    AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
     /// Updates the the resource cache with the new image data.
     UpdateImage(ImageKey, ImageDescriptor, Vec<u8>),
     /// Drops an image from the resource cache.
     DeleteImage(ImageKey),
     CloneApi(MsgSender<IdNamespace>),
     /// Supplies a new frame to WebRender.
     ///
     /// After receiving this message, WebRender will read the display list, followed by the
@@ -161,16 +163,17 @@ pub struct AuxiliaryLists {
     data: Vec<u8>,
     descriptor: AuxiliaryListsDescriptor,
 }
 
 /// Describes the memory layout of the auxiliary lists.
 ///
 /// Auxiliary lists consist of some number of gradient stops, complex clip regions, filters, and
 /// glyph instances, in that order.
+#[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct AuxiliaryListsDescriptor {
     gradient_stops_size: usize,
     complex_clip_regions_size: usize,
     filters_size: usize,
     glyph_instances_size: usize,
 }
 
@@ -282,24 +285,21 @@ pub struct BuiltDisplayList {
     data: Vec<u8>,
     descriptor: BuiltDisplayListDescriptor,
 }
 
 /// Describes the memory layout of a display list.
 ///
 /// A display list consists of some number of display list items, followed by a number of display
 /// items.
+#[repr(C)]
 #[derive(Copy, Clone, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
-    pub mode: DisplayListMode,
-
     /// The size in bytes of the display list items in this display list.
     display_list_items_size: usize,
-    /// The size in bytes of the display items in this display list.
-    display_items_size: usize,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ColorF {
     pub r: f32,
     pub g: f32,
     pub b: f32,
@@ -358,19 +358,37 @@ impl ColorU {
 }
 
 #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDescriptor {
     pub format: ImageFormat,
     pub width: u32,
     pub height: u32,
     pub stride: Option<u32>,
+    pub offset: u32,
     pub is_opaque: bool,
 }
 
+impl ImageDescriptor {
+    pub fn new(width: u32, height: u32, format: ImageFormat, is_opaque: bool) -> Self {
+        ImageDescriptor {
+            width: width,
+            height: height,
+            format: format,
+            stride: None,
+            offset: 0,
+            is_opaque: is_opaque,
+        }
+    }
+
+    pub fn compute_stride(&self) -> u32 {
+        self.stride.unwrap_or(self.width * self.format.bytes_per_pixel().unwrap())
+    }
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
     pub repeat: bool,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -390,24 +408,16 @@ pub struct ComplexClipRegion {
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct DisplayItem {
     pub item: SpecificDisplayItem,
     pub rect: LayoutRect,
     pub clip: ClipRegion,
 }
 
-#[repr(u32)]
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub enum DisplayListMode {
-    Default                 = 0,
-    PseudoFloat             = 1,
-    PseudoPositionedContent = 2,
-}
-
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum ExtendMode {
     Clamp,
     Repeat,
--- a/gfx/webrender_traits/src/units.rs
+++ b/gfx/webrender_traits/src/units.rs
@@ -68,16 +68,20 @@ pub type ScrollLayerSize = TypedSize2D<f
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct WorldPixel;
 
 pub type WorldRect = TypedRect<f32, WorldPixel>;
 pub type WorldPoint = TypedPoint2D<f32, WorldPixel>;
 pub type WorldSize = TypedSize2D<f32, WorldPixel>;
 pub type WorldPoint4D = TypedPoint4D<f32, WorldPixel>;
 
+/// Offset in number of tiles.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Tiles;
+pub type TileOffset = TypedPoint2D<u16, Tiles>;
 
 pub type LayoutTransform = TypedMatrix4D<f32, LayoutPixel, LayoutPixel>;
 pub type LayerTransform = TypedMatrix4D<f32, LayerPixel, LayerPixel>;
 pub type LayerToScrollTransform = TypedMatrix4D<f32, LayerPixel, ScrollLayerPixel>;
 pub type ScrollToLayerTransform = TypedMatrix4D<f32, ScrollLayerPixel, LayerPixel>;
 pub type LayerToWorldTransform = TypedMatrix4D<f32, LayerPixel, WorldPixel>;
 pub type WorldToLayerTransform = TypedMatrix4D<f32, WorldPixel, LayerPixel>;
 pub type ScrollToWorldTransform = TypedMatrix4D<f32, ScrollLayerPixel, WorldPixel>;
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -1187,37 +1187,37 @@ dependencies = [
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-dwrote 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.19.0",
+ "webrender_traits 0.20.0",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.19.0",
- "webrender_traits 0.19.0",
+ "webrender_traits 0.20.0",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender_traits"
-version = "0.19.0"
+version = "0.20.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -1174,37 +1174,37 @@ dependencies = [
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-dwrote 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.19.0",
+ "webrender_traits 0.20.0",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.19.0",
- "webrender_traits 0.19.0",
+ "webrender_traits 0.20.0",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender_traits"
-version = "0.19.0"
+version = "0.20.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",