Bug 1492880. Update webrender to commit a601f9c291cee83257241ef61aaf62353c613438
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 20 Sep 2018 23:27:49 -0400
changeset 437609 d6bea517aec2a034b9eafc31c0934fd57fc2c3f8
parent 437608 5ab8b903147a0cc97b21d278299840b9e38aa1f6
child 437610 4a4ccba9abe32f8dc2ca8eba0da0d064ef7e37cc
push id34688
push userebalazs@mozilla.com
push dateFri, 21 Sep 2018 09:39:54 +0000
treeherdermozilla-central@8b93a94b92c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1492880
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1492880. Update webrender to commit a601f9c291cee83257241ef61aaf62353c613438
gfx/webrender/Cargo.toml
gfx/webrender/res/clip_shared.glsl
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -41,37 +41,41 @@ serde_json = { optional = true, version 
 smallvec = "0.6"
 thread_profiler = "0.1.1"
 time = "0.1"
 webrender_api = { version = "0.57.2", path = "../webrender_api" }
 ws = { optional = true, version = "0.7.3" }
 
 [dependencies.pathfinder_font_renderer]
 git = "https://github.com/pcwalton/pathfinder"
+branch = "webrender"
 optional = true
 # Uncomment to test FreeType on macOS:
 # features = ["freetype"]
 
 [dependencies.pathfinder_gfx_utils]
 git = "https://github.com/pcwalton/pathfinder"
+branch = "webrender"
 optional = true
 
 [dependencies.pathfinder_partitioner]
 git = "https://github.com/pcwalton/pathfinder"
+branch = "webrender"
 optional = true
 
 [dependencies.pathfinder_path_utils]
 git = "https://github.com/pcwalton/pathfinder"
+branch = "webrender"
 optional = true
 
 [dev-dependencies]
 mozangle = "0.1"
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.4", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.6"
-core-graphics = "0.16"
-core-text = { version = "11", default-features = false }
+core-graphics = "0.17.1"
+core-text = { version = "13", default-features = false }
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -54,29 +54,26 @@ RectWithSize intersect_rect(RectWithSize
 // which is the intersection of all clip instances of a given primitive
 ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
                                       Transform prim_transform,
                                       Transform clip_transform,
                                       ClipArea area) {
     vec2 device_pos = area.screen_origin +
                       aPosition.xy * area.common_data.task_rect.size;
 
-    // TODO(gw): We only check the clip transform matrix here. We should
-    //           probably also be checking the prim_transform matrix. I
-    //           have left it as is for now, since that matches the
-    //           previous behavior.
-    if (clip_transform.is_axis_aligned) {
+    if (clip_transform.is_axis_aligned && prim_transform.is_axis_aligned) {
+        mat4 snap_mat = clip_transform.m * prim_transform.inv_m;
         vec4 snap_positions = compute_snap_positions(
-            clip_transform.m,
+            snap_mat,
             local_clip_rect
         );
 
         vec2 snap_offsets = compute_snap_offset_impl(
             device_pos,
-            clip_transform.m,
+            snap_mat,
             local_clip_rect,
             RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy),
             snap_positions
         );
 
         device_pos -= snap_offsets;
     }
 
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -5,45 +5,49 @@
 use api::{ColorU, FontKey, FontRenderMode, GlyphDimensions};
 use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
 use app_units::Au;
 use core_foundation::array::{CFArray, CFArrayRef};
 use core_foundation::base::TCFType;
 use core_foundation::dictionary::CFDictionary;
 use core_foundation::number::{CFNumber, CFNumberRef};
 use core_foundation::string::{CFString, CFStringRef};
-use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little};
-#[cfg(not(feature = "pathfinder"))]
-use core_graphics::base::kCGImageAlphaPremultipliedFirst;
+use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst};
+use core_graphics::base::{kCGBitmapByteOrder32Little};
 use core_graphics::color_space::CGColorSpace;
 use core_graphics::context::CGContext;
 #[cfg(not(feature = "pathfinder"))]
-use core_graphics::context::CGTextDrawingMode;
+use core_graphics::context::{CGBlendMode, CGTextDrawingMode};
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGGlyph};
 use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
 #[cfg(not(feature = "pathfinder"))]
-use core_graphics::geometry::CGRect;
+use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect};
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
 use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
+use euclid::Size2D;
 use gamma_lut::{ColorLut, GammaLut};
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
 #[cfg(feature = "pathfinder")]
 use glyph_rasterizer::NativeFontHandleWrapper;
 #[cfg(not(feature = "pathfinder"))]
 use glyph_rasterizer::{GlyphFormat, GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
+const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32;
+
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
     ct_fonts: FastHashMap<(FontKey, Au, Vec<FontVariation>), CTFont>,
     #[allow(dead_code)]
+    graphics_context: GraphicsContext,
+    #[allow(dead_code)]
     gamma_lut: GammaLut,
 }
 
 // core text is safe to use on multiple threads and non-shareable resources are
 // all hidden inside their font context.
 unsafe impl Send for FontContext {}
 
 struct GlyphMetrics {
@@ -111,17 +115,19 @@ fn get_glyph_metrics(
             rasterized_height: 0,
             rasterized_ascent: 0,
             rasterized_descent: 0,
             advance: 0.0,
         };
     }
 
     let mut advance = CGSize { width: 0.0, height: 0.0 };
-    ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
+    unsafe {
+        ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1);
+    }
 
     if bounds.size.width > 0.0 {
         bounds.size.width += extra_width;
     }
     if advance.width > 0.0 {
         advance.width += extra_width;
     }
 
@@ -276,16 +282,17 @@ impl FontContext {
 
         // Force CG to use sRGB color space to gamma correct.
         let contrast = 0.0;
         let gamma = 0.0;
 
         Ok(FontContext {
             cg_fonts: FastHashMap::default(),
             ct_fonts: FastHashMap::default(),
+            graphics_context: GraphicsContext::new(),
             gamma_lut: GammaLut::new(contrast, gamma, gamma),
         })
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.cg_fonts.contains_key(font_key)
     }
 
@@ -339,22 +346,24 @@ impl FontContext {
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         let character = ch as u16;
         let mut glyph = 0;
 
         self.get_ct_font(font_key, Au::from_px(16), &[])
             .and_then(|ref ct_font| {
-                let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);
+                unsafe {
+                    let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1);
 
-                if result {
-                    Some(glyph as u32)
-                } else {
-                    None
+                    if result {
+                        Some(glyph as u32)
+                    } else {
+                        None
+                    }
                 }
             })
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         key: &GlyphKey,
@@ -489,21 +498,27 @@ impl FontContext {
         let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
         let scale = font.oversized_scale_factor(x_scale, y_scale);
         let size = font.size.scale_by((y_scale / scale) as f32);
         let ct_font = match self.get_ct_font(font.font_key, size, &font.variations) {
             Some(font) => font,
             None => return GlyphRasterResult::LoadFailed,
         };
 
-        let bitmap = is_bitmap_font(&ct_font);
-        let (mut shape, (x_offset, y_offset)) = if bitmap {
-            (FontTransform::identity(), (0.0, 0.0))
+        let glyph_type = if is_bitmap_font(&ct_font) {
+            GlyphType::Bitmap
         } else {
-            (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
+            GlyphType::Vector
+        };
+
+        let (mut shape, (x_offset, y_offset)) = match glyph_type {
+            GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)),
+            GlyphType::Vector => {
+                (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
+            }
         };
         if font.flags.contains(FontInstanceFlags::FLIP_X) {
             shape = shape.flip_x();
         }
         if font.flags.contains(FontInstanceFlags::FLIP_Y) {
             shape = shape.flip_y();
         }
         if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
@@ -521,67 +536,36 @@ impl FontContext {
                 tx: 0.0,
                 ty: 0.0,
             })
         } else {
             None
         };
 
         let glyph = key.index() as CGGlyph;
-        let (strike_scale, pixel_step) = if bitmap { (y_scale, 1.0) } else { (x_scale, y_scale / x_scale) };
+        let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap {
+            (y_scale, 1.0)
+        } else {
+            (x_scale, y_scale / x_scale)
+        };
+
         let extra_strikes = font.get_extra_strikes(strike_scale / scale);
         let metrics = get_glyph_metrics(
             &ct_font,
             transform.as_ref(),
             glyph,
             x_offset,
             y_offset,
             extra_strikes as f64 * pixel_step,
         );
         if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
             return GlyphRasterResult::LoadFailed
         }
 
-        // The result of this function, in all render modes, is going to be a
-        // BGRA surface with white text on transparency using premultiplied
-        // alpha. For subpixel text, the RGB values will be the mask value for
-        // the individual components. For bitmap glyphs, the RGB values will be
-        // the (premultiplied) color of the pixel. For Alpha and Mono, each
-        // pixel will have R==G==B==A at the end of this function.
-        // We access the color channels in little-endian order.
-        // The CGContext will create and own our pixel buffer.
-        // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
-        // an opaque background. In order to hit the most efficient path in CG
-        // for this, we will tell CG that the CGContext is opaque, by passing
-        // an "[...]AlphaNone[...]" context flag. This creates a slight
-        // contradiction to the way we use the buffer after CG is done with it,
-        // because we will convert it into text-on-transparency. But that's ok;
-        // we still get four bytes per pixel and CG won't mess with the alpha
-        // channel after we've stopped calling CG functions. We just need to
-        // make sure that we don't look at the alpha values of the pixels that
-        // we get from CG, and compute our own alpha value only from RGB.
-        // Note that CG requires kCGBitmapByteOrder32Little in order to do
-        // subpixel AA at all (which we need it to do in both Subpixel and
-        // Alpha+smoothing mode). But little-endian is what we want anyway, so
-        // this works out nicely.
-        let context_flags = if bitmap {
-            kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst
-        } else {
-            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst
-        };
-
-        let mut cg_context = CGContext::create_bitmap_context(
-            None,
-            metrics.rasterized_width as usize,
-            metrics.rasterized_height as usize,
-            8,
-            metrics.rasterized_width as usize * 4,
-            &CGColorSpace::create_device_rgb(),
-            context_flags,
-        );
+        let raster_size = Size2D::new(metrics.rasterized_width, metrics.rasterized_height);
 
         // If the font render mode is Alpha, we support two different ways to
         // compute the grayscale mask, depending on the value of the platform
         // options' font_smoothing flag:
         //  - Alpha + smoothing:
         //    We will recover a grayscale mask from a subpixel rasterization, in
         //    such a way that the result looks as close to subpixel text
         //    blending as we can make it. This involves gamma correction,
@@ -598,84 +582,102 @@ impl FontContext {
         // uses less font dilation (looks thinner) than dark text.
         // As a consequence, when we ask CG to rasterize with subpixel AA, we
         // will render white-on-black text as opposed to black-on-white text if
         // the text color brightness exceeds a certain threshold. This applies
         // to both the Subpixel and the "Alpha + smoothing" modes, but not to
         // the "Alpha without smoothing" and Mono modes.
         let use_white_on_black = should_use_white_on_black(font.color);
         let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING);
-        let (antialias, smooth, text_color, bg_color, bg_alpha, invert) = if bitmap {
-            (true, false, 0.0, 0.0, 0.0, false)
-        } else {
-            match (font.render_mode, use_font_smoothing) {
-                (FontRenderMode::Subpixel, _) |
-                (FontRenderMode::Alpha, true) => if use_white_on_black {
-                    (true, true, 1.0, 0.0, 1.0, false)
-                } else {
-                    (true, true, 0.0, 1.0, 1.0, true)
-                },
-                (FontRenderMode::Alpha, false) => (true, false, 0.0, 1.0, 1.0, true),
-                (FontRenderMode::Mono, _) => (false, false, 0.0, 1.0, 1.0, true),
+        let (antialias, smooth, text_color, bg_color, bg_alpha, invert) = match glyph_type {
+            GlyphType::Bitmap => (true, false, 0.0, 0.0, 0.0, false),
+            GlyphType::Vector => {
+                match (font.render_mode, use_font_smoothing) {
+                    (FontRenderMode::Subpixel, _) |
+                    (FontRenderMode::Alpha, true) => if use_white_on_black {
+                        (true, true, 1.0, 0.0, 1.0, false)
+                    } else {
+                        (true, true, 0.0, 1.0, 1.0, true)
+                    },
+                    (FontRenderMode::Alpha, false) => (true, false, 0.0, 1.0, 1.0, true),
+                    (FontRenderMode::Mono, _) => (false, false, 0.0, 1.0, 1.0, true),
+                }
             }
         };
 
-        // These are always true in Gecko, even for non-AA fonts
-        cg_context.set_allows_font_subpixel_positioning(true);
-        cg_context.set_should_subpixel_position_fonts(true);
+        {
+            let cg_context = self.graphics_context.get_context(&raster_size, glyph_type);
+
+            // These are always true in Gecko, even for non-AA fonts
+            cg_context.set_allows_font_subpixel_positioning(true);
+            cg_context.set_should_subpixel_position_fonts(true);
+
+            // Don't quantize because we're doing it already.
+            cg_context.set_allows_font_subpixel_quantization(false);
+            cg_context.set_should_subpixel_quantize_fonts(false);
+
+            cg_context.set_should_smooth_fonts(smooth);
+            cg_context.set_should_antialias(antialias);
 
-        // Don't quantize because we're doing it already.
-        cg_context.set_allows_font_subpixel_quantization(false);
-        cg_context.set_should_subpixel_quantize_fonts(false);
+            // Fill the background. This could be opaque white, opaque black, or
+            // transparency.
+            cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_alpha);
+            let rect = CGRect {
+                origin: CGPoint { x: 0.0, y: 0.0 },
+                size: CGSize {
+                    width: metrics.rasterized_width as f64,
+                    height: metrics.rasterized_height as f64,
+                },
+            };
 
-        cg_context.set_should_smooth_fonts(smooth);
-        cg_context.set_should_antialias(antialias);
+            // Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER
+            // operator, which can't clear to the transparent color!
+            cg_context.set_blend_mode(CGBlendMode::Copy);
+            cg_context.fill_rect(rect);
+            cg_context.set_blend_mode(CGBlendMode::Normal);
 
-        // Fill the background. This could be opaque white, opaque black, or
-        // transparency.
-        cg_context.set_rgb_fill_color(bg_color, bg_color, bg_color, bg_alpha);
-        let rect = CGRect {
-            origin: CGPoint { x: 0.0, y: 0.0 },
-            size: CGSize {
-                width: metrics.rasterized_width as f64,
-                height: metrics.rasterized_height as f64,
-            },
-        };
-        cg_context.fill_rect(rect);
+            // Set the text color and draw the glyphs.
+            cg_context.set_rgb_fill_color(text_color, text_color, text_color, 1.0);
+            cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
+
+            // CG Origin is bottom left, WR is top left. Need -y offset
+            let mut draw_origin = CGPoint {
+                x: -metrics.rasterized_left as f64 + x_offset,
+                y: metrics.rasterized_descent as f64 - y_offset,
+            };
+
+            if let Some(transform) = transform {
+                cg_context.set_text_matrix(&transform);
 
-        // Set the text color and draw the glyphs.
-        cg_context.set_rgb_fill_color(text_color, text_color, text_color, 1.0);
-        cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
+                draw_origin = draw_origin.apply_transform(&transform.invert());
+            } else {
+                // Make sure to reset this because some previous glyph rasterization might have
+                // changed it.
+                cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY);
+            }
 
-        // CG Origin is bottom left, WR is top left. Need -y offset
-        let mut draw_origin = CGPoint {
-            x: -metrics.rasterized_left as f64 + x_offset,
-            y: metrics.rasterized_descent as f64 - y_offset,
-        };
-
-        if let Some(transform) = transform {
-            cg_context.set_text_matrix(&transform);
-
-            draw_origin = draw_origin.apply_transform(&transform.invert());
+            if extra_strikes > 0 {
+                let strikes = 1 + extra_strikes;
+                let glyphs = vec![glyph; strikes];
+                let origins = (0..strikes).map(|i| {
+                    CGPoint {
+                        x: draw_origin.x + i as f64 * pixel_step,
+                        y: draw_origin.y,
+                    }
+                }).collect::<Vec<_>>();
+                ct_font.draw_glyphs(&glyphs, &origins, cg_context.clone());
+            } else {
+                ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
+            }
         }
 
-        if extra_strikes > 0 {
-            let strikes = 1 + extra_strikes;
-            let glyphs = vec![glyph; strikes];
-            let origins = (0..strikes)
-                .map(|i| CGPoint { x: draw_origin.x + i as f64 * pixel_step, y: draw_origin.y })
-                .collect::<Vec<_>>();
-            ct_font.draw_glyphs(&glyphs, &origins, cg_context.clone());
-        } else {
-            ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone());
-        }
+        let mut rasterized_pixels = self.graphics_context
+                                        .get_rasterized_pixels(&raster_size, glyph_type);
 
-        let mut rasterized_pixels = cg_context.data().to_vec();
-
-        if !bitmap {
+        if glyph_type == GlyphType::Vector {
             // We rendered text into an opaque surface. The code below needs to
             // ignore the current value of each pixel's alpha channel. But it's
             // allowed to write to the alpha channel, because we're done calling
             // CG functions now.
 
             if smooth {
                 // Convert to linear space for subpixel AA.
                 // We explicitly do not do this for grayscale AA ("Alpha without
@@ -717,21 +719,133 @@ impl FontContext {
             }
         }
 
         GlyphRasterResult::Bitmap(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
-            scale: (if bitmap { scale / y_scale } else { scale }) as f32,
-            format: if bitmap { GlyphFormat::ColorBitmap } else { font.get_glyph_format() },
+            scale: match glyph_type {
+                GlyphType::Bitmap => (scale / y_scale) as f32,
+                GlyphType::Vector => scale as f32,
+            },
+            format: match glyph_type {
+                GlyphType::Bitmap => GlyphFormat::ColorBitmap,
+                GlyphType::Vector => font.get_glyph_format(),
+            },
             bytes: rasterized_pixels,
         })
     }
 }
 
 #[cfg(feature = "pathfinder")]
 impl<'a> Into<CGFont> for NativeFontHandleWrapper<'a> {
     fn into(self) -> CGFont {
         (self.0).0.clone()
     }
 }
+
+// Avoids taking locks by recycling Core Graphics contexts.
+#[allow(dead_code)]
+struct GraphicsContext {
+    vector_context: CGContext,
+    vector_context_size: Size2D<u32>,
+    bitmap_context: CGContext,
+    bitmap_context_size: Size2D<u32>,
+}
+
+impl GraphicsContext {
+    fn new() -> GraphicsContext {
+        let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH);
+        GraphicsContext {
+            vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector),
+            vector_context_size: size,
+            bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap),
+            bitmap_context_size: size,
+        }
+    }
+
+    #[allow(dead_code)]
+    fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
+                   -> &mut CGContext {
+        let (cached_context, cached_size) = match glyph_type {
+            GlyphType::Vector => {
+                (&mut self.vector_context, &mut self.vector_context_size)
+            }
+            GlyphType::Bitmap => {
+                (&mut self.bitmap_context, &mut self.bitmap_context_size)
+            }
+        };
+        let rounded_size = Size2D::new(size.width.next_power_of_two(),
+                                       size.height.next_power_of_two());
+        if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height {
+            *cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width),
+                                       u32::max(cached_size.height, rounded_size.height));
+            *cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type);
+        }
+        cached_context
+    }
+
+    #[allow(dead_code)]
+    fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType)
+                             -> Vec<u8> {
+        let (cached_context, cached_size) = match glyph_type {
+            GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size),
+            GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size),
+        };
+        let cached_data = cached_context.data();
+        let cached_stride = cached_size.width as usize * 4;
+
+        let result_len = size.width as usize * size.height as usize * 4;
+        let mut result = Vec::with_capacity(result_len);
+        for y in (cached_size.height - size.height)..cached_size.height {
+            let cached_start = y as usize * cached_stride;
+            let cached_end = cached_start + size.width as usize * 4;
+            result.extend_from_slice(&cached_data[cached_start..cached_end]);
+        }
+        debug_assert_eq!(result.len(), result_len);
+        result
+    }
+
+    fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext {
+        // The result of rasterization, in all render modes, is going to be a
+        // BGRA surface with white text on transparency using premultiplied
+        // alpha. For subpixel text, the RGB values will be the mask value for
+        // the individual components. For bitmap glyphs, the RGB values will be
+        // the (premultiplied) color of the pixel. For Alpha and Mono, each
+        // pixel will have R==G==B==A at the end of this function.
+        // We access the color channels in little-endian order.
+        // The CGContext will create and own our pixel buffer.
+        // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto
+        // an opaque background. In order to hit the most efficient path in CG
+        // for this, we will tell CG that the CGContext is opaque, by passing
+        // an "[...]AlphaNone[...]" context flag. This creates a slight
+        // contradiction to the way we use the buffer after CG is done with it,
+        // because we will convert it into text-on-transparency. But that's ok;
+        // we still get four bytes per pixel and CG won't mess with the alpha
+        // channel after we've stopped calling CG functions. We just need to
+        // make sure that we don't look at the alpha values of the pixels that
+        // we get from CG, and compute our own alpha value only from RGB.
+        // Note that CG requires kCGBitmapByteOrder32Little in order to do
+        // subpixel AA at all (which we need it to do in both Subpixel and
+        // Alpha+smoothing mode). But little-endian is what we want anyway, so
+        // this works out nicely.
+        let color_type = match glyph_type {
+            GlyphType::Vector => kCGImageAlphaNoneSkipFirst,
+            GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst,
+        };
+
+        CGContext::create_bitmap_context(None,
+                                         size.width as usize,
+                                         size.height as usize,
+                                         8,
+                                         size.width as usize * 4,
+                                         &CGColorSpace::create_device_rgb(),
+                                         kCGBitmapByteOrder32Little | color_type)
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+enum GlyphType {
+    Vector,
+    Bitmap,
+}
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -1302,16 +1302,21 @@ impl LazyInitializedDebugRenderer {
                     self.failed = true;
                 }
             }
         }
 
         self.debug_renderer.as_mut()
     }
 
+    /// Returns mut ref to `DebugRenderer` if one already exists, otherwise returns `None`.
+    pub fn try_get_mut<'a>(&'a mut self) -> Option<&'a mut DebugRenderer> {
+        self.debug_renderer.as_mut()
+    }
+
     pub fn deinit(self, device: &mut Device) {
         if let Some(debug_renderer) = self.debug_renderer {
             debug_renderer.deinit(device);
         }
     }
 }
 
 pub struct RendererVAOs {
@@ -2461,17 +2466,17 @@ impl Renderer {
         self.profile_counters.reset();
         self.profile_counters.frame_counter.inc();
 
         profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("end frame");
             self.gpu_profile.end_frame();
             #[cfg(feature = "debug_renderer")]
             {
-                if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) {
+                if let Some(debug_renderer) = self.debug.try_get_mut() {
                     debug_renderer.render(&mut self.device, framebuffer_size);
                 }
             }
             self.device.end_frame();
         });
         if framebuffer_size.is_some() {
             self.last_time = current_time;
         }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1,14 +1,14 @@
 /* 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::{AddFont, BlobImageResources, AsyncBlobImageRasterizer, ResourceUpdate};
-use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest};
+use api::{BlobImageDescriptor, BlobImageHandler, BlobImageRequest, RasterizedBlobImage};
 use api::{ClearCache, ColorF, DevicePoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
 use api::{ExternalImageData, ExternalImageType, BlobImageResult, BlobImageParams};
 use api::{FontInstanceData, FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
 use api::{GlyphDimensions, IdNamespace};
 use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
 use api::{TileOffset, TileSize, TileRange, NormalizedRect, BlobImageData};
 use app_units::Au;
@@ -27,16 +27,17 @@ use glyph_rasterizer::{FontInstance, Gly
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::UvRectKind;
 use image::{compute_tile_range, for_each_tile_in_range};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
 use render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
+use smallvec::SmallVec;
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::collections::hash_map::ValuesMut;
 use std::{cmp, mem};
 use std::fmt::Debug;
 use std::hash::Hash;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
@@ -93,18 +94,19 @@ pub struct ImageProperties {
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
 }
 
 /// Post scene building state.
-struct RasterizedBlobImage {
-    data: FastHashMap<Option<TileOffset>, BlobImageResult>,
+enum RasterizedBlob {
+    Tiled(FastHashMap<TileOffset, RasterizedBlobImage>),
+    NonTiled(Vec<RasterizedBlobImage>),
 }
 
 /// Pre scene building state.
 /// We use this to generate the async blob rendering requests.
 struct BlobImageTemplate {
     descriptor: ImageDescriptor,
     tiling: Option<TileSize>,
     dirty_rect: Option<DeviceUintRect>,
@@ -393,17 +395,17 @@ pub struct ResourceCache {
     glyph_rasterizer: GlyphRasterizer,
 
     // The set of images that aren't present or valid in the texture cache,
     // and need to be rasterized and/or uploaded this frame. This includes
     // both blobs and regular images.
     pending_image_requests: FastHashSet<ImageRequest>,
 
     blob_image_handler: Option<Box<BlobImageHandler>>,
-    rasterized_blob_images: FastHashMap<ImageKey, RasterizedBlobImage>,
+    rasterized_blob_images: FastHashMap<ImageKey, RasterizedBlob>,
     blob_image_templates: FastHashMap<ImageKey, BlobImageTemplate>,
 
     // If while building a frame we encounter blobs that we didn't already
     // rasterize, add them to this list and rasterize them synchronously.
     missing_blob_images: Vec<BlobImageParams>,
     // The rasterizer associated with the current scene.
     blob_image_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
 }
@@ -592,20 +594,50 @@ impl ResourceCache {
     }
 
     pub fn set_blob_rasterizer(&mut self, rasterizer: Box<AsyncBlobImageRasterizer>) {
         self.blob_image_rasterizer = Some(rasterizer);
     }
 
     pub fn add_rasterized_blob_images(&mut self, images: Vec<(BlobImageRequest, BlobImageResult)>) {
         for (request, result) in images {
+            let data = match result {
+                Ok(data) => data,
+                Err(..) => {
+                    warn!("Failed to rasterize a blob image");
+                    continue;
+                }
+            };
+
+            // First make sure we have an entry for this key (using a placeholder
+            // if need be).
             let image = self.rasterized_blob_images.entry(request.key).or_insert_with(
-                || { RasterizedBlobImage { data: FastHashMap::default() } }
+                || { RasterizedBlob::Tiled(FastHashMap::default()) }
             );
-            image.data.insert(request.tile, result);
+
+            if let Some(tile) = request.tile {
+                if let RasterizedBlob::NonTiled(..) = *image {
+                    *image = RasterizedBlob::Tiled(FastHashMap::default());
+                }
+
+                if let RasterizedBlob::Tiled(ref mut tiles) = *image {
+                    tiles.insert(tile, data);
+                }
+            } else {
+                if let RasterizedBlob::NonTiled(ref mut queue) = *image {
+                    // If our new rasterized rect overwrites items in the queue, discard them.
+                    queue.retain(|img| {
+                        !data.rasterized_rect.contains_rect(&img.rasterized_rect)
+                    });
+
+                    queue.push(data);
+                } else {
+                    *image = RasterizedBlob::NonTiled(vec![data]);
+                }
+            }
         }
     }
 
     pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
         // Push the new font to the font renderer, and also store
         // it locally for glyph metric requests.
         self.glyph_rasterizer.add_font(font_key, template.clone());
         self.resources.font_templates.insert(font_key, template);
@@ -920,19 +952,20 @@ impl ResourceCache {
         }
 
         if !self.pending_image_requests.insert(request) {
             return
         }
 
         if template.data.is_blob() {
             let request: BlobImageRequest = request.into();
-            let missing = match self.rasterized_blob_images.get(&request.key) {
-                Some(img) => !img.data.contains_key(&request.tile),
-                None => true,
+            let missing = match (self.rasterized_blob_images.get(&request.key), request.tile) {
+                (Some(RasterizedBlob::Tiled(tiles)), Some(tile)) => !tiles.contains_key(&tile),
+                (Some(RasterizedBlob::NonTiled(..)), None) => false,
+                _ => true,
             };
 
             // For some reason the blob image is missing. We'll fall back to
             // rasterizing it on the render backend thread.
             if missing {
                 let descriptor = match template.tiling {
                     Some(tile_size) => {
                         let tile = request.tile.unwrap();
@@ -1057,23 +1090,42 @@ impl ResourceCache {
                                 tile: Some(tile),
                             },
                             descriptor,
                             dirty_rect: None,
                         }
                     );
                 });
             } else {
-                let needs_upload = match self.cached_images.try_get(&key) {
+                let mut needs_upload = match self.cached_images.try_get(&key) {
                     Some(&ImageResult::UntiledAuto(ref entry)) => {
                         self.texture_cache.needs_upload(&entry.texture_cache_handle)
                     }
                     _ => true,
                 };
 
+                // If the queue of ratserized updates is growing it probably means that
+                // the texture is not getting uploaded because the display item is off-screen.
+                // In that case we are better off
+                // - Either not kicking rasterization for that image (avoid wasted cpu work
+                //   but will jank next time the item is visible because of lazy rasterization.
+                // - Clobber the update queue by pushing an update with a larger dirty rect
+                //   to prevent it from accumulating.
+                //
+                // We do the latter here but it's not ideal and might want to revisit and do
+                // the former instead.
+                match self.rasterized_blob_images.get(&key) {
+                    Some(RasterizedBlob::NonTiled(ref queue)) => {
+                        if queue.len() > 2 {
+                            needs_upload = true;
+                        }
+                    }
+                    _ => {},
+                };
+
                 let dirty_rect = if needs_upload {
                     // The texture cache entry has been evicted, treat it as all dirty.
                     None
                 } else {
                     template.dirty_rect
                 };
 
                 blob_request_params.push(
@@ -1109,38 +1161,29 @@ impl ResourceCache {
                 //println!("Missing image template (key={:?})!", key);
                 return;
             }
         };
         let tile_size = match template.tiling {
             Some(size) => size,
             None => { return; }
         };
-        let image = match self.rasterized_blob_images.get_mut(&key) {
-            Some(image) => image,
-            None => {
-                //println!("Missing rasterized blob (key={:?})!", key);
-                return;
-            }
+
+        let tiles = match self.rasterized_blob_images.get_mut(&key) {
+            Some(RasterizedBlob::Tiled(tiles)) => tiles,
+            _ => { return; }
         };
+
         let tile_range = compute_tile_range(
             &area,
             &template.descriptor.size,
             tile_size,
         );
-        image.data.retain(|tile, _| {
-            match *tile {
-                Some(offset) => tile_range.contains(&offset),
-                // This would be a bug. If we get here the blob should be tiled.
-                None => {
-                    error!("Blob image template and image data tiling don't match.");
-                    false
-                }
-            }
-        });
+
+        tiles.retain(|tile, _| { tile_range.contains(tile) });
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
         glyph_keys: &[GlyphKey],
         gpu_cache: &mut GpuCache,
         render_task_tree: &mut RenderTaskTree,
@@ -1395,139 +1438,147 @@ impl ResourceCache {
         self.missing_blob_images.clear();
     }
 
     fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
         for request in self.pending_image_requests.drain() {
             let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
             debug_assert!(image_template.data.uses_texture_cache());
 
-            let mut blob_rasterized_rect = None;
-            let image_data = match image_template.data {
+            let mut updates: SmallVec<[(ImageData, Option<DeviceUintRect>); 1]> = SmallVec::new();
+
+            match image_template.data {
                 ImageData::Raw(..) | ImageData::External(..) => {
                     // Safe to clone here since the Raw image data is an
                     // Arc, and the external image data is small.
-                    image_template.data.clone()
+                    updates.push((image_template.data.clone(), None));
                 }
                 ImageData::Blob(..) => {
-                    let blob_image = self.rasterized_blob_images.get(&request.key).unwrap();
-                    match blob_image.data.get(&request.tile) {
-                        Some(result) => {
-                            let result = result
-                                .as_ref()
-                                .expect("Failed to render a blob image");
 
-                            // TODO: we may want to not panic and show a placeholder instead.
-
-                            blob_rasterized_rect = Some(result.rasterized_rect);
-
-                            ImageData::Raw(Arc::clone(&result.data))
+                    let blob_image = self.rasterized_blob_images.get_mut(&request.key).unwrap();
+                    match (blob_image, request.tile) {
+                        (RasterizedBlob::Tiled(ref tiles), Some(tile)) => {
+                            let img = &tiles[&tile];
+                            updates.push((
+                                ImageData::Raw(Arc::clone(&img.data)),
+                                Some(img.rasterized_rect)
+                            ));
                         }
-                        None => {
+                        (RasterizedBlob::NonTiled(ref mut queue), None) => {
+                            for img in queue.drain(..) {
+                                updates.push((
+                                    ImageData::Raw(img.data),
+                                    Some(img.rasterized_rect)
+                                ));
+                            }
+                        }
+                        _ =>  {
                             debug_assert!(false, "invalid blob image request during frame building");
                             continue;
                         }
                     }
                 }
             };
 
-            let entry = match *self.cached_images.get_mut(&request.key) {
-                ImageResult::UntiledAuto(ref mut entry) => entry,
-                ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
-                ImageResult::Err(_) => panic!("Update requested for invalid entry")
-            };
-
-            match (blob_rasterized_rect, entry.dirty_rect) {
-                (Some(rasterized), Some(dirty)) => {
-                    debug_assert!(request.tile.is_some() || rasterized.contains_rect(&dirty));
-                }
-                _ => {}
-            }
-
-            let mut descriptor = image_template.descriptor.clone();
-            let local_dirty_rect;
-
-            if let Some(tile) = request.tile {
-                let tile_size = image_template.tiling.unwrap();
-                let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
-
-                local_dirty_rect = if let Some(rect) = entry.dirty_rect.take() {
-                    // We should either have a dirty rect, or we are re-uploading where the dirty
-                    // rect is ignored anyway.
-                    let intersection = intersect_for_tile(rect, clipped_tile_size, tile_size, tile);
-                    debug_assert!(intersection.is_some() ||
-                                  self.texture_cache.needs_upload(&entry.texture_cache_handle));
-                    intersection
-                } else {
-                    None
+            for (image_data, blob_rasterized_rect) in updates {
+                let entry = match *self.cached_images.get_mut(&request.key) {
+                    ImageResult::UntiledAuto(ref mut entry) => entry,
+                    ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
+                    ImageResult::Err(_) => panic!("Update requested for invalid entry")
                 };
 
-                // The tiled image could be stored on the CPU as one large image or be
-                // already broken up into tiles. This affects the way we compute the stride
-                // and offset.
-                let tiled_on_cpu = image_template.data.is_blob();
-                if !tiled_on_cpu {
-                    let bpp = descriptor.format.bytes_per_pixel();
-                    let stride = descriptor.compute_stride();
-                    descriptor.stride = Some(stride);
-                    descriptor.offset +=
-                        tile.y as u32 * tile_size as u32 * stride +
-                        tile.x as u32 * tile_size as u32 * bpp;
+                let mut descriptor = image_template.descriptor.clone();
+                let mut local_dirty_rect;
+
+                if let Some(tile) = request.tile {
+                    let tile_size = image_template.tiling.unwrap();
+                    let clipped_tile_size = compute_tile_size(&descriptor, tile_size, tile);
+
+                    local_dirty_rect = if let Some(rect) = entry.dirty_rect.take() {
+                        // We should either have a dirty rect, or we are re-uploading where the dirty
+                        // rect is ignored anyway.
+                        let intersection = intersect_for_tile(rect, clipped_tile_size, tile_size, tile);
+                        debug_assert!(intersection.is_some() ||
+                                      self.texture_cache.needs_upload(&entry.texture_cache_handle));
+                        intersection
+                    } else {
+                        None
+                    };
+
+                    // The tiled image could be stored on the CPU as one large image or be
+                    // already broken up into tiles. This affects the way we compute the stride
+                    // and offset.
+                    let tiled_on_cpu = image_template.data.is_blob();
+                    if !tiled_on_cpu {
+                        let bpp = descriptor.format.bytes_per_pixel();
+                        let stride = descriptor.compute_stride();
+                        descriptor.stride = Some(stride);
+                        descriptor.offset +=
+                            tile.y as u32 * tile_size as u32 * stride +
+                            tile.x as u32 * tile_size as u32 * bpp;
+                    }
+
+                    descriptor.size = clipped_tile_size;
+                } else {
+                    local_dirty_rect = entry.dirty_rect.take();
                 }
 
-                descriptor.size = clipped_tile_size;
-            } else {
-                local_dirty_rect = entry.dirty_rect.take();
-            }
-
-            let filter = match request.rendering {
-                ImageRendering::Pixelated => {
-                    TextureFilter::Nearest
+                // If we are uploading the dirty region of a blob image we might have several
+                // rects to upload so we use each of these rasterized rects rather than the
+                // overall dirty rect of the image.
+                if blob_rasterized_rect.is_some() {
+                    local_dirty_rect = blob_rasterized_rect;
                 }
-                ImageRendering::Auto | ImageRendering::CrispEdges => {
-                    // If the texture uses linear filtering, enable mipmaps and
-                    // trilinear filtering, for better image quality. We only
-                    // support this for now on textures that are not placed
-                    // into the shared cache. This accounts for any image
-                    // that is > 512 in either dimension, so it should cover
-                    // the most important use cases. We may want to support
-                    // mip-maps on shared cache items in the future.
-                    if descriptor.allow_mipmaps &&
-                       descriptor.size.width > 512 &&
-                       descriptor.size.height > 512 &&
-                       !self.texture_cache.is_allowed_in_shared_cache(
-                        TextureFilter::Linear,
-                        &descriptor,
-                    ) {
-                        TextureFilter::Trilinear
-                    } else {
-                        TextureFilter::Linear
+
+                let filter = match request.rendering {
+                    ImageRendering::Pixelated => {
+                        TextureFilter::Nearest
                     }
-                }
-            };
-
-            let eviction = if image_template.data.is_blob() {
-                Eviction::Manual
-            } else {
-                Eviction::Auto
-            };
+                    ImageRendering::Auto | ImageRendering::CrispEdges => {
+                        // If the texture uses linear filtering, enable mipmaps and
+                        // trilinear filtering, for better image quality. We only
+                        // support this for now on textures that are not placed
+                        // into the shared cache. This accounts for any image
+                        // that is > 512 in either dimension, so it should cover
+                        // the most important use cases. We may want to support
+                        // mip-maps on shared cache items in the future.
+                        if descriptor.allow_mipmaps &&
+                           descriptor.size.width > 512 &&
+                           descriptor.size.height > 512 &&
+                           !self.texture_cache.is_allowed_in_shared_cache(
+                            TextureFilter::Linear,
+                            &descriptor,
+                        ) {
+                            TextureFilter::Trilinear
+                        } else {
+                            TextureFilter::Linear
+                        }
+                    }
+                };
 
-            //Note: at this point, the dirty rectangle is local to the descriptor space
-            self.texture_cache.update(
-                &mut entry.texture_cache_handle,
-                descriptor,
-                filter,
-                Some(image_data),
-                [0.0; 3],
-                local_dirty_rect,
-                gpu_cache,
-                None,
-                UvRectKind::Rect,
-                eviction,
-            );
+                let eviction = if image_template.data.is_blob() {
+                    Eviction::Manual
+                } else {
+                    Eviction::Auto
+                };
+
+                //Note: at this point, the dirty rectangle is local to the descriptor space
+                self.texture_cache.update(
+                    &mut entry.texture_cache_handle,
+                    descriptor,
+                    filter,
+                    Some(image_data),
+                    [0.0; 3],
+                    local_dirty_rect,
+                    gpu_cache,
+                    None,
+                    UvRectKind::Rect,
+                    eviction,
+                );
+            }
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
 
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -53,32 +53,32 @@ impl SceneProperties {
     /// multiple set_properties and add_properties calls during a
     /// single transaction, and still correctly determine if any
     /// properties have changed. This can have significant power
     /// saving implications, allowing a frame build to be skipped
     /// if the properties haven't changed in many cases.
     pub fn flush_pending_updates(&mut self) -> bool {
         let mut properties_changed = false;
 
-        if let Some(pending_properties) = self.pending_properties.take() {
-            if pending_properties != self.current_properties {
+        if let Some(ref pending_properties) = self.pending_properties {
+            if *pending_properties != self.current_properties {
                 self.transform_properties.clear();
                 self.float_properties.clear();
 
                 for property in &pending_properties.transforms {
                     self.transform_properties
                         .insert(property.key.id, property.value);
                 }
 
                 for property in &pending_properties.floats {
                     self.float_properties
                         .insert(property.key.id, property.value);
                 }
 
-                self.current_properties = pending_properties;
+                self.current_properties = pending_properties.clone();
                 properties_changed = true;
             }
         }
 
         properties_changed
     }
 
     /// Get the current value for a transform property.
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -21,12 +21,12 @@ ipc-channel = {version = "0.11.0", optio
 euclid = { version = "0.19", features = ["serde"] }
 serde = { version = "=1.0.66", features = ["rc"] }
 serde_derive = { version = "=1.0.66", features = ["deserialize_in_place"] }
 serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.6"
-core-graphics = "0.16"
+core-graphics = "0.17.1"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -22,11 +22,11 @@ version = "0.57.2"
 default-features = false
 features = ["capture", "serialize_program"]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.6"
-core-graphics = "0.16"
+core-graphics = "0.17.1"
 foreign-types = "0.3.0"
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-f17f6a491d6ff3dc3e13e998dda788d8f5856338
+a601f9c291cee83257241ef61aaf62353c613438
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -26,17 +26,17 @@ crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
 osmesa-src = { git = "https://github.com/jrmuizel/osmesa-src", optional = true, branch = "serialize" }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
 webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
 winit = "0.16"
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
-core-graphics = "0.16"
+core-graphics = "0.17.1"
 core-foundation = "0.6"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
 pathfinder = [ "webrender/pathfinder" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"