Bug 1379604 - Update webrender to cset 479ae6475a18527206a2534c2b8a5bfb8b06bd6e. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 13 Jul 2017 10:27:44 -0400
changeset 608377 838e4d60fd234beb6b2593315ecc88c60a6e97c7
parent 607967 30ea2905130e85f9e1d8d56fa3097901eec6514b
child 608378 2296eea1f470f522d15427c82e00dc3e3f4333f9
push id68252
push userkgupta@mozilla.com
push dateThu, 13 Jul 2017 14:29:22 +0000
reviewersjrmuizel
bugs1379604
milestone56.0a1
Bug 1379604 - Update webrender to cset 479ae6475a18527206a2534c2b8a5bfb8b06bd6e. r?jrmuizel MozReview-Commit-ID: GpuG8Rb8uz6
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/build.rs
gfx/webrender/examples/basic.rs
gfx/webrender/res/cs_blur.fs.glsl
gfx/webrender/res/cs_text_run.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/image.rs
gfx/webrender_api/src/webgl.rs
--- 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: 519e51986308fc11d6ba6771f1c11ea6a3133921
+Latest Commit: 479ae6475a18527206a2534c2b8a5bfb8b06bd6e
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.47.0"
+version = "0.48.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
--- a/gfx/webrender/build.rs
+++ b/gfx/webrender/build.rs
@@ -45,10 +45,14 @@ fn main() {
         let path = entry.path();
 
         if entry.file_name().to_str().unwrap().ends_with(".glsl") {
             println!("cargo:rerun-if-changed={}", path.display());
             glsl_files.push(path.to_owned());
         }
     }
 
+    // Sort the file list so that the shaders.rs file is filled
+    // deterministically.
+    glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
+
     write_shaders(glsl_files, &shaders_file);
 }
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -285,17 +285,16 @@ fn body(api: &RenderApi,
         ];
 
         builder.push_text(text_bounds,
                           None,
                           &glyphs,
                           font_key,
                           ColorF::new(1.0, 1.0, 0.0, 1.0),
                           Au::from_px(32),
-                          0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::zero();
         let simple_box_bounds = (20, 200).by(50, 50);
         let offset = vec2(10.0, 10.0);
         let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
--- a/gfx/webrender/res/cs_blur.fs.glsl
+++ b/gfx/webrender/res/cs_blur.fs.glsl
@@ -11,16 +11,25 @@
 //           the number of texture fetches needed for a gaussian blur.
 
 float gauss(float x, float sigma) {
     return (1.0 / sqrt(6.283185307179586 * sigma * sigma)) * exp(-(x * x) / (2.0 * sigma * sigma));
 }
 
 void main(void) {
     vec4 cache_sample = texture(sCacheRGBA8, vUv);
+
+    // TODO(gw): The gauss function gets NaNs when blur radius
+    //           is zero. In the future, detect this earlier
+    //           and skip the blur passes completely.
+    if (vBlurRadius == 0) {
+        oFragColor = cache_sample;
+        return;
+    }
+
     vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma));
 
     for (int i=1 ; i < vBlurRadius ; ++i) {
         vec2 offset = vec2(float(i)) * vOffsetScale;
 
         vec2 st0 = clamp(vUv.xy + offset, vUvRect.xy, vUvRect.zw);
         vec4 color0 = texture(sCacheRGBA8, vec3(st0, vUv.z));
 
--- a/gfx/webrender/res/cs_text_run.vs.glsl
+++ b/gfx/webrender/res/cs_text_run.vs.glsl
@@ -16,17 +16,17 @@ void main(void) {
     Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index);
     GlyphResource res = fetch_glyph_resource(resource_address);
 
     // Glyphs size is already in device-pixels.
     // The render task origin is in device-pixels. Offset that by
     // the glyph offset, relative to its primitive bounding rect.
     vec2 size = res.uv_rect.zw - res.uv_rect.xy;
     vec2 local_pos = glyph.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio;
-    vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
+    vec2 origin = prim.task.render_target_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0);
     vec4 local_rect = vec4(origin, size);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
     vec2 pos = mix(local_rect.xy,
                    local_rect.xy + local_rect.zw,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -714,22 +714,18 @@ TransformVertexInfo write_transform_vert
     // Intersect those adjusted lines to find the actual vertex position.
     vec2 device_pos = intersect_lines(adjusted_prev_p0,
                                       adjusted_prev_p1,
                                       adjusted_next_p0,
                                       adjusted_next_p1);
 
     vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
 
-    /// Compute the snapping offset.
-    vec2 snap_offset = compute_snap_offset(layer_pos.xy / layer_pos.w,
-                                           local_clip_rect, layer, snap_rect);
-
     // Apply offsets for the render task to get correct screen location.
-    vec2 final_pos = device_pos + snap_offset -
+    vec2 final_pos = device_pos - //Note: `snap_rect` is not used
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     vLocalBounds = vec4(local_rect.p0, local_rect.p1);
 
     return TransformVertexInfo(layer_pos.xyw, device_pos);
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -352,16 +352,17 @@ struct Texture {
     gl: Rc<gl::Gl>,
     id: gl::GLuint,
     format: ImageFormat,
     width: u32,
     height: u32,
     filter: TextureFilter,
     mode: RenderTargetMode,
     fbo_ids: Vec<FBOId>,
+    depth_rb: Option<RBOId>,
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         if !self.fbo_ids.is_empty() {
             let fbo_ids: Vec<_> = self.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
@@ -476,16 +477,19 @@ pub struct ProgramId(pub gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct VAOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct FBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
+pub struct RBOId(gl::GLuint);
+
+#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct VBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 struct IBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct UBOId(gl::GLuint);
 
@@ -1099,16 +1103,17 @@ impl Device {
                 gl: Rc::clone(&self.gl),
                 id,
                 width: 0,
                 height: 0,
                 format: ImageFormat::Invalid,
                 filter: TextureFilter::Nearest,
                 mode: RenderTargetMode::None,
                 fbo_ids: vec![],
+                depth_rb: None,
             };
 
             debug_assert!(self.textures.contains_key(&texture_id) == false);
             self.textures.insert(texture_id, texture);
 
             texture_ids.push(texture_id);
         }
 
@@ -1224,82 +1229,88 @@ impl Device {
 
     pub fn create_fbo_for_texture_if_necessary(&mut self,
                                                texture_id: TextureId,
                                                layer_count: Option<i32>) {
         let texture = self.textures.get_mut(&texture_id).unwrap();
 
         match layer_count {
             Some(layer_count) => {
-                debug_assert!(layer_count > 0);
+                assert!(layer_count > 0);
+                assert_eq!(texture_id.target, gl::TEXTURE_2D_ARRAY);
 
                 // If we have enough layers allocated already, just use them.
                 // TODO(gw): Probably worth removing some after a while if
                 //           there is a surplus?
                 let current_layer_count = texture.fbo_ids.len() as i32;
                 if current_layer_count >= layer_count {
                     return;
                 }
 
                 let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
                 let type_ = gl_type_for_texture_format(texture.format);
 
                 self.gl.tex_image_3d(texture_id.target,
-                                      0,
-                                      internal_format as gl::GLint,
-                                      texture.width as gl::GLint,
-                                      texture.height as gl::GLint,
-                                      layer_count,
-                                      0,
-                                      gl_format,
-                                      type_,
-                                      None);
+                                     0,
+                                     internal_format as gl::GLint,
+                                     texture.width as gl::GLint,
+                                     texture.height as gl::GLint,
+                                     layer_count,
+                                     0,
+                                     gl_format,
+                                     type_,
+                                     None);
 
                 let needed_layer_count = layer_count - current_layer_count;
                 let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
                 texture.fbo_ids.extend(new_fbos.into_iter().map(|id| FBOId(id)));
 
-                for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
-                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
-                    self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
-                                                       gl::COLOR_ATTACHMENT0,
-                                                       texture_id.name,
-                                                       0,
-                                                       fbo_index as gl::GLint);
-
-                    // TODO(gw): Share depth render buffer between FBOs to
-                    //           save memory!
-                    // TODO(gw): Free these renderbuffers on exit!
+                let depth_rb = if let Some(rbo) = texture.depth_rb {
+                    rbo.0
+                } else {
                     let renderbuffer_ids = self.gl.gen_renderbuffers(1);
                     let depth_rb = renderbuffer_ids[0];
                     self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
                     self.gl.renderbuffer_storage(gl::RENDERBUFFER,
-                                                  gl::DEPTH_COMPONENT24,
-                                                  texture.width as gl::GLsizei,
-                                                  texture.height as gl::GLsizei);
+                                                 gl::DEPTH_COMPONENT24,
+                                                 texture.width as gl::GLsizei,
+                                                 texture.height as gl::GLsizei);
+                    texture.depth_rb = Some(RBOId(depth_rb));
+                    depth_rb
+                };
+
+                for (fbo_index, fbo_id) in texture.fbo_ids.iter().enumerate() {
+                    self.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo_id.0);
+                    self.gl.framebuffer_texture_layer(gl::FRAMEBUFFER,
+                                                      gl::COLOR_ATTACHMENT0,
+                                                      texture_id.name,
+                                                      0,
+                                                      fbo_index as gl::GLint);
                     self.gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
-                                                      gl::DEPTH_ATTACHMENT,
-                                                      gl::RENDERBUFFER,
-                                                      depth_rb);
+                                                     gl::DEPTH_ATTACHMENT,
+                                                     gl::RENDERBUFFER,
+                                                     depth_rb);
                 }
             }
             None => {
-                debug_assert!(texture.fbo_ids.len() == 0 || texture.fbo_ids.len() == 1);
                 if texture.fbo_ids.is_empty() {
+                    assert!(texture_id.target != gl::TEXTURE_2D_ARRAY);
+
                     let new_fbo = self.gl.gen_framebuffers(1)[0];
-
                     self.gl.bind_framebuffer(gl::FRAMEBUFFER, new_fbo);
 
                     self.gl.framebuffer_texture_2d(gl::FRAMEBUFFER,
-                                                    gl::COLOR_ATTACHMENT0,
-                                                    texture_id.target,
-                                                    texture_id.name,
-                                                    0);
+                                                   gl::COLOR_ATTACHMENT0,
+                                                   texture_id.target,
+                                                   texture_id.name,
+                                                   0);
 
                     texture.fbo_ids.push(FBOId(new_fbo));
+                } else {
+                    assert_eq!(texture.fbo_ids.len(), 1);
                 }
             }
         }
 
         // TODO(gw): Hack! Modify the code above to use the normal binding interfaces the device exposes.
         self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.bound_read_fbo.0);
         self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0);
     }
@@ -1391,25 +1402,28 @@ impl Device {
                               internal_format,
                               0,
                               0,
                               0,
                               gl_format,
                               type_,
                               None);
 
+        if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
+            self.gl.delete_renderbuffers(&[depth_rb]);
+        }
+
         if !texture.fbo_ids.is_empty() {
-            let fbo_ids: Vec<_> = texture.fbo_ids.iter().map(|&FBOId(fbo_id)| fbo_id).collect();
+            let fbo_ids: Vec<_> = texture.fbo_ids.drain(..).map(|FBOId(fbo_id)| fbo_id).collect();
             self.gl.delete_framebuffers(&fbo_ids[..]);
         }
 
         texture.format = ImageFormat::Invalid;
         texture.width = 0;
         texture.height = 0;
-        texture.fbo_ids.clear();
     }
 
     pub fn create_program(&mut self,
                           base_filename: &str,
                           include_filename: &str,
                           vertex_format: VertexFormat) -> Result<ProgramId, ShaderError> {
         self.create_program_with_prefix(base_filename, &[include_filename], None, vertex_format)
     }
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -61,24 +61,32 @@ impl NestedDisplayListInfo {
     fn convert_id_to_nested(&self, id: &ClipId) -> ClipId {
         match *id {
             ClipId::Clip(id, _, pipeline_id) => ClipId::Clip(id, self.nest_index, pipeline_id),
             _ => *id,
         }
     }
 
     fn convert_scroll_id_to_nested(&self, id: &ClipId) -> ClipId {
+        if id.pipeline_id() != self.scroll_node_id.pipeline_id() {
+            return *id;
+        }
+
         if id.is_root_scroll_node() {
             self.scroll_node_id
         } else {
             self.convert_id_to_nested(id)
         }
     }
 
     fn convert_clip_id_to_nested(&self, id: &ClipId) -> ClipId {
+        if id.pipeline_id() != self.clip_node_id.pipeline_id() {
+            return *id;
+        }
+
         if id.is_root_scroll_node() {
             self.clip_node_id
         } else {
             self.convert_id_to_nested(id)
         }
     }
 }
 
@@ -114,17 +122,17 @@ impl<'a> FlattenContext<'a> {
             clip_node_id: info.clip_node_id(),
         });
     }
 
     fn pop_nested_display_list_ids(&mut self) {
         self.nested_display_list_info.pop();
     }
 
-    fn convert_new_id_to_neested(&self, id: &ClipId) -> ClipId {
+    fn convert_new_id_to_nested(&self, id: &ClipId) -> ClipId {
         if let Some(nested_info) = self.nested_display_list_info.last() {
             nested_info.convert_id_to_nested(id)
         } else {
             *id
         }
     }
 
     fn convert_clip_scroll_info_to_nested(&self, info: &mut ClipAndScrollInfo) {
@@ -353,17 +361,17 @@ impl Frame {
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
                         pipeline_id: PipelineId,
                         parent_id: &ClipId,
                         new_clip_id: &ClipId,
                         clip_region: ClipRegion) {
-        let new_clip_id = context.convert_new_id_to_neested(new_clip_id);
+        let new_clip_id = context.convert_new_id_to_nested(new_clip_id);
         context.builder.add_clip_node(new_clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
     }
 
     fn flatten_scroll_frame<'a>(&mut self,
@@ -376,17 +384,17 @@ impl Frame {
                                 clip_region: ClipRegion) {
         let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id);
         context.builder.add_clip_node(clip_id,
                                       *parent_id,
                                       pipeline_id,
                                       clip_region,
                                       &mut self.clip_scroll_tree);
 
-        let new_scroll_frame_id = context.convert_new_id_to_neested(new_scroll_frame_id);
+        let new_scroll_frame_id = context.convert_new_id_to_nested(new_scroll_frame_id);
         context.builder.add_scroll_frame(new_scroll_frame_id,
                                          clip_id,
                                          pipeline_id,
                                          &frame_rect,
                                          &content_rect.size,
                                          &mut self.clip_scroll_tree);
     }
 
@@ -478,38 +486,49 @@ impl Frame {
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
                           parent_id: ClipId,
                           bounds: &LayerRect,
+                          local_clip: &LocalClip,
                           context: &mut FlattenContext,
                           reference_frame_relative_offset: LayerVector2D) {
         let pipeline = match context.scene.pipeline_map.get(&pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
         let display_list = match context.scene.display_lists.get(&pipeline_id) {
             Some(display_list) => display_list,
             None => return,
         };
 
+        let mut clip_region = ClipRegion::create_for_clip_node_with_local_clip(local_clip);
+        clip_region.origin += reference_frame_relative_offset;
+        let parent_pipeline_id = parent_id.pipeline_id();
+        let clip_id = self.clip_scroll_tree.generate_new_clip_id(parent_pipeline_id);
+        context.builder.add_clip_node(clip_id,
+                                      parent_id,
+                                      parent_pipeline_id,
+                                      clip_region,
+                                      &mut self.clip_scroll_tree);
+
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = LayerToScrollTransform::create_translation(
             reference_frame_relative_offset.x + bounds.origin.x,
             reference_frame_relative_offset.y + bounds.origin.y,
             0.0);
 
         let iframe_reference_frame_id =
-            context.builder.push_reference_frame(Some(parent_id),
+            context.builder.push_reference_frame(Some(clip_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
         context.builder.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
@@ -578,17 +597,16 @@ impl Frame {
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
                 context.builder.add_text(clip_and_scroll,
                                          item.rect(),
                                          item.local_clip(),
                                          text_info.font_key,
                                          text_info.size,
-                                         text_info.blur_radius,
                                          &text_info.color,
                                          item.glyphs(),
                                          item.display_list().get(item.glyphs()).count(),
                                          text_info.glyph_options);
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
                                                                 &item.rect(),
@@ -661,16 +679,17 @@ impl Frame {
                                               &info.stacking_context,
                                               item.filters());
                 return Some(subtraversal);
             }
             SpecificDisplayItem::Iframe(ref info) => {
                 self.flatten_iframe(info.pipeline_id,
                                     clip_and_scroll.scroll_node_id,
                                     &item.rect(),
+                                    &item.local_clip(),
                                     context,
                                     reference_frame_relative_offset);
             }
             SpecificDisplayItem::Clip(ref info) => {
                 let complex_clips = context.get_complex_clips(pipeline_id, item.complex_clip().0);
                 let mut clip_region = ClipRegion::for_clip_node(*item.local_clip().clip_rect(),
                                                                 complex_clips,
                                                                 info.image_mask);
@@ -713,16 +732,24 @@ impl Frame {
             }
             SpecificDisplayItem::PopNestedDisplayList => context.pop_nested_display_list_ids(),
 
             // Do nothing; these are dummy items for the display list parser
             SpecificDisplayItem::SetGradientStops => { }
 
             SpecificDisplayItem::PopStackingContext =>
                 unreachable!("Should have returned in parent method."),
+            SpecificDisplayItem::PushTextShadow(shadow) => {
+                context.builder.push_text_shadow(shadow,
+                                                 clip_and_scroll,
+                                                 item.local_clip());
+            }
+            SpecificDisplayItem::PopTextShadow => {
+                context.builder.pop_text_shadow();
+            }
         }
         None
     }
 
     /// Try to optimize the rendering of a solid rectangle that is clipped by a single
     /// rounded rectangle, by only masking the parts of the rectangle that intersect
     /// the rounded parts of the clip. This is pretty simple now, so has a lot of
     /// potential for further optimizations.
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,28 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
-use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TileOffset};
-use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
+use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow};
+use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
 use frame::FrameId;
 use gpu_cache::GpuCache;
 use internal_types::HardwareCompositeOp;
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
-use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu};
+use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu};
 use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
@@ -101,26 +101,58 @@ fn make_polygon(stacking_context: &Stack
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
     pub cache_expiry_frames: u32,
 }
 
+struct PendingTextShadow {
+    shadow: TextShadow,
+    text_primitives: Vec<TextRunPrimitiveCpu>,
+    clip_and_scroll: ClipAndScrollInfo,
+    local_rect: LayerRect,
+    local_clip: LocalClip,
+}
+
+impl PendingTextShadow {
+    fn new(shadow: TextShadow,
+           clip_and_scroll: ClipAndScrollInfo,
+           local_clip: &LocalClip) -> PendingTextShadow {
+        PendingTextShadow {
+            shadow: shadow,
+            text_primitives: Vec::new(),
+            clip_and_scroll: clip_and_scroll,
+            local_clip: local_clip.clone(),
+            local_rect: LayerRect::zero(),
+        }
+    }
+
+    fn push(&mut self,
+            local_rect: LayerRect,
+            primitive: &TextRunPrimitiveCpu) {
+        self.text_primitives.push(primitive.clone());
+        let shadow_rect = local_rect.inflate(self.shadow.blur_radius,
+                                             self.shadow.blur_radius);
+        self.local_rect = self.local_rect.union(&shadow_rect);
+    }
+}
+
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
     prim_store: PrimitiveStore,
     cmds: Vec<PrimitiveRunCmd>,
     config: FrameBuilderConfig,
 
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     packed_layers: Vec<PackedLayer>,
+    pending_text_shadows: Vec<PendingTextShadow>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
@@ -139,32 +171,34 @@ impl FrameBuilder {
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
             Some(prev) => {
                 FrameBuilder {
                     stacking_context_store: recycle_vec(prev.stacking_context_store),
                     clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
                     cmds: recycle_vec(prev.cmds),
                     packed_layers: recycle_vec(prev.packed_layers),
+                    pending_text_shadows: recycle_vec(prev.pending_text_shadows),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
+                    pending_text_shadows: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size,
                     background_color,
                     config,
                     has_root_stacking_context: false,
@@ -282,16 +316,18 @@ impl FrameBuilder {
         self.has_root_stacking_context = true;
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
+        assert!(self.pending_text_shadows.is_empty(),
+            "Found unpopped text shadows when popping stacking context!");
     }
 
     pub fn push_reference_frame(&mut self,
                                 parent_id: Option<ClipId>,
                                 pipeline_id: PipelineId,
                                 rect: &LayerRect,
                                 transform: &LayerToScrollTransform,
                                 clip_scroll_tree: &mut ClipScrollTree)
@@ -387,16 +423,47 @@ impl FrameBuilder {
 
         clip_scroll_tree.add_node(node, new_node_id);
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
+    pub fn push_text_shadow(&mut self,
+                            shadow: TextShadow,
+                            clip_and_scroll: ClipAndScrollInfo,
+                            local_clip: &LocalClip) {
+        let text_shadow = PendingTextShadow::new(shadow,
+                                                 clip_and_scroll,
+                                                 local_clip);
+        self.pending_text_shadows.push(text_shadow);
+    }
+
+    pub fn pop_text_shadow(&mut self) {
+        let mut text_shadow = self.pending_text_shadows
+                                  .pop()
+                                  .expect("Too many PopTextShadows?");
+        if !text_shadow.text_primitives.is_empty() {
+            let prim_cpu = TextShadowPrimitiveCpu {
+                text_primitives: text_shadow.text_primitives,
+                shadow: text_shadow.shadow,
+            };
+
+            text_shadow.local_rect = text_shadow.local_rect
+                                                .translate(&text_shadow.shadow.offset);
+
+            self.add_primitive(text_shadow.clip_and_scroll,
+                               &text_shadow.local_rect,
+                               &text_shadow.local_clip,
+                               &[],
+                               PrimitiveContainer::TextShadow(prim_cpu));
+        }
+    }
+
     pub fn add_solid_rectangle(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: &LayerRect,
                                local_clip: &LocalClip,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
         if color.a == 0.0 {
             return;
@@ -727,42 +794,40 @@ impl FrameBuilder {
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     rect: LayerRect,
                     local_clip: &LocalClip,
                     font_key: FontKey,
                     size: Au,
-                    blur_radius: f32,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
-        if color.a == 0.0 {
+        let is_text_shadow = !self.pending_text_shadows.is_empty();
+
+        if color.a == 0.0 && !is_text_shadow {
             return
         }
 
         if size.0 <= 0 {
             return
         }
 
-        // Expand the rectangle of the text run by the blur radius.
-        let rect = rect.inflate(blur_radius, blur_radius);
-
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
         let mut render_mode = self.config.default_font_render_mode;
 
         // There are some conditions under which we can't use
         // subpixel text rendering, even if enabled.
         if render_mode == FontRenderMode::Subpixel {
             // text-blur shadow needs to force alpha AA.
-            if blur_radius != 0.0 {
+            if is_text_shadow {
                 render_mode = FontRenderMode::Alpha;
             }
 
             if color.a != 1.0 {
                 render_mode = FontRenderMode::Alpha;
             }
 
             // text on a stacking context that has filters
@@ -776,30 +841,35 @@ impl FrameBuilder {
                     render_mode = FontRenderMode::Alpha;
                 }
             }
         }
 
         let prim_cpu = TextRunPrimitiveCpu {
             font_key,
             logical_font_size: size,
-            blur_radius,
             glyph_range,
             glyph_count,
             glyph_instances: Vec::new(),
             color: *color,
             render_mode,
             glyph_options,
         };
 
-        self.add_primitive(clip_and_scroll,
-                           &rect,
-                           local_clip,
-                           &[],
-                           PrimitiveContainer::TextRun(prim_cpu));
+        if is_text_shadow {
+            for shadow in &mut self.pending_text_shadows {
+                shadow.push(rect, &prim_cpu);
+            }
+        } else {
+            self.add_primitive(clip_and_scroll,
+                               &rect,
+                               local_clip,
+                               &[],
+                               PrimitiveContainer::TextRun(prim_cpu));
+        }
     }
 
     pub fn fill_box_shadow_rect(&mut self,
                                 clip_and_scroll: ClipAndScrollInfo,
                                 box_bounds: &LayerRect,
                                 bs_rect: LayerRect,
                                 local_clip: &LocalClip,
                                 color: &ColorF,
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -256,16 +256,17 @@ impl GlyphRasterizer {
         // differences in rasterizers due to the different coordinates
         // that text runs get associated with by the texture cache allocator.
         rasterized_glyphs.sort_by(|a, b| a.request.cmp(&b.request));
 
         // Update the caches.
         for job in rasterized_glyphs {
             let image_id = job.result.and_then(
                 |glyph| if glyph.width > 0 && glyph.height > 0 {
+                    assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
                     let image_id = texture_cache.insert(
                         ImageDescriptor {
                             width: glyph.width,
                             height: glyph.height,
                             stride: None,
                             format: ImageFormat::BGRA8,
                             is_opaque: false,
                             offset: 0,
@@ -329,17 +330,17 @@ impl GlyphRequest {
     pub fn new(
         font_key: FontKey,
         size: Au,
         color: ColorF,
         index: u32,
         point: LayoutPoint,
         render_mode: FontRenderMode,
         glyph_options: Option<GlyphOptions>,
-    ) -> GlyphRequest {
+    ) -> Self {
         GlyphRequest {
             key: GlyphKey::new(font_key, size, color, index, point, render_mode),
             render_mode,
             glyph_options,
         }
     }
 }
 
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -198,16 +198,17 @@ impl DebugColorVertex {
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RenderTargetMode {
     None,
     SimpleRenderTarget,
     LayerRenderTarget(i32),      // Number of texture layers
 }
 
+#[derive(Debug)]
 pub enum TextureUpdateOp {
     Create {
       width: u32,
       height: u32,
       format: ImageFormat,
       filter: TextureFilter,
       mode: RenderTargetMode,
       data: Option<ImageData>,
@@ -233,16 +234,17 @@ pub enum TextureUpdateOp {
         height: u32,
         format: ImageFormat,
         filter: TextureFilter,
         mode: RenderTargetMode,
     },
     Free,
 }
 
+#[derive(Debug)]
 pub struct TextureUpdate {
     pub id: CacheTextureId,
     pub op: TextureUpdateOp,
 }
 
 pub struct TextureUpdateList {
     pub updates: Vec<TextureUpdate>,
 }
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -39,16 +39,24 @@ impl ClipRegion {
         ClipRegion {
             origin: rect.origin,
             main: LayerRect::new(LayerPoint::zero(), rect.size),
             image_mask,
             complex_clips,
         }
     }
 
+    pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
+        let complex_clips = match local_clip {
+            &LocalClip::Rect(_) => Vec::new(),
+            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
+        };
+        ClipRegion::for_clip_node(*local_clip.clip_rect(), complex_clips, None)
+    }
+
     pub fn for_local_clip(local_clip: &LocalClip) -> ClipRegion {
         let complex_clips = match local_clip {
             &LocalClip::Rect(_) => Vec::new(),
             &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
         };
 
         ClipRegion {
             origin: LayerPoint::zero(),
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -8,21 +8,32 @@ use api::{NativeFontHandle, GlyphOptions
 use api::{GlyphKey};
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
-use freetype::freetype::{FT_Done_Face, FT_Error};
+use freetype::freetype::{FT_Done_Face, FT_Error, FT_Int32};
 
 use std::{cmp, mem, ptr, slice};
 use std::collections::HashMap;
 
+// This constant is not present in the freetype
+// bindings due to bindgen not handling the way
+// the macro is defined.
+const FT_LOAD_TARGET_LIGHT: FT_Int32 = 1 << 16;
+
+// Default to slight hinting, which is what most
+// Linux distros use by default, and is a better
+// default than no hinting.
+// TODO(gw): Make this configurable.
+const GLYPH_LOAD_FLAGS: FT_Int32 = FT_LOAD_TARGET_LIGHT;
+
 struct Face {
     face: FT_Face,
 }
 
 pub struct FontContext {
     lib: FT_Library,
     faces: HashMap<FontKey, Face>,
 }
@@ -35,24 +46,16 @@ unsafe impl Send for FontContext {}
 pub struct RasterizedGlyph {
     pub top: f32,
     pub left: f32,
     pub width: u32,
     pub height: u32,
     pub bytes: Vec<u8>,
 }
 
-fn float_to_fixed(before: usize, f: f64) -> i32 {
-    ((1i32 << before) as f64 * f) as i32
-}
-
-fn float_to_fixed_ft(f: f64) -> i32 {
-    float_to_fixed(6, f)
-}
-
 const SUCCESS: FT_Error = FT_Error(0);
 
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
         unsafe {
             let result = FT_Init_FreeType(&mut lib);
             assert!(result.succeeded(), "Unable to initialize FreeType library {:?}", result);
@@ -110,24 +113,26 @@ impl FontContext {
 
     fn load_glyph(&self,
                   font_key: FontKey,
                   size: Au,
                   character: u32) -> Option<FT_GlyphSlot> {
 
         debug_assert!(self.faces.contains_key(&font_key));
         let face = self.faces.get(&font_key).unwrap();
-        let char_size = float_to_fixed_ft(size.to_f64_px());
+        let char_size = size.to_f64_px() * 64.0 + 0.5;
 
         assert_eq!(SUCCESS, unsafe {
             FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0)
         });
 
         let result = unsafe {
-            FT_Load_Glyph(face.face, character as FT_UInt, 0)
+            FT_Load_Glyph(face.face,
+                          character as FT_UInt,
+                          GLYPH_LOAD_FLAGS)
         };
 
         if result == SUCCESS {
             let slot = unsafe { (*face.face).glyph };
             assert!(slot != ptr::null_mut());
             Some(slot)
         } else {
             error!("Unable to load glyph for {} of size {:?} from font {:?}, {:?}",
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
 use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
-use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
+use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat};
 use api::device_length;
 use app_units::Au;
 use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo};
 use renderer::MAX_VERTEX_TEXTURE_WIDTH;
@@ -110,16 +110,17 @@ pub enum PrimitiveKind {
     TextRun,
     Image,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
+    TextShadow,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
@@ -475,44 +476,89 @@ impl RadialGradientPrimitiveCpu {
 
         let gradient_builder = GradientGpuBlockBuilder::new(self.stops_range,
                                                             display_list);
         gradient_builder.build(false, &mut request);
     }
 }
 
 #[derive(Debug, Clone)]
+pub struct TextShadowPrimitiveCpu {
+    pub text_primitives: Vec<TextRunPrimitiveCpu>,
+    pub shadow: TextShadow,
+}
+
+#[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font_key: FontKey,
     pub logical_font_size: Au,
-    pub blur_radius: f32,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     // TODO(gw): Maybe make this an Arc for sharing with resource cache
     pub glyph_instances: Vec<GlyphInstance>,
     pub color: ColorF,
     pub render_mode: FontRenderMode,
     pub glyph_options: Option<GlyphOptions>,
 }
 
-impl ToGpuBlocks for TextRunPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.push(self.color);
+impl TextRunPrimitiveCpu {
+    fn prepare_for_render(&mut self,
+                          resource_cache: &mut ResourceCache,
+                          device_pixel_ratio: f32,
+                          display_list: &BuiltDisplayList) {
+        // Cache the glyph positions, if not in the cache already.
+        // TODO(gw): In the future, remove `glyph_instances`
+        //           completely, and just reference the glyphs
+        //           directly from the displaty list.
+        if self.glyph_instances.is_empty() {
+            let src_glyphs = display_list.get(self.glyph_range);
+            for src in src_glyphs {
+                self.glyph_instances.push(GlyphInstance {
+                    index: src.index,
+                    point: src.point,
+                });
+            }
+        }
 
+        let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
+
+        resource_cache.request_glyphs(self.font_key,
+                                      font_size_dp,
+                                      self.color,
+                                      &self.glyph_instances,
+                                      self.render_mode,
+                                      self.glyph_options);
+    }
+
+    fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // Two glyphs are packed per GPU block.
         for glyph_chunk in self.glyph_instances.chunks(2) {
             // In the case of an odd number of glyphs, the
             // last glyph will get duplicated in the final
             // GPU block.
             let first_glyph = glyph_chunk.first().unwrap();
             let second_glyph = glyph_chunk.last().unwrap();
-            request.push([first_glyph.point.x,
-                          first_glyph.point.y,
-                          second_glyph.point.x,
-                          second_glyph.point.y]);
+            let data = match self.render_mode {
+                FontRenderMode::Mono |
+                FontRenderMode::Alpha => [
+                    first_glyph.point.x,
+                    first_glyph.point.y,
+                    second_glyph.point.x,
+                    second_glyph.point.y,
+                ],
+                // The sub-pixel offset has already been taken into account
+                // by the glyph rasterizer, thus the truncating here.
+                FontRenderMode::Subpixel => [
+                    first_glyph.point.x.trunc(),
+                    first_glyph.point.y.trunc(),
+                    second_glyph.point.x.trunc(),
+                    second_glyph.point.y.trunc(),
+                ],
+            };
+            request.push(data);
         }
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct GlyphPrimitive {
     offset: LayerPoint,
@@ -674,54 +720,58 @@ pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
     Image(ImagePrimitiveCpu),
     YuvImage(YuvImagePrimitiveCpu),
     Border(BorderPrimitiveCpu),
     AlignedGradient(GradientPrimitiveCpu),
     AngleGradient(GradientPrimitiveCpu),
     RadialGradient(RadialGradientPrimitiveCpu),
     BoxShadow(BoxShadowPrimitiveCpu),
+    TextShadow(TextShadowPrimitiveCpu),
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
     pub cpu_rectangles: Vec<RectanglePrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
+    pub cpu_text_shadows: Vec<TextShadowPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
     pub cpu_box_shadows: Vec<BoxShadowPrimitiveCpu>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_rectangles: Vec::new(),
             cpu_bounding_rects: Vec::new(),
             cpu_text_runs: Vec::new(),
+            cpu_text_shadows: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
             cpu_box_shadows: Vec::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_rectangles: recycle_vec(self.cpu_rectangles),
             cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
+            cpu_text_shadows: recycle_vec(self.cpu_text_shadows),
             cpu_images: recycle_vec(self.cpu_images),
             cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
             cpu_gradients: recycle_vec(self.cpu_gradients),
             cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
             cpu_borders: recycle_vec(self.cpu_borders),
             cpu_box_shadows: recycle_vec(self.cpu_box_shadows),
         }
     }
@@ -766,16 +816,33 @@ impl PrimitiveStore {
                     clip_task: None,
                     local_rect: *local_rect,
                     local_clip_rect: *local_clip_rect,
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
+            PrimitiveContainer::TextShadow(text_shadow) => {
+                let metadata = PrimitiveMetadata {
+                    opacity: PrimitiveOpacity::translucent(),
+                    clips,
+                    clip_cache_info: clip_info,
+                    prim_kind: PrimitiveKind::TextShadow,
+                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()),
+                    gpu_location: GpuCacheHandle::new(),
+                    render_task: None,
+                    clip_task: None,
+                    local_rect: *local_rect,
+                    local_clip_rect: *local_clip_rect,
+                };
+
+                self.cpu_text_shadows.push(text_shadow);
+                metadata
+            }
             PrimitiveContainer::Image(image_cpu) => {
                 let metadata = PrimitiveMetadata {
                     opacity: PrimitiveOpacity::translucent(),
                     clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_location: GpuCacheHandle::new(),
@@ -992,60 +1059,44 @@ impl PrimitiveStore {
                 // the patch is clamped / mirrored across the box shadow rect.
                 let box_shadow_cpu = &self.cpu_box_shadows[metadata.cpu_prim_index.0];
                 let edge_size = box_shadow_cpu.edge_size.ceil() * device_pixel_ratio;
                 let edge_size = edge_size as i32 + 2;   // Account for bilinear filtering
                 let cache_size = DeviceIntSize::new(edge_size, edge_size);
                 let location = RenderTaskLocation::Dynamic(None, cache_size);
                 metadata.render_task.as_mut().unwrap().location = location;
             }
-            PrimitiveKind::TextRun => {
-                let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
-
-                let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio);
-                let src_glyphs = display_list.get(text.glyph_range);
-
-                // Cache the glyph positions, if not in the cache already.
-                // TODO(gw): In the future, remove `glyph_instances`
-                //           completely, and just reference the glyphs
-                //           directly from the displaty list.
-                if text.glyph_instances.is_empty() {
-                    for src in src_glyphs {
-                        text.glyph_instances.push(GlyphInstance {
-                            index: src.index,
-                            point: src.point,
-                        });
-                    }
+            PrimitiveKind::TextShadow => {
+                let shadow = &mut self.cpu_text_shadows[metadata.cpu_prim_index.0];
+                for text in &mut shadow.text_primitives {
+                    text.prepare_for_render(resource_cache,
+                                            device_pixel_ratio,
+                                            display_list);
                 }
 
-                metadata.render_task = if text.blur_radius == 0.0 {
-                    None
-                } else {
-                    // This is a text-shadow element. Create a render task that will
-                    // render the text run to a target, and then apply a gaussian
-                    // blur to that text run in order to build the actual primitive
-                    // which will be blitted to the framebuffer.
-                    let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
-                    let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
-                    let cache_size = DeviceIntSize::new(cache_width, cache_height);
-                    let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
-                    let blur_radius = device_length(text.blur_radius,
-                                                    device_pixel_ratio);
-                    Some(RenderTask::new_blur(cache_key,
-                                              cache_size,
-                                              blur_radius,
-                                              prim_index))
-                };
-
-                resource_cache.request_glyphs(text.font_key,
-                                              font_size_dp,
-                                              text.color,
-                                              &text.glyph_instances,
-                                              text.render_mode,
-                                              text.glyph_options);
+                // This is a text-shadow element. Create a render task that will
+                // render the text run to a target, and then apply a gaussian
+                // blur to that text run in order to build the actual primitive
+                // which will be blitted to the framebuffer.
+                let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32;
+                let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32;
+                let cache_size = DeviceIntSize::new(cache_width, cache_height);
+                let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
+                let blur_radius = device_length(shadow.shadow.blur_radius,
+                                                device_pixel_ratio);
+                metadata.render_task = Some(RenderTask::new_blur(cache_key,
+                                                                 cache_size,
+                                                                 blur_radius,
+                                                                 prim_index));
+            }
+            PrimitiveKind::TextRun => {
+                let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
+                text.prepare_for_render(resource_cache,
+                                        device_pixel_ratio,
+                                        display_list);
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
 
                 match image_cpu.kind {
                     ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => {
                         resource_cache.request_image(image_key, image_rendering, tile_offset);
 
@@ -1113,17 +1164,25 @@ impl PrimitiveStore {
                 }
                 PrimitiveKind::RadialGradient => {
                     let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0];
                     gradient.build_gpu_blocks_for_angle_radial(display_list,
                                                                request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                    text.write_gpu_blocks(request);
+                    request.push(text.color);
+                    text.write_gpu_blocks(&mut request);
+                }
+                PrimitiveKind::TextShadow => {
+                    let prim = &self.cpu_text_shadows[metadata.cpu_prim_index.0];
+                    request.push(prim.shadow.color);
+                    for text in &prim.text_primitives {
+                        text.write_gpu_blocks(&mut request);
+                    }
                 }
             }
         }
 
         metadata
     }
 }
 
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -5,17 +5,17 @@
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use gpu_cache::GpuCache;
 use internal_types::{SourceTexture, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use rayon::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
@@ -55,17 +55,16 @@ pub struct RenderBackend {
     resource_cache: ResourceCache,
 
     scene: Scene,
     frame: Frame,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
     webrender_context_handle: Option<GLContextHandleWrapper>,
     webgl_contexts: HashMap<WebGLContextId, GLContextWrapper>,
-    dirty_webgl_contexts: HashSet<WebGLContextId>,
     current_bound_webgl_context_id: Option<WebGLContextId>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
 
     next_webgl_id: usize,
 
     vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
 
@@ -110,17 +109,16 @@ impl RenderBackend {
             resource_cache,
             gpu_cache: GpuCache::new(),
             scene: Scene::new(),
             frame: Frame::new(config),
             next_namespace_id: IdNamespace(1),
             notifier,
             webrender_context_handle,
             webgl_contexts: HashMap::new(),
-            dirty_webgl_contexts: HashSet::new(),
             current_bound_webgl_context_id: None,
             recorder,
             main_thread_dispatcher,
             next_webgl_id: 0,
             vr_compositor_handler,
             window_size: initial_window_size,
             inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), initial_window_size),
             render_on_scroll: false,
@@ -354,17 +352,16 @@ impl RenderBackend {
 
                                         let (real_size, texture_id, limits) = ctx.get_info();
 
                                         self.webgl_contexts.insert(id, ctx);
 
                                         self.resource_cache
                                             .add_webgl_texture(id, SourceTexture::WebGL(texture_id),
                                                                real_size);
-                                        self.dirty_webgl_contexts.insert(id);
 
                                         tx.send(Ok((id, limits))).unwrap();
                                     },
                                     Err(msg) => {
                                         tx.send(Err(msg.to_owned())).unwrap();
                                     }
                                 }
                             } else {
@@ -379,42 +376,39 @@ impl RenderBackend {
                             }
                             match ctx.resize(&size) {
                                 Ok(_) => {
                                     // Update webgl texture size. Texture id may change too.
                                     let (real_size, texture_id, _) = ctx.get_info();
                                     self.resource_cache
                                         .update_webgl_texture(context_id, SourceTexture::WebGL(texture_id),
                                                               real_size);
-                                    self.dirty_webgl_contexts.insert(context_id);
                                 },
                                 Err(msg) => {
                                     error!("Error resizing WebGLContext: {}", msg);
                                 }
                             }
                         }
                         ApiMsg::WebGLCommand(context_id, command) => {
                             // TODO: Buffer the commands and only apply them here if they need to
                             // be synchronous.
                             let ctx = &self.webgl_contexts[&context_id];
                             if Some(context_id) != self.current_bound_webgl_context_id {
                                 ctx.make_current();
                                 self.current_bound_webgl_context_id = Some(context_id);
                             }
                             ctx.apply_command(command);
-                            self.dirty_webgl_contexts.insert(context_id);
                         },
 
                         ApiMsg::VRCompositorCommand(context_id, command) => {
                             if Some(context_id) != self.current_bound_webgl_context_id {
                                 self.webgl_contexts[&context_id].make_current();
                                 self.current_bound_webgl_context_id = Some(context_id);
                             }
                             self.handle_vr_compositor_command(context_id, command);
-                            self.dirty_webgl_contexts.insert(context_id);
                         }
                         ApiMsg::GenerateFrame(property_bindings) => {
                             profile_scope!("GenerateFrame");
 
                             // Ideally, when there are property bindings present,
                             // we won't need to rebuild the entire frame here.
                             // However, to avoid conflicts with the ongoing work to
                             // refactor how scroll roots + transforms work, this
@@ -491,28 +485,20 @@ impl RenderBackend {
 
         // When running in OSMesa mode with texture sharing,
         // a flush is required on any GL contexts to ensure
         // that read-back from the shared texture returns
         // valid data! This should be fine to have run on all
         // implementations - a single flush for each webgl
         // context at the start of a render frame should
         // incur minimal cost.
-        // glFlush is not enough in some GPUs.
-        // glFlush doesn't guarantee the completion of the GL commands when the shared texture is sampled.
-        // This leads to some graphic glitches on some demos or even nothing being rendered at all (GPU Mali-T880).
-        // glFinish guarantees the completion of the commands but it may hurt performance a lot.
-        // Sync Objects are the recommended way to ensure that textures are ready in OpenGL 3.0+.
-        // They are more performant than glFinish and guarantee the completion of the GL commands.
-        for (id, webgl_context) in &self.webgl_contexts {
-            if self.dirty_webgl_contexts.remove(&id) {
-                webgl_context.make_current();
-                webgl_context.apply_command(WebGLCommand::FenceAndWaitSync);
-                webgl_context.unbind();
-            }
+        for (_, webgl_context) in &self.webgl_contexts {
+            webgl_context.make_current();
+            webgl_context.apply_command(WebGLCommand::Flush);
+            webgl_context.unbind();
         }
 
         let accumulated_scale_factor = self.accumulated_scale_factor();
         self.frame.create(&self.scene,
                           &mut self.resource_cache,
                           self.window_size,
                           self.inner_rect,
                           accumulated_scale_factor);
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -15,17 +15,17 @@ use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
 use std::sync::Arc;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use api::{Epoch, FontKey, FontTemplate, GlyphKey, ImageKey, ImageRendering};
 use api::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use api::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
-use api::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
+use api::{GlyphOptions, GlyphInstance, SubpixelPoint, TileOffset, TileSize};
 use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData};
 use api::BlobImageResources;
 use api::{ExternalImageData, ExternalImageType, LayoutPoint};
 use rayon::ThreadPool;
 use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest};
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
@@ -38,29 +38,31 @@ const DEFAULT_TILE_SIZE: TileSize = 512;
 // storing the coordinates as texel values
 // we don't need to go through and update
 // various CPU-side structures.
 pub struct CacheItem {
     pub texture_id: SourceTexture,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
+#[derive(Debug)]
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
     pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
 
+#[derive(Debug)]
 struct ImageResource {
     data: ImageData,
     descriptor: ImageDescriptor,
     epoch: Epoch,
     tiling: Option<TileSize>,
     dirty_rect: Option<DeviceUintRect>
 }
 
@@ -475,31 +477,31 @@ impl ResourceCache {
                          font_key: FontKey,
                          size: Au,
                          color: ColorF,
                          glyph_instances: &[GlyphInstance],
                          render_mode: FontRenderMode,
                          glyph_options: Option<GlyphOptions>,
                          mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) {
         debug_assert_eq!(self.state, State::QueryResources);
-        let mut glyph_key = GlyphRequest::new(
+        let mut glyph_request = GlyphRequest::new(
             font_key,
             size,
             color,
             0,
-            LayoutPoint::new(0.0, 0.0),
+            LayoutPoint::zero(),
             render_mode,
             glyph_options
         );
         let mut texture_id = None;
         for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() {
-            glyph_key.key.index = glyph_instance.index;
-            glyph_key.key.subpixel_point.set_offset(glyph_instance.point, render_mode);
+            glyph_request.key.index = glyph_instance.index;
+            glyph_request.key.subpixel_point = SubpixelPoint::new(glyph_instance.point, render_mode);
 
-            let image_id = self.cached_glyphs.get(&glyph_key, self.current_frame_id);
+            let image_id = self.cached_glyphs.get(&glyph_request, self.current_frame_id);
             let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id));
             if let Some(cache_item) = cache_item {
                 f(loop_index, &cache_item.uv_rect_handle);
                 debug_assert!(texture_id == None ||
                               texture_id == Some(cache_item.texture_id));
                 texture_id = Some(cache_item.texture_id);
             }
         }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -45,24 +45,19 @@ trait AlphaBatchHelpers {
                       metadata: &PrimitiveMetadata) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
     fn get_blend_mode(&self, needs_blending: bool, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                if text_run_cpu.blur_radius == 0.0 {
-                    match text_run_cpu.render_mode {
-                        FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
-                        FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
-                    }
-                } else {
-                    // Text runs drawn to blur never get drawn with subpixel AA.
-                    BlendMode::Alpha
+                match text_run_cpu.render_mode {
+                    FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
+                    FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
                 }
             }
             PrimitiveKind::Image |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {
                 if needs_blending {
                     BlendMode::PremultipliedAlpha
@@ -478,60 +473,50 @@ impl AlphaRenderItem {
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        let batch_kind = if text_cpu.blur_radius == 0.0 {
-                            AlphaBatchKind::TextRun
-                        } else {
-                            // Select a generic primitive shader that can blit the
-                            // results of the cached text blur to the framebuffer,
-                            // applying tile clipping etc.
-                            AlphaBatchKind::CacheImage
-                        };
-
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
-                        let cache_task_index = match prim_metadata.render_task {
-                            Some(ref task) => {
-                                let cache_task_id = task.id;
-                                render_tasks.get_task_index(&cache_task_id,
-                                                            child_pass_index).0 as i32
-                            }
-                            None => 0,
-                        };
-
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
                         let texture_id = ctx.resource_cache.get_glyphs(text_cpu.font_key,
                                                                        font_size_dp,
                                                                        text_cpu.color,
                                                                        &text_cpu.glyph_instances,
                                                                        text_cpu.render_mode,
                                                                        text_cpu.glyph_options, |index, handle| {
                             let uv_address = handle.as_int(gpu_cache);
-                            instances.push(base_instance.build(index as i32, cache_task_index, uv_address));
+                            instances.push(base_instance.build(index as i32, 0, uv_address));
                         });
 
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
                                 colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                             };
 
-                            let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
+                            let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
                             let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                             batch.add_instances(&instances);
                         }
                     }
+                    PrimitiveKind::TextShadow => {
+                        let cache_task_id = prim_metadata.render_task.as_ref().expect("no render task!").id;
+                        let cache_task_index = render_tasks.get_task_index(&cache_task_id,
+                                                                           child_pass_index);
+                        let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, no_textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(0, cache_task_index.0 as i32, 0));
+                    }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         for part_index in 0..(gradient_cpu.stops_count - 1) {
                             batch.add_instance(base_instance.build(part_index as i32, 0, 0));
                         }
                     }
@@ -1038,53 +1023,58 @@ impl RenderTarget for ColorRenderTarget 
                     PrimitiveKind::BoxShadow => {
                         let instance = SimplePrimitiveInstance::new(prim_address,
                                                                     render_tasks.get_task_index(&task.id, pass_index),
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
                         self.box_shadow_cache_prims.push(instance.build(0, 0, 0));
                     }
-                    PrimitiveKind::TextRun => {
-                        let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        // We only cache text runs with a text-shadow (for now).
-                        debug_assert!(text.blur_radius != 0.0);
+                    PrimitiveKind::TextShadow => {
+                        let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
 
                         // todo(gw): avoid / recycle this allocation...
                         let mut instances = Vec::new();
+                        let mut base_index = 0;
 
-                        let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
+                        let task_index = render_tasks.get_task_index(&task.id, pass_index);
 
                         let instance = SimplePrimitiveInstance::new(prim_address,
-                                                                    render_tasks.get_task_index(&task.id, pass_index),
+                                                                    task_index,
                                                                     RenderTaskIndex(0),
                                                                     PackedLayerIndex(0),
                                                                     0);     // z is disabled for rendering cache primitives
 
-                        let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
-                                                                       font_size_dp,
-                                                                       text.color,
-                                                                       &text.glyph_instances,
-                                                                       text.render_mode,
-                                                                       text.glyph_options, |index, handle| {
-                            let uv_address = handle.as_int(gpu_cache);
-                            instances.push(instance.build(index as i32, 0, uv_address));
-                        });
+                        for text in &prim.text_primitives {
+                            let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
+
+                            let texture_id = ctx.resource_cache.get_glyphs(text.font_key,
+                                                                           font_size_dp,
+                                                                           text.color,
+                                                                           &text.glyph_instances,
+                                                                           text.render_mode,
+                                                                           text.glyph_options, |index, handle| {
+                                let uv_address = handle.as_int(gpu_cache);
+                                instances.push(instance.build(base_index + index as i32, 0, uv_address));
+                            });
 
-                        if texture_id != SourceTexture::Invalid {
-                            let textures = BatchTextures {
-                                colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
-                            };
+                            if texture_id != SourceTexture::Invalid {
+                                let textures = BatchTextures {
+                                    colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
+                                };
 
-                            self.text_run_cache_prims.extend_from_slice(&instances);
+                                self.text_run_cache_prims.extend_from_slice(&instances);
+                                base_index += text.glyph_instances.len() as i32;
+                                instances.clear();
 
-                            debug_assert!(textures.colors[0] != SourceTexture::Invalid);
-                            debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
-                                          self.text_run_textures.colors[0] == textures.colors[0]);
-                            self.text_run_textures = textures;
+                                debug_assert!(textures.colors[0] != SourceTexture::Invalid);
+                                debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid ||
+                                              self.text_run_textures.colors[0] == textures.colors[0]);
+                                self.text_run_textures = textures;
+                            }
                         }
                     }
                     _ => {
                         // No other primitives make use of primitive caching yet!
                         unreachable!()
                     }
                 }
             }
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_api"
-version = "0.47.0"
+version = "0.48.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -112,17 +112,17 @@ pub struct GLContextAttributes([u8; 0]);
 
 #[cfg(not(feature = "webgl"))]
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct GLLimits([u8; 0]);
 
 #[cfg(not(feature = "webgl"))]
 #[derive(Clone, Deserialize, Serialize)]
 pub enum WebGLCommand {
-    FenceAndWaitSync,
+    Flush,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct PipelineId(pub u32, pub u32);
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -60,16 +60,18 @@ pub enum SpecificDisplayItem {
     Gradient(GradientDisplayItem),
     RadialGradient(RadialGradientDisplayItem),
     Iframe(IframeDisplayItem),
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops,
     PushNestedDisplayList,
     PopNestedDisplayList,
+    PushTextShadow(TextShadow),
+    PopTextShadow,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub parent_id: ClipId,
     pub image_mask: Option<ImageMask>,
 }
@@ -79,17 +81,16 @@ pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct TextDisplayItem {
     pub font_key: FontKey,
     pub size: Au,
     pub color: ColorF,
-    pub blur_radius: f32,
     pub glyph_options: Option<GlyphOptions>,
 } // IMPLICIT: glyphs: Vec<GlyphInstance>
 
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     // These are currently only used on windows for dwrite fonts.
     pub use_embedded_bitmap: bool,
     pub force_gdi_rendering: bool,
@@ -219,16 +220,23 @@ pub struct BoxShadowDisplayItem {
     pub offset: LayoutVector2D,
     pub color: ColorF,
     pub blur_radius: f32,
     pub spread_radius: f32,
     pub border_radius: f32,
     pub clip_mode: BoxShadowClipMode,
 }
 
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct TextShadow {
+    pub offset: LayoutVector2D,
+    pub color: ColorF,
+    pub blur_radius: f32,
+}
+
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum ExtendMode {
     Clamp,
     Repeat,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -9,17 +9,17 @@ use serde::ser::{SerializeSeq, Serialize
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
 use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
 use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
 use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
 use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip};
 use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
 use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
-use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem};
+use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem};
 use {YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
@@ -538,30 +538,28 @@ impl DisplayListBuilder {
 
     pub fn push_text(&mut self,
                      rect: LayoutRect,
                      local_clip: Option<LocalClip>,
                      glyphs: &[GlyphInstance],
                      font_key: FontKey,
                      color: ColorF,
                      size: Au,
-                     blur_radius: f32,
                      glyph_options: Option<GlyphOptions>) {
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
         // the font_advance.html reftest, which creates a very large
         // font as a crash test - the rendering is also ignored
         // by the azure renderer.
         if size < Au::from_px(4096) {
             let item = SpecificDisplayItem::Text(TextDisplayItem {
                 color,
                 font_key,
                 size,
-                blur_radius,
                 glyph_options,
             });
 
             self.push_item(item, rect, local_clip);
             self.push_iter(glyphs);
         }
     }
 
@@ -892,33 +890,49 @@ impl DisplayListBuilder {
         self.clip_stack.push(info);
     }
 
     pub fn pop_clip_id(&mut self) {
         self.clip_stack.pop();
         assert!(self.clip_stack.len() > 0);
     }
 
-    pub fn push_iframe(&mut self, rect: LayoutRect, pipeline_id: PipelineId) {
+    pub fn push_iframe(&mut self,
+                       rect: LayoutRect,
+                       local_clip: Option<LocalClip>,
+                       pipeline_id: PipelineId) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem { pipeline_id: pipeline_id });
-        self.push_item(item, rect, None);
+        self.push_item(item, rect, local_clip);
     }
 
     // Don't use this function. It will go away.
     //
     // We're using this method as a hack in Gecko to retain parts sub-parts of display
     // lists so that we can regenerate them without building Gecko display items. WebRender
     // will replace references to the root scroll frame id with the current scroll frame
     // id.
     pub fn push_nested_display_list(&mut self, built_display_list: &BuiltDisplayList) {
         self.push_new_empty_item(SpecificDisplayItem::PushNestedDisplayList);
         self.data.extend_from_slice(&built_display_list.data);
         self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList);
     }
 
+    pub fn push_text_shadow(&mut self,
+                            rect: LayoutRect,
+                            local_clip: Option<LocalClip>,
+                            shadow: TextShadow) {
+        self.push_item(SpecificDisplayItem::PushTextShadow(shadow),
+                       rect,
+                       local_clip);
+    }
+
+    pub fn pop_text_shadow(&mut self) {
+        self.push_new_empty_item(SpecificDisplayItem::PopTextShadow);
+    }
+
     pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
         let end_time = precise_time_ns();
 
         (self.pipeline_id,
          self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
                 builder_start_time: self.builder_start_time,
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -144,21 +144,16 @@ impl SubpixelPoint {
             x: render_mode.subpixel_quantize_offset(point.x),
             y: render_mode.subpixel_quantize_offset(point.y),
         }
     }
 
     pub fn to_f64(&self) -> (f64, f64) {
         (self.x.into(), self.y.into())
     }
-
-    pub fn set_offset(&mut self, point: LayoutPoint, render_mode: FontRenderMode) {
-        self.x = render_mode.subpixel_quantize_offset(point.x);
-        self.y = render_mode.subpixel_quantize_offset(point.y);
-    }
 }
 
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub struct GlyphKey {
     pub font_key: FontKey,
     // The font size is in *device* pixels, not logical pixels.
     // It is stored as an Au since we need sub-pixel sizes, but
     // can't store as a f32 due to use of this type as a hash key.
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -87,17 +87,17 @@ impl ImageDescriptor {
         }
     }
 
     pub fn compute_stride(&self) -> u32 {
         self.stride.unwrap_or(self.width * self.format.bytes_per_pixel().unwrap())
     }
 }
 
-#[derive(Clone, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
     Raw(Arc<Vec<u8>>),
     Blob(BlobImageData),
     External(ExternalImageData),
 }
 
 impl ImageData {
     pub fn new(bytes: Vec<u8>) -> ImageData {
--- a/gfx/webrender_api/src/webgl.rs
+++ b/gfx/webrender_api/src/webgl.rs
@@ -124,17 +124,16 @@ pub enum WebGLCommand {
     DrawingBufferWidth(MsgSender<i32>),
     DrawingBufferHeight(MsgSender<i32>),
     Finish(MsgSender<()>),
     Flush,
     GenerateMipmap(u32),
     CreateVertexArray(MsgSender<Option<WebGLVertexArrayId>>),
     DeleteVertexArray(WebGLVertexArrayId),
     BindVertexArray(Option<WebGLVertexArrayId>),
-    FenceAndWaitSync,
 }
 
 #[cfg(feature = "nightly")]
 macro_rules! define_resource_id_struct {
     ($name:ident) => {
         #[derive(Clone, Copy, Eq, Hash, PartialEq)]
         pub struct $name(NonZero<u32>);
 
@@ -384,18 +383,17 @@ impl fmt::Debug for WebGLCommand {
             TexSubImage2D(..) => "TexSubImage2D",
             DrawingBufferWidth(..) => "DrawingBufferWidth",
             DrawingBufferHeight(..) => "DrawingBufferHeight",
             Finish(..) => "Finish",
             Flush => "Flush",
             GenerateMipmap(..) => "GenerateMipmap",
             CreateVertexArray(..) => "CreateVertexArray",
             DeleteVertexArray(..) => "DeleteVertexArray",
-            BindVertexArray(..) => "BindVertexArray",
-            FenceAndWaitSync => "FenceAndWaitSync",
+            BindVertexArray(..) => "BindVertexArray"
         };
 
         write!(f, "CanvasWebGLMsg::{}(..)", name)
     }
 }
 
 impl WebGLCommand {
     /// NOTE: This method consumes the command
@@ -628,18 +626,16 @@ impl WebGLCommand {
             WebGLCommand::GenerateMipmap(target) =>
                 ctx.gl().generate_mipmap(target),
             WebGLCommand::CreateVertexArray(chan) =>
                 Self::create_vertex_array(ctx.gl(), chan),
             WebGLCommand::DeleteVertexArray(id) =>
                 ctx.gl().delete_vertex_arrays(&[id.get()]),
             WebGLCommand::BindVertexArray(id) =>
                 ctx.gl().bind_vertex_array(id.map_or(0, WebGLVertexArrayId::get)),
-            WebGLCommand::FenceAndWaitSync =>
-                Self::fence_and_wait_sync(ctx.gl()),
         }
 
         // FIXME: Use debug_assertions once tests are run with them
         let error = ctx.gl().get_error();
         assert!(error == gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
     }
 
     fn read_pixels(gl: &gl::Gl, x: i32, y: i32, width: i32, height: i32, format: u32, pixel_type: u32,
@@ -1039,18 +1035,9 @@ impl WebGLCommand {
     }
 
 
     #[inline]
     fn compile_shader(gl: &gl::Gl, shader_id: WebGLShaderId, source: String) {
         gl.shader_source(shader_id.get(), &[source.as_bytes()]);
         gl.compile_shader(shader_id.get());
     }
-
-    fn fence_and_wait_sync(gl: &gl::Gl) {
-        // Call FenceSync and ClientWaitSync to ensure that textures are ready.
-        let sync = gl.fence_sync(gl::SYNC_GPU_COMMANDS_COMPLETE, 0);
-        // SYNC_FLUSH_COMMANDS_BIT is used to automatically generate a glFlush before blocking on the sync object.
-        gl.wait_sync(sync, gl::SYNC_FLUSH_COMMANDS_BIT, gl::TIMEOUT_IGNORED);
-        // Release GLsync object
-        gl.delete_sync(sync);
-    }
 }