Bug 1446358 - Update webrender to commit 2083e83d958dd4a230ccae5c518e4bc8fbf88009. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 20 Mar 2018 09:01:12 -0400
changeset 769914 cd5cac5d50594bfb6a22eb5044dfbcfd5b35dc8b
parent 769888 827c686c570935483c3ad8022aa92b9e005574c3
push id103260
push userkgupta@mozilla.com
push dateTue, 20 Mar 2018 13:01:41 +0000
reviewersjrmuizel
bugs1446358
milestone61.0a1
Bug 1446358 - Update webrender to commit 2083e83d958dd4a230ccae5c518e4bc8fbf88009. r?jrmuizel MozReview-Commit-ID: 36QLv2CRPh0
gfx/webrender/Cargo.toml
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/angle.rs
gfx/wrench/src/args.yaml
gfx/wrench/src/egl.rs
gfx/wrench/src/main.rs
gfx/wrench/src/perf.rs
gfx/wrench/src/png.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/reftest.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -30,18 +30,18 @@ webrender_api = {path = "../webrender_ap
 bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.8"
 png = { optional = true, version = "0.11" }
 smallvec = "0.6"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
-image = { optional = true, version = "0.17" }
-base64 = { optional = true, version = "0.3.0" }
+image = { optional = true, version = "0.18" }
+base64 = { optional = true, version = "0.6" }
 ron = { optional = true, version = "0.1.7" }
 
 [dev-dependencies]
 mozangle = "0.1"
 env_logger = "0.5"
 rand = "0.3"                # for the benchmarks
 glutin = "0.12"             # for the example apps
 
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -4,32 +4,31 @@
 
 #define VECS_PER_SPECIFIC_BRUSH 2
 
 #include shared,prim_shared,brush
 
 flat varying int vGradientAddress;
 flat varying float vGradientRepeat;
 
-flat varying vec2 vStartCenter;
-flat varying vec2 vEndCenter;
+flat varying vec2 vCenter;
 flat varying float vStartRadius;
 flat varying float vEndRadius;
 
 varying vec2 vPos;
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 #ifdef WR_VERTEX_SHADER
 
 struct RadialGradient {
-    vec4 start_end_center;
-    vec4 start_end_radius_ratio_xy_extend_mode;
+    vec4 center_start_end_radius;
+    vec4 ratio_xy_extend_mode;
 };
 
 RadialGradient fetch_radial_gradient(int address) {
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return RadialGradient(data[0], data[1]);
 }
 
 void brush_vs(
@@ -38,50 +37,46 @@ void brush_vs(
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
-    vStartCenter = gradient.start_end_center.xy;
-    vEndCenter = gradient.start_end_center.zw;
-
-    vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x;
-    vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y;
+    vCenter = gradient.center_start_end_radius.xy;
+    vStartRadius = gradient.center_start_end_radius.z;
+    vEndRadius = gradient.center_start_end_radius.w;
 
     // Transform all coordinates by the y scale so the
     // fragment shader can work with circles
-    float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z;
+    float ratio_xy = gradient.ratio_xy_extend_mode.x;
     vPos.y *= ratio_xy;
-    vStartCenter.y *= ratio_xy;
-    vEndCenter.y *= ratio_xy;
+    vCenter.y *= ratio_xy;
 
     vGradientAddress = user_data.x;
 
     // Whether to repeat the gradient instead of clamping.
-    vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) != EXTEND_MODE_CLAMP);
+    vGradientRepeat = float(int(gradient.ratio_xy_extend_mode.y) != EXTEND_MODE_CLAMP);
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
-    vec2 cd = vEndCenter - vStartCenter;
-    vec2 pd = vPos - vStartCenter;
+    vec2 pd = vPos - vCenter;
     float rd = vEndRadius - vStartRadius;
 
-    // Solve for t in length(t * cd - pd) = vStartRadius + t * rd
+    // Solve for t in length(t - pd) = vStartRadius + t * rd
     // using a quadratic equation in form of At^2 - 2Bt + C = 0
-    float A = dot(cd, cd) - rd * rd;
-    float B = dot(pd, cd) + vStartRadius * rd;
+    float A = -(rd * rd);
+    float B = vStartRadius * rd;
     float C = dot(pd, pd) - vStartRadius * vStartRadius;
 
     float offset;
     if (A == 0.0) {
         // Since A is 0, just solve for -2Bt + C = 0
         if (B == 0.0) {
             discard;
         }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -926,32 +926,16 @@ impl Device {
 
         if self.bound_program != program.id {
             self.gl.use_program(program.id);
             self.bound_program = program.id;
             self.program_mode_id = UniformLocation(program.u_mode);
         }
     }
 
-    //TODO: remove once the Angle workaround is no longer needed
-    pub fn reset_angle_sampler_metadata(&mut self, texture: &Texture) {
-        self.bind_texture(DEFAULT_TEXTURE, texture);
-        self.gl.tex_parameter_f(
-            texture.target,
-            gl::TEXTURE_BASE_LEVEL,
-            1.0 as _,
-        );
-        self.gl.draw_arrays(gl::TRIANGLES, 0, 1); // dummy draw
-        self.gl.tex_parameter_f(
-            texture.target,
-            gl::TEXTURE_BASE_LEVEL,
-            0.0 as _, // assumes 0.0 is the normal value for this texture
-        );
-    }
-
     pub fn create_texture(
         &mut self,
         target: TextureTarget,
         format: ImageFormat,
     ) -> Texture {
         Texture {
             id: self.gl.gen_textures(1)[0],
             target: get_gl_target(target),
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -733,21 +733,20 @@ impl<'a> DisplayListFlattener<'a> {
                     info.tile_size,
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 self.add_radial_gradient(
                     clip_and_scroll,
                     &prim_info,
-                    info.gradient.start_center,
+                    info.gradient.center,
                     info.gradient.start_radius,
-                    info.gradient.end_center,
                     info.gradient.end_radius,
-                    info.gradient.ratio_xy,
+                    info.gradient.radius.width / info.gradient.radius.height,
                     item.gradient_stops(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                 let bounds = box_shadow_info
@@ -1819,21 +1818,20 @@ impl<'a> DisplayListFlattener<'a> {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
                     let mut info = info.clone();
                     info.rect = segment;
 
                     self.add_radial_gradient(
                         clip_and_scroll,
                         &info,
-                        border.gradient.start_center - segment_rel,
+                        border.gradient.center - segment_rel,
                         border.gradient.start_radius,
-                        border.gradient.end_center - segment_rel,
                         border.gradient.end_radius,
-                        border.gradient.ratio_xy,
+                        border.gradient.radius.width / border.gradient.radius.height,
                         gradient_stops,
                         border.gradient.extend_mode,
                         segment.size,
                         LayerSize::zero(),
                     );
                 }
             }
         }
@@ -1933,31 +1931,29 @@ impl<'a> DisplayListFlattener<'a> {
             }
         }
     }
 
     fn add_radial_gradient_impl(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
-        start_center: LayerPoint,
+        center: LayerPoint,
         start_radius: f32,
-        end_center: LayerPoint,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         gradient_index: CachedGradientIndex,
     ) {
         let prim = BrushPrimitive::new(
             BrushKind::RadialGradient {
                 stops_range: stops,
                 extend_mode,
-                start_center,
-                end_center,
+                center,
                 start_radius,
                 end_radius,
                 ratio_xy,
                 gradient_index,
             },
             None,
         );
 
@@ -1968,19 +1964,18 @@ impl<'a> DisplayListFlattener<'a> {
             PrimitiveContainer::Brush(prim),
         );
     }
 
     pub fn add_radial_gradient(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayerPrimitiveInfo,
-        start_center: LayerPoint,
+        center: LayerPoint,
         start_radius: f32,
-        end_center: LayerPoint,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         tile_size: LayerSize,
         tile_spacing: LayerSize,
     ) {
         let gradient_index = CachedGradientIndex(self.cached_gradients.len());
@@ -1991,33 +1986,31 @@ impl<'a> DisplayListFlattener<'a> {
             tile_spacing,
             64 * 64,
         );
 
         if prim_infos.is_empty() {
             self.add_radial_gradient_impl(
                 clip_and_scroll,
                 info,
-                start_center,
+                center,
                 start_radius,
-                end_center,
                 end_radius,
                 ratio_xy,
                 stops,
                 extend_mode,
                 gradient_index,
             );
         } else {
             for prim_info in prim_infos {
                 self.add_radial_gradient_impl(
                     clip_and_scroll,
                     &prim_info,
-                    start_center,
+                    center,
                     start_radius,
-                    end_center,
                     end_radius,
                     ratio_xy,
                     stops,
                     extend_mode,
                     gradient_index,
                 );
             }
         }
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,57 +1,62 @@
 /* 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::{DevicePoint, DeviceUintSize, GlyphKey};
+use api::GlyphKey;
 use glyph_rasterizer::{FontInstance, GlyphFormat};
 use internal_types::FastHashMap;
 use resource_cache::ResourceClassCache;
-use std::sync::Arc;
-use texture_cache::TextureCacheHandle;
+use texture_cache::{TextureCache, TextureCacheHandle, EvictionNotice};
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct CachedGlyphInfo {
+    pub texture_cache_handle: TextureCacheHandle,
+    pub format: GlyphFormat,
+}
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct GenericCachedGlyphInfo<D> {
-    pub texture_cache_handle: TextureCacheHandle,
-    pub glyph_bytes: D,
-    pub size: DeviceUintSize,
-    pub offset: DevicePoint,
-    pub scale: f32,
-    pub format: GlyphFormat,
+pub enum GlyphCacheEntry {
+    // A glyph that has been successfully rasterized.
+    Cached(CachedGlyphInfo),
+    // A glyph that should not be rasterized (i.e. a space).
+    Blank,
+    // A glyph that has been submitted to the font backend for rasterization,
+    // but is still pending a result.
+    Pending,
 }
 
-pub type CachedGlyphInfo = GenericCachedGlyphInfo<Arc<Vec<u8>>>;
-pub type GlyphKeyCache = ResourceClassCache<GlyphKey, Option<CachedGlyphInfo>>;
+pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, EvictionNotice>;
 
-#[cfg(any(feature = "capture", feature = "replay"))]
-pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo<String>;
-#[cfg(any(feature = "capture", feature = "replay"))]
-pub type PlainGlyphKeyCache = ResourceClassCache<GlyphKey, Option<PlainCachedGlyphInfo>>;
-#[cfg(feature = "capture")]
-pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>;
-#[cfg(feature = "replay")]
-pub type PlainGlyphCacheOwn = FastHashMap<FontInstance, PlainGlyphKeyCache>;
+impl GlyphKeyCache {
+    pub fn eviction_notice(&self) -> &EvictionNotice {
+        &self.user_data
+    }
+}
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphCache {
-    pub glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
+    glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
 }
 
 impl GlyphCache {
     pub fn new() -> Self {
         GlyphCache {
             glyph_key_caches: FastHashMap::default(),
         }
     }
 
     pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
         self.glyph_key_caches
             .entry(font)
-            .or_insert(ResourceClassCache::new())
+            .or_insert_with(|| GlyphKeyCache::new())
     }
 
     pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache {
         self.glyph_key_caches
             .get(font)
             .expect("BUG: Unable to find glyph key cache!")
     }
 
@@ -73,9 +78,41 @@ impl GlyphCache {
             .filter(&key_fun)
             .cloned()
             .collect::<Vec<_>>();
         for key in caches_to_destroy {
             let mut cache = self.glyph_key_caches.remove(&key).unwrap();
             cache.clear();
         }
     }
+
+    // Clear out evicted entries from glyph key caches and, if possible,
+    // also remove entirely any subsequently empty glyph key caches.
+    fn clear_evicted(&mut self, texture_cache: &TextureCache) {
+        self.glyph_key_caches.retain(|_, cache| {
+            // Scan for any glyph key caches that have evictions.
+            if cache.eviction_notice().check() {
+                // If there are evictions, filter out any glyphs evicted from the
+                // texture cache from the glyph key cache.
+                let mut keep_cache = false;
+                cache.retain(|_, entry| {
+                    let keep_glyph = match *entry {
+                        GlyphCacheEntry::Cached(ref glyph) =>
+                            texture_cache.is_allocated(&glyph.texture_cache_handle),
+                        GlyphCacheEntry::Pending => true,
+                        // If the cache only has blank glyphs left, just get rid of it.
+                        GlyphCacheEntry::Blank => false,
+                    };
+                    keep_cache |= keep_glyph;
+                    keep_glyph
+                });
+                // Only keep the glyph key cache if it still has valid glyphs.
+                keep_cache
+            } else {
+                true
+            }
+        });
+    }
+
+    pub fn begin_frame(&mut self, texture_cache: &TextureCache) {
+        self.clear_evicted(texture_cache);
+    }
 }
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(test)]
 use api::{IdNamespace, LayoutPoint};
-use api::{ColorF, ColorU, DevicePoint, DeviceUintSize};
+use api::{ColorF, ColorU};
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
 use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
 use api::{GlyphDimensions, GlyphKey, SubpixelDirection};
 use api::{ImageData, ImageDescriptor, ImageFormat, LayerToWorldTransform};
 use app_units::Au;
 use device::TextureFilter;
-use glyph_cache::{CachedGlyphInfo, GlyphCache};
+use glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
 use gpu_cache::GpuCache;
-use internal_types::{FastHashSet, ResourceCacheError};
+use internal_types::ResourceCacheError;
 use platform::font::FontContext;
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
 use rayon::prelude::*;
 use std::cmp;
 use std::collections::hash_map::Entry;
 use std::hash::{Hash, Hasher};
 use std::mem;
@@ -304,21 +304,21 @@ pub struct GlyphRasterizer {
     font_contexts: Arc<FontContexts>,
 
     // Maintain a set of glyphs that have been requested this
     // frame. This ensures the glyph thread won't rasterize
     // the same glyph more than once in a frame. This is required
     // because the glyph cache hash table is not updated
     // until the end of the frame when we wait for glyph requests
     // to be resolved.
-    pending_glyphs: FastHashSet<GlyphRequest>,
+    pending_glyphs: usize,
 
     // Receives the rendered glyphs.
-    glyph_rx: Receiver<Vec<GlyphRasterJob>>,
-    glyph_tx: Sender<Vec<GlyphRasterJob>>,
+    glyph_rx: Receiver<GlyphRasterJobs>,
+    glyph_tx: Sender<GlyphRasterJobs>,
 
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
 }
 
@@ -336,17 +336,17 @@ impl GlyphRasterizer {
         }
 
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(FontContexts {
                 worker_contexts: contexts,
                 shared_context: Mutex::new(shared_context),
                 workers: Arc::clone(&workers),
             }),
-            pending_glyphs: FastHashSet::default(),
+            pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
@@ -386,92 +386,85 @@ impl GlyphRasterizer {
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
     ) {
         assert!(
             self.font_contexts
                 .lock_shared_context()
                 .has_font(&font.font_key)
         );
-        let mut glyphs = Vec::new();
+        let mut new_glyphs = Vec::new();
 
         let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
 
         // select glyphs that have not been requested yet.
         for key in glyph_keys {
             match glyph_key_cache.entry(key.clone()) {
                 Entry::Occupied(mut entry) => {
-                    if let Ok(Some(ref mut glyph_info)) = *entry.get_mut() {
-                        if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) {
-                            // This case gets hit when we have already rasterized
-                            // the glyph and stored it in CPU memory, but the glyph
-                            // has been evicted from the texture cache. In which case
-                            // we need to re-upload it to the GPU.
-                            texture_cache.update(
-                                &mut glyph_info.texture_cache_handle,
-                                ImageDescriptor {
-                                    width: glyph_info.size.width,
-                                    height: glyph_info.size.height,
-                                    stride: None,
-                                    format: ImageFormat::BGRA8,
-                                    is_opaque: false,
-                                    offset: 0,
-                                },
-                                TextureFilter::Linear,
-                                Some(ImageData::Raw(glyph_info.glyph_bytes.clone())),
-                                [glyph_info.offset.x, glyph_info.offset.y, glyph_info.scale],
-                                None,
-                                gpu_cache,
-                            );
+                    let value = entry.into_mut();
+                    match *value {
+                        GlyphCacheEntry::Cached(ref glyph) => {
+                            // Skip the glyph if it is already has a valid texture cache handle.
+                            if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) {
+                                continue;
+                            }
                         }
+                        // Otherwise, skip the entry if it is blank or pending.
+                        GlyphCacheEntry::Blank |
+                        GlyphCacheEntry::Pending => continue,
                     }
+                    // This case gets hit when we already rasterized the glyph, but the
+                    // glyph has been evicted from the texture cache. Just force it to
+                    // pending so it gets rematerialized.
+                    *value = GlyphCacheEntry::Pending;
                 }
-                Entry::Vacant(..) => {
-                    let request = GlyphRequest::new(&font, key);
-                    if self.pending_glyphs.insert(request.clone()) {
-                        glyphs.push(request);
-                    }
+                Entry::Vacant(entry) => {
+                    // This is the first time we've seen the glyph, so mark it as pending.
+                    entry.insert(GlyphCacheEntry::Pending);
                 }
             }
+
+            new_glyphs.push(key.clone());
         }
 
-        if glyphs.is_empty() {
+        if new_glyphs.is_empty() {
             return;
         }
 
+        self.pending_glyphs += 1;
         let font_contexts = Arc::clone(&self.font_contexts);
         let glyph_tx = self.glyph_tx.clone();
         // spawn an async task to get off of the render backend thread as early as
         // possible and in that task use rayon's fork join dispatch to rasterize the
         // glyphs in the thread pool.
         self.workers.spawn(move || {
-            let jobs = glyphs
+            let jobs = new_glyphs
                 .par_iter()
-                .map(|request: &GlyphRequest| {
+                .map(|key: &GlyphKey| {
                     profile_scope!("glyph-raster");
                     let mut context = font_contexts.lock_current_context();
                     let job = GlyphRasterJob {
-                        request: request.clone(),
-                        result: context.rasterize_glyph(&request.font, &request.key),
+                        key: key.clone(),
+                        result: context.rasterize_glyph(&font, key),
                     };
 
                     // Sanity check.
                     if let Some(ref glyph) = job.result {
                         let bpp = 4; // We always render glyphs in 32 bits RGBA format.
                         assert_eq!(
                             glyph.bytes.len(),
                             bpp * (glyph.width * glyph.height) as usize
                         );
                     }
 
                     job
                 })
                 .collect();
 
-            glyph_tx.send(jobs).unwrap();
+            glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap();
         });
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         glyph_key: &GlyphKey,
     ) -> Option<GlyphDimensions> {
@@ -488,82 +481,70 @@ impl GlyphRasterizer {
 
     pub fn resolve_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         _texture_cache_profile: &mut TextureCacheProfileCounters,
     ) {
-        let mut rasterized_glyphs = Vec::with_capacity(self.pending_glyphs.len());
+        // Pull rasterized glyphs from the queue and Update the caches.
+        while self.pending_glyphs > 0 {
+            self.pending_glyphs -= 1;
 
-        // Pull rasterized glyphs from the queue.
-
-        while !self.pending_glyphs.is_empty() {
             // TODO: rather than blocking until all pending glyphs are available
             // we could try_recv and steal work from the thread pool to take advantage
             // of the fact that this thread is alive and we avoid the added latency
             // of blocking it.
-            let raster_jobs = self.glyph_rx
+            let GlyphRasterJobs { font, mut jobs } = self.glyph_rx
                 .recv()
                 .expect("BUG: Should be glyphs pending!");
-            for job in raster_jobs {
-                debug_assert!(self.pending_glyphs.contains(&job.request));
-                self.pending_glyphs.remove(&job.request);
 
-                rasterized_glyphs.push(job);
-            }
-        }
+            // Ensure that the glyphs are always processed in the same
+            // order for a given text run (since iterating a hash set doesn't
+            // guarantee order). This can show up as very small float inaccuacry
+            // differences in rasterizers due to the different coordinates
+            // that text runs get associated with by the texture cache allocator.
+            jobs.sort_by(|a, b| a.key.cmp(&b.key));
 
-        // Ensure that the glyphs are always processed in the same
-        // order for a given text run (since iterating a hash set doesn't
-        // guarantee order). This can show up as very small float inaccuacry
-        // 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));
+            let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font);
 
-        // Update the caches.
-        for job in rasterized_glyphs {
-            let glyph_info = 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 glyph_bytes = Arc::new(glyph.bytes);
-                    let mut texture_cache_handle = TextureCacheHandle::new();
-                    texture_cache.request(&mut texture_cache_handle, gpu_cache);
-                    texture_cache.update(
-                        &mut texture_cache_handle,
-                        ImageDescriptor {
-                            width: glyph.width,
-                            height: glyph.height,
-                            stride: None,
-                            format: ImageFormat::BGRA8,
-                            is_opaque: false,
-                            offset: 0,
-                        },
-                        TextureFilter::Linear,
-                        Some(ImageData::Raw(glyph_bytes.clone())),
-                        [glyph.left, -glyph.top, glyph.scale],
-                        None,
-                        gpu_cache,
-                    );
-                    Some(CachedGlyphInfo {
-                        texture_cache_handle,
-                        glyph_bytes,
-                        size: DeviceUintSize::new(glyph.width, glyph.height),
-                        offset: DevicePoint::new(glyph.left, -glyph.top),
-                        scale: glyph.scale,
-                        format: glyph.format,
-                    })
-                } else {
-                    None
+            for GlyphRasterJob { key, result } in jobs {
+                let glyph_info = result.map_or(GlyphCacheEntry::Blank, |glyph| {
+                    if glyph.width > 0 && glyph.height > 0 {
+                        assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
+                        let mut texture_cache_handle = TextureCacheHandle::new();
+                        texture_cache.request(&mut texture_cache_handle, gpu_cache);
+                        texture_cache.update(
+                            &mut texture_cache_handle,
+                            ImageDescriptor {
+                                width: glyph.width,
+                                height: glyph.height,
+                                stride: None,
+                                format: ImageFormat::BGRA8,
+                                is_opaque: false,
+                                offset: 0,
+                            },
+                            TextureFilter::Linear,
+                            Some(ImageData::Raw(Arc::new(glyph.bytes))),
+                            [glyph.left, -glyph.top, glyph.scale],
+                            None,
+                            gpu_cache,
+                            Some(glyph_key_cache.eviction_notice()),
+                        );
+                        GlyphCacheEntry::Cached(CachedGlyphInfo {
+                            texture_cache_handle,
+                            format: glyph.format,
+                        })
+                    } else {
+                        GlyphCacheEntry::Blank
+                    }
                 });
-
-            let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(job.request.font);
-
-            glyph_key_cache.insert(job.request.key, Ok(glyph_info));
+                glyph_key_cache.insert(key, glyph_info);
+            }
         }
 
         // Now that we are done with the critical path (rendering the glyphs),
         // we can schedule removing the fonts if needed.
         if !self.fonts_to_remove.is_empty() {
             let font_contexts = Arc::clone(&self.font_contexts);
             let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
             self.workers.spawn(move || {
@@ -578,17 +559,17 @@ impl GlyphRasterizer {
                 }
             });
         }
     }
 
     #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
-        self.pending_glyphs.clear();
+        self.pending_glyphs = 0;
         self.fonts_to_remove.clear();
     }
 }
 
 impl FontContext {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
         match template {
             &FontTemplate::Raw(ref bytes, index) => {
@@ -614,20 +595,25 @@ impl GlyphRequest {
         GlyphRequest {
             key: key.clone(),
             font: font.clone(),
         }
     }
 }
 
 struct GlyphRasterJob {
-    request: GlyphRequest,
+    key: GlyphKey,
     result: Option<RasterizedGlyph>,
 }
 
+struct GlyphRasterJobs {
+    font: FontInstance,
+    jobs: Vec<GlyphRasterJob>,
+}
+
 #[test]
 fn rasterize_200_glyphs() {
     // This test loads a font from disc, the renders 4 requests containing
     // 50 glyphs each, deletes the font and waits for the result.
 
     use rayon::ThreadPoolBuilder;
     use std::fs::File;
     use std::io::Read;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -217,18 +217,17 @@ pub enum BrushKind {
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
     RadialGradient {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
-        start_center: LayerPoint,
-        end_center: LayerPoint,
+        center: LayerPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
     },
     LinearGradient {
         gradient_index: CachedGradientIndex,
         stops_range: ItemRange<GradientStop>,
         stops_count: usize,
@@ -374,28 +373,28 @@ impl BrushPrimitive {
                 ]);
                 request.push([
                     pack_as_float(extend_mode as u32),
                     0.0,
                     0.0,
                     0.0,
                 ]);
             }
-            BrushKind::RadialGradient { start_center, end_center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
+            BrushKind::RadialGradient { center, start_radius, end_radius, ratio_xy, extend_mode, .. } => {
                 request.push([
-                    start_center.x,
-                    start_center.y,
-                    end_center.x,
-                    end_center.y,
+                    center.x,
+                    center.y,
+                    start_radius,
+                    end_radius,
                 ]);
                 request.push([
-                    start_radius,
-                    end_radius,
                     ratio_xy,
                     pack_as_float(extend_mode as u32),
+                    0.,
+                    0.,
                 ]);
             }
         }
     }
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
 /* 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::{ApiMsg, FrameMsg};
+use api::{ApiMsg, FrameMsg, SceneMsg};
 use bincode::{serialize, Infinite};
 use byteorder::{LittleEndian, WriteBytesExt};
 use std::any::TypeId;
 use std::fmt::Debug;
 use std::fs::File;
 use std::io::Write;
 use std::mem;
 use std::path::PathBuf;
@@ -62,21 +62,33 @@ impl ApiRecordingReceiver for BinaryReco
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
     match *msg {
         ApiMsg::UpdateResources(..) |
         ApiMsg::AddDocument { .. } |
         ApiMsg::DeleteDocument(..) => true,
         ApiMsg::UpdateDocument(_, ref msgs) => {
+            if msgs.generate_frame {
+                return true;
+            }
+
+            for msg in &msgs.scene_ops {
+                match *msg {
+                    SceneMsg::SetDisplayList { .. } |
+                    SceneMsg::SetRootPipeline { .. } => return true,
+                    _ => {}
+                }
+            }
+
             for msg in &msgs.frame_ops {
                 match *msg {
                     FrameMsg::GetScrollNodeState(..) |
                     FrameMsg::HitTest(..) => {}
-                    _ => { return true; }
+                    _ => return true,
                 }
             }
 
             false
         }
         _ => false,
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -690,18 +690,19 @@ impl RenderBackend {
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
+        let mut keep_going = true;
 
-        loop {
+        while keep_going {
             profile_scope!("handle_msg");
 
             while let Ok(msg) = self.scene_rx.try_recv() {
                 match msg {
                     SceneBuilderResult::Transaction {
                         document_id,
                         mut built_scene,
                         resource_updates,
@@ -735,32 +736,29 @@ impl RenderBackend {
                                 &mut frame_counter,
                                 &mut profile_counters
                             );
                         }
                     }
                 }
             }
 
-            let keep_going = match self.api_rx.recv() {
+            keep_going = match self.api_rx.recv() {
                 Ok(msg) => {
                     if let Some(ref mut r) = self.recorder {
                         r.write_msg(frame_counter, &msg);
                     }
                     self.process_api_msg(msg, &mut profile_counters, &mut frame_counter)
                 }
                 Err(..) => { false }
             };
+        }
 
-            if !keep_going {
-                let _ = self.scene_tx.send(SceneBuilderRequest::Stop);
-                self.notifier.shut_down();
-                break;
-            }
-        }
+        let _ = self.scene_tx.send(SceneBuilderRequest::Stop);
+        self.notifier.shut_down();
     }
 
     fn process_api_msg(
         &mut self,
         msg: ApiMsg,
         profile_counters: &mut BackendProfileCounters,
         frame_counter: &mut u32,
     ) -> bool {
@@ -960,35 +958,35 @@ impl RenderBackend {
 
         for frame_msg in transaction_msg.frame_ops {
             let _timer = profile_counters.total_time.timer();
             op.combine(self.process_frame_msg(document_id, frame_msg));
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
-        if !doc.can_render() {
-            // TODO: this happens if we are building the first scene asynchronously and
-            // scroll at the same time. we should keep track of the fact that we skipped
-            // composition here and do it as soon as we receive the scene.
-            op.render = false;
-            op.composite = false;
-        }
-
         if transaction_msg.generate_frame {
             if let Some(ref mut ros) = doc.render_on_scroll {
                 *ros = true;
             }
 
             if doc.current.scene.root_pipeline_id.is_some() {
                 op.render = true;
                 op.composite = true;
             }
         }
 
+        if !doc.can_render() {
+            // TODO: this happens if we are building the first scene asynchronously and
+            // scroll at the same time. we should keep track of the fact that we skipped
+            // composition here and do it as soon as we receive the scene.
+            op.render = false;
+            op.composite = false;
+        }
+
         debug_assert!(op.render || !op.composite);
 
         if op.render {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
 
             // borrow ck hack for profile_counters
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -928,16 +928,17 @@ impl RenderTaskCache {
             texture_cache.update(
                 &mut cache_entry.handle,
                 descriptor,
                 TextureFilter::Linear,
                 None,
                 [0.0; 3],
                 None,
                 gpu_cache,
+                None,
             );
 
             // Get the allocation details in the texture cache, and store
             // this in the render task. The renderer will draw this
             // task into the appropriate layer and rect of the texture
             // cache on this frame.
             let (texture_id, texture_layer, uv_rect) =
                 texture_cache.get_cache_location(&cache_entry.handle);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -30,17 +30,17 @@ use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
 use device::{FileWatcherHandler, ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 use euclid::{rect, Transform3D};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
-use glyph_rasterizer::GlyphFormat;
+use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use gpu_types::PrimitiveInstance;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
 use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use picture::ContentOrigin;
 use prim_store::DeferredResolve;
@@ -1304,19 +1304,16 @@ impl Renderer {
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
         device.begin_frame();
 
         let shaders = Shaders::new(&mut device, gl_type, &options)?;
 
-        let texture_cache = TextureCache::new(max_device_size);
-        let max_texture_size = texture_cache.max_texture_size();
-
         let backend_profile_counters = BackendProfileCounters::new();
 
         let dither_matrix_texture = if options.enable_dithering {
             let dither_matrix: [u8; 64] = [
                 00,
                 48,
                 12,
                 60,
@@ -1484,21 +1481,17 @@ impl Renderer {
             });
         let enable_render_on_scroll = options.enable_render_on_scroll;
 
         let blob_image_renderer = options.blob_image_renderer.take();
         let thread_listener_for_render_backend = thread_listener.clone();
         let thread_listener_for_scene_builder = thread_listener.clone();
         let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
         let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
-        let resource_cache = ResourceCache::new(
-            texture_cache,
-            workers,
-            blob_image_renderer,
-        )?;
+        let glyph_rasterizer = GlyphRasterizer::new(workers)?;
 
         let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(config, api_tx.clone());
         thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(scene_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
                 thread_listener.thread_started(&scene_thread_name);
             }
 
@@ -1510,16 +1503,24 @@ impl Renderer {
             }
         })?;
 
         thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
             register_thread_with_profiler(rb_thread_name.clone());
             if let Some(ref thread_listener) = *thread_listener_for_render_backend {
                 thread_listener.thread_started(&rb_thread_name);
             }
+
+            let texture_cache = TextureCache::new(max_device_size);
+            let resource_cache = ResourceCache::new(
+                texture_cache,
+                glyph_rasterizer,
+                blob_image_renderer,
+            );
+
             let mut backend = RenderBackend::new(
                 api_rx,
                 payload_rx_for_backend,
                 result_tx,
                 scene_tx,
                 scene_rx,
                 device_pixel_ratio,
                 resource_cache,
@@ -1547,17 +1548,17 @@ impl Renderer {
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             shaders,
             debug: debug_renderer,
             debug_flags,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
-            max_texture_size: max_texture_size,
+            max_texture_size: max_device_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             gpu_profile,
             prim_vao,
             blur_vao,
             clip_vao,
@@ -2353,24 +2354,16 @@ impl Renderer {
 
     fn draw_instanced_batch<T>(
         &mut self,
         data: &[T],
         vertex_array_kind: VertexArrayKind,
         textures: &BatchTextures,
         stats: &mut RendererStats,
     ) {
-        // Work around Angle bug that forgets to update sampler metadata,
-        // by making the use of those samplers uniform across programs.
-        // https://github.com/servo/webrender/wiki/Driver-issues#texturesize-in-vertex-shaders
-        let work_around_angle_bug = cfg!(windows);
-        if work_around_angle_bug {
-            self.device.reset_angle_sampler_metadata(&self.texture_resolver.dummy_cache_texture);
-        }
-
         for i in 0 .. textures.colors.len() {
             self.texture_resolver.bind(
                 &textures.colors[i],
                 TextureSampler::color(i),
                 &mut self.device,
             );
         }
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -14,26 +14,21 @@ use api::{TileOffset, TileSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
-use glyph_cache::GlyphCache;
-#[cfg(feature = "capture")]
-use glyph_cache::{PlainGlyphCacheRef, PlainCachedGlyphInfo};
-#[cfg(feature = "replay")]
-use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn};
+use glyph_cache::{GlyphCache, GlyphCacheEntry};
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use internal_types::{FastHashMap, FastHashSet, ResourceCacheError, SourceTexture, TextureUpdateList};
+use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
-use rayon::ThreadPool;
 use render_backend::FrameId;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 #[cfg(any(feature = "capture", feature = "replay"))]
@@ -138,56 +133,50 @@ impl ImageTemplates {
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct CachedImageInfo {
     texture_cache_handle: TextureCacheHandle,
     epoch: Epoch,
 }
 
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub enum ResourceClassCacheError {
-    OverLimitSize,
-}
-
-pub type ResourceCacheResult<V> = Result<V, ResourceClassCacheError>;
-
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ResourceClassCache<K: Hash + Eq, V> {
-    resources: FastHashMap<K, ResourceCacheResult<V>>,
+pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
+    resources: FastHashMap<K, V>,
+    pub user_data: U,
 }
 
-impl<K, V> ResourceClassCache<K, V>
+impl<K, V, U> ResourceClassCache<K, V, U>
 where
     K: Clone + Hash + Eq + Debug,
+    U: Default,
 {
-    pub fn new() -> ResourceClassCache<K, V> {
+    pub fn new() -> ResourceClassCache<K, V, U> {
         ResourceClassCache {
             resources: FastHashMap::default(),
+            user_data: Default::default(),
         }
     }
 
-    fn get(&self, key: &K) -> &ResourceCacheResult<V> {
+    pub fn get(&self, key: &K) -> &V {
         self.resources.get(key)
             .expect("Didn't find a cached resource with that ID!")
     }
 
-    pub fn insert(&mut self, key: K, value: ResourceCacheResult<V>) {
+    pub fn insert(&mut self, key: K, value: V) {
         self.resources.insert(key, value);
     }
 
-    pub fn get_mut(&mut self, key: &K) -> &mut ResourceCacheResult<V> {
+    pub fn get_mut(&mut self, key: &K) -> &mut V {
         self.resources.get_mut(key)
             .expect("Didn't find a cached resource with that ID!")
     }
 
-    pub fn entry(&mut self, key: K) -> Entry<K, ResourceCacheResult<V>> {
+    pub fn entry(&mut self, key: K) -> Entry<K, V> {
         self.resources.entry(key)
     }
 
     pub fn clear(&mut self) {
         self.resources.clear();
     }
 
     fn clear_keys<F>(&mut self, key_fun: F)
@@ -198,16 +187,23 @@ where
             .keys()
             .filter(&key_fun)
             .cloned()
             .collect::<Vec<_>>();
         for key in resources_to_destroy {
             let _ = self.resources.remove(&key).unwrap();
         }
     }
+
+    pub fn retain<F>(&mut self, f: F)
+    where
+        F: FnMut(&K, &mut V) -> bool,
+    {
+        self.resources.retain(f);
+    }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageRequest {
     pub key: ImageKey,
@@ -219,17 +215,24 @@ impl Into<BlobImageRequest> for ImageReq
     fn into(self) -> BlobImageRequest {
         BlobImageRequest {
             key: self.key,
             tile: self.tile,
         }
     }
 }
 
-type ImageCache = ResourceClassCache<ImageRequest, CachedImageInfo>;
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ImageCacheError {
+    OverLimitSize,
+}
+
+type ImageCache = ResourceClassCache<ImageRequest, Result<CachedImageInfo, ImageCacheError>, ()>;
 pub type FontInstanceMap = Arc<RwLock<FastHashMap<FontInstanceKey, FontInstance>>>;
 
 #[derive(Default)]
 struct Resources {
     font_templates: FastHashMap<FontKey, FontTemplate>,
     font_instances: FontInstanceMap,
     image_templates: ImageTemplates,
 }
@@ -268,34 +271,32 @@ pub struct ResourceCache {
     pending_image_requests: FastHashSet<ImageRequest>,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
 }
 
 impl ResourceCache {
     pub fn new(
         texture_cache: TextureCache,
-        workers: Arc<ThreadPool>,
+        glyph_rasterizer: GlyphRasterizer,
         blob_image_renderer: Option<Box<BlobImageRenderer>>,
-    ) -> Result<Self, ResourceCacheError> {
-        let glyph_rasterizer = GlyphRasterizer::new(workers)?;
-
-        Ok(ResourceCache {
+    ) -> Self {
+        ResourceCache {
             cached_glyphs: GlyphCache::new(),
             cached_images: ResourceClassCache::new(),
             cached_render_tasks: RenderTaskCache::new(),
             resources: Resources::default(),
             cached_glyph_dimensions: FastHashMap::default(),
             texture_cache,
             state: State::Idle,
             current_frame_id: FrameId(0),
             pending_image_requests: FastHashSet::default(),
             glyph_rasterizer,
             blob_image_renderer,
-        })
+        }
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.texture_cache.max_texture_size()
     }
 
     fn should_tile(limit: u32, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
         let size_check = descriptor.width > limit || descriptor.height > limit;
@@ -561,17 +562,17 @@ impl ResourceCache {
 
         let side_size =
             template.tiling.map_or(cmp::max(template.descriptor.width, template.descriptor.height),
                                    |tile_size| tile_size as u32);
         if side_size > self.texture_cache.max_texture_size() {
             // The image or tiling size is too big for hardware texture size.
             warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!",
                   template.descriptor.width, template.descriptor.height, template.tiling.unwrap_or(0));
-            self.cached_images.insert(request, Err(ResourceClassCacheError::OverLimitSize));
+            self.cached_images.insert(request, Err(ImageCacheError::OverLimitSize));
             return;
         }
 
         // If this image exists in the texture cache, *and* the epoch
         // in the cache matches that of the template, then it is
         // valid to use as-is.
         let (entry, needs_update) = match self.cached_images.entry(request) {
             Occupied(entry) => {
@@ -677,17 +678,17 @@ impl ResourceCache {
         self.glyph_rasterizer.prepare_font(&mut font);
         let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
 
         let mut current_texture_id = SourceTexture::Invalid;
         let mut current_glyph_format = GlyphFormat::Subpixel;
         debug_assert!(fetch_buffer.is_empty());
 
         for (loop_index, key) in glyph_keys.iter().enumerate() {
-            if let Ok(Some(ref glyph)) = *glyph_key_cache.get(key) {
+            if let GlyphCacheEntry::Cached(ref glyph) = *glyph_key_cache.get(key) {
                 let cache_item = self.texture_cache.get(&glyph.texture_cache_handle);
                 if current_texture_id != cache_item.texture_id ||
                    current_glyph_format != glyph.format {
                     if !fetch_buffer.is_empty() {
                         f(current_texture_id, current_glyph_format, fetch_buffer);
                         fetch_buffer.clear();
                     }
                     current_texture_id = cache_item.texture_id;
@@ -789,16 +790,17 @@ impl ResourceCache {
             })
             .collect()
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
+        self.cached_glyphs.begin_frame(&mut self.texture_cache);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         texture_cache_profile: &mut TextureCacheProfileCounters,
@@ -923,16 +925,17 @@ impl ResourceCache {
             self.texture_cache.update(
                 &mut entry.texture_cache_handle,
                 descriptor,
                 filter,
                 Some(image_data),
                 [0.0; 3],
                 image_template.dirty_rect,
                 gpu_cache,
+                None,
             );
             image_template.dirty_rect = None;
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
@@ -1030,28 +1033,28 @@ pub struct PlainResources {
     font_instances: FastHashMap<FontInstanceKey, FontInstance>,
     image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
 }
 
 #[cfg(feature = "capture")]
 #[derive(Serialize)]
 pub struct PlainCacheRef<'a> {
     current_frame_id: FrameId,
-    glyphs: PlainGlyphCacheRef<'a>,
+    glyphs: &'a GlyphCache,
     glyph_dimensions: &'a GlyphDimensionsCache,
     images: &'a ImageCache,
     render_tasks: &'a RenderTaskCache,
     textures: &'a TextureCache,
 }
 
 #[cfg(feature = "replay")]
 #[derive(Deserialize)]
 pub struct PlainCacheOwn {
     current_frame_id: FrameId,
-    glyphs: PlainGlyphCacheOwn,
+    glyphs: GlyphCache,
     glyph_dimensions: GlyphDimensionsCache,
     images: ImageCache,
     render_tasks: RenderTaskCache,
     textures: TextureCache,
 }
 
 #[cfg(feature = "replay")]
 const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
@@ -1223,74 +1226,20 @@ impl ResourceCache {
                 })
                 .collect(),
         };
 
         (resources, external_images)
     }
 
     #[cfg(feature = "capture")]
-    pub fn save_caches(&self, root: &PathBuf) -> PlainCacheRef {
-        use std::io::Write;
-        use std::fs;
-
-        let path_glyphs = root.join("glyphs");
-        if !path_glyphs.is_dir() {
-            fs::create_dir(&path_glyphs).unwrap();
-        }
-
-        info!("\tcached glyphs");
-        let mut glyph_paths = FastHashMap::default();
-        for cache in self.cached_glyphs.glyph_key_caches.values() {
-            for result in cache.resources.values() {
-                let arc = match *result {
-                    Ok(Some(ref info)) => &info.glyph_bytes,
-                    Ok(None) | Err(_) => continue,
-                };
-                let glyph_id = glyph_paths.len() + 1;
-                let entry = match glyph_paths.entry(arc.as_ptr()) {
-                    Entry::Occupied(_) => continue,
-                    Entry::Vacant(e) => e,
-                };
-
-                let file_name = format!("{}.raw", glyph_id);
-                let short_path = format!("glyphs/{}", file_name);
-                fs::File::create(path_glyphs.join(&file_name))
-                    .expect(&format!("Unable to create {}", short_path))
-                    .write_all(&*arc)
-                    .unwrap();
-                entry.insert(short_path);
-            }
-        }
-
+    pub fn save_caches(&self, _root: &PathBuf) -> PlainCacheRef {
         PlainCacheRef {
             current_frame_id: self.current_frame_id,
-            glyphs: self.cached_glyphs.glyph_key_caches
-                .iter()
-                .map(|(font_instance, cache)| {
-                    let resources = cache.resources
-                        .iter()
-                        .map(|(key, result)| {
-                            (key.clone(), match *result {
-                                Ok(Some(ref info)) => Ok(Some(PlainCachedGlyphInfo {
-                                    texture_cache_handle: info.texture_cache_handle.clone(),
-                                    glyph_bytes: glyph_paths[&info.glyph_bytes.as_ptr()].clone(),
-                                    size: info.size,
-                                    offset: info.offset,
-                                    scale: info.scale,
-                                    format: info.format,
-                                })),
-                                Ok(None) => Ok(None),
-                                Err(ref e) => Err(e.clone()),
-                            })
-                        })
-                        .collect();
-                    (font_instance, ResourceClassCache { resources })
-                })
-                .collect(),
+            glyphs: &self.cached_glyphs,
             glyph_dimensions: &self.cached_glyph_dimensions,
             images: &self.cached_images,
             render_tasks: &self.cached_render_tasks,
             textures: &self.texture_cache,
         }
     }
 
     #[cfg(feature = "replay")]
@@ -1306,57 +1255,18 @@ impl ResourceCache {
         info!("loading resource cache");
         //TODO: instead of filling the local path to Arc<data> map as we process
         // each of the resource types, we could go through all of the local paths
         // and fill out the map as the first step.
         let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
 
         match caches {
             Some(cached) => {
-                let glyph_key_caches = cached.glyphs
-                    .into_iter()
-                    .map(|(font_instance, rcc)| {
-                        let resources = rcc.resources
-                            .into_iter()
-                            .map(|(key, result)| {
-                                (key, match result {
-                                    Ok(Some(info)) => {
-                                        let glyph_bytes = match raw_map.entry(info.glyph_bytes) {
-                                            Entry::Occupied(e) => {
-                                                e.get().clone()
-                                            }
-                                            Entry::Vacant(e) => {
-                                                let mut buffer = Vec::new();
-                                                File::open(root.join(e.key()))
-                                                    .expect(&format!("Unable to open {}", e.key()))
-                                                    .read_to_end(&mut buffer)
-                                                    .unwrap();
-                                                e.insert(Arc::new(buffer))
-                                                    .clone()
-                                            }
-                                        };
-                                        Ok(Some(CachedGlyphInfo {
-                                            texture_cache_handle: info.texture_cache_handle,
-                                            glyph_bytes,
-                                            size: info.size,
-                                            offset: info.offset,
-                                            scale: info.scale,
-                                            format: info.format,
-                                        }))
-                                    },
-                                    Ok(None) => Ok(None),
-                                    Err(e) => Err(e),
-                                })
-                            })
-                            .collect();
-                        (font_instance, ResourceClassCache { resources })
-                    })
-                    .collect();
                 self.current_frame_id = cached.current_frame_id;
-                self.cached_glyphs = GlyphCache { glyph_key_caches };
+                self.cached_glyphs = cached.glyphs;
                 self.cached_glyph_dimensions = cached.glyph_dimensions;
                 self.cached_images = cached.images;
                 self.cached_render_tasks = cached.render_tasks;
                 self.texture_cache = cached.textures;
             }
             None => {
                 self.current_frame_id = FrameId(0);
                 self.cached_glyphs.clear();
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -9,18 +9,20 @@ use device::TextureFilter;
 use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::ImageSource;
 use internal_types::{CacheTextureId, FastHashMap, TextureUpdateList, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpdateOp};
 use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use resource_cache::CacheItem;
+use std::cell::Cell;
 use std::cmp;
 use std::mem;
+use std::rc::Rc;
 
 // The fixed number of layers for the shared texture cache.
 // There is one array texture per image format, allocated lazily.
 const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4;
 const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1;
 
 // The dimensions of each layer in the texture cache.
 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
@@ -98,16 +100,18 @@ struct CacheEntry {
     last_access: FrameId,
     // Handle to the resource rect in the GPU cache.
     uv_rect_handle: GpuCacheHandle,
     // Image format of the item.
     format: ImageFormat,
     filter: TextureFilter,
     // The actual device texture ID this is part of.
     texture_id: CacheTextureId,
+    // Optional notice when the entry is evicted from the cache.
+    eviction_notice: Option<EvictionNotice>,
 }
 
 impl CacheEntry {
     // Create a new entry for a standalone texture.
     fn new_standalone(
         texture_id: CacheTextureId,
         size: DeviceUintSize,
         format: ImageFormat,
@@ -119,16 +123,17 @@ impl CacheEntry {
             size,
             user_data,
             last_access,
             kind: EntryKind::Standalone,
             texture_id,
             format,
             filter,
             uv_rect_handle: GpuCacheHandle::new(),
+            eviction_notice: None,
         }
     }
 
     // Update the GPU cache for this texture cache entry.
     // This ensures that the UV rect, and texture layer index
     // are up to date in the GPU cache for vertex shaders
     // to fetch from.
     fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
@@ -145,16 +150,22 @@ impl CacheEntry {
                 p0: origin.to_f32(),
                 p1: (origin + self.size).to_f32(),
                 texture_layer: layer_index,
                 user_data: self.user_data,
             };
             image_source.write_gpu_blocks(&mut request);
         }
     }
+
+    fn evict(&self) {
+        if let Some(eviction_notice) = self.eviction_notice.as_ref() {
+            eviction_notice.notify();
+        }
+    }
 }
 
 type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntry>;
 
 // A texture cache handle is a weak reference to a cache entry.
 // If the handle has not been inserted into the cache yet, the
 // value will be None. Even when the value is Some(), the location
 // may not actually be valid if it has been evicted by the cache.
@@ -168,16 +179,44 @@ pub struct TextureCacheHandle {
 }
 
 impl TextureCacheHandle {
     pub fn new() -> Self {
         TextureCacheHandle { entry: None }
     }
 }
 
+// An eviction notice is a shared condition useful for detecting
+// when a TextureCacheHandle gets evicted from the TextureCache.
+// It is optionally installed to the TextureCache when an update()
+// is scheduled. A single notice may be shared among any number of
+// TextureCacheHandle updates. The notice may then be subsequently
+// checked to see if any of the updates using it have been evicted.
+#[derive(Clone, Debug, Default)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct EvictionNotice {
+    evicted: Rc<Cell<bool>>,
+}
+
+impl EvictionNotice {
+    fn notify(&self) {
+        self.evicted.set(true);
+    }
+
+    pub fn check(&self) -> bool {
+        if self.evicted.get() {
+            self.evicted.set(false);
+            true
+        } else {
+            false
+        }
+    }
+}
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCache {
     // A lazily allocated, fixed size, texture array for
     // each format the texture cache supports.
     array_rgba8_nearest: TextureArray,
     array_a8_linear: TextureArray,
     array_rgba8_linear: TextureArray,
@@ -296,16 +335,17 @@ impl TextureCache {
         &mut self,
         handle: &mut TextureCacheHandle,
         descriptor: ImageDescriptor,
         filter: TextureFilter,
         data: Option<ImageData>,
         user_data: [f32; 3],
         mut dirty_rect: Option<DeviceUintRect>,
         gpu_cache: &mut GpuCache,
+        eviction_notice: Option<&EvictionNotice>,
     ) {
         // Determine if we need to allocate texture cache memory
         // for this item. We need to reallocate if any of the following
         // is true:
         // - Never been in the cache
         // - Has been in the cache but was evicted.
         // - Exists in the cache but dimensions / format have changed.
         let realloc = match handle.entry {
@@ -334,16 +374,19 @@ impl TextureCache {
             // If we reallocated, we need to upload the whole item again.
             dirty_rect = None;
         }
 
         let entry = self.entries
             .get_opt_mut(handle.entry.as_ref().unwrap())
             .expect("BUG: handle must be valid now");
 
+        // Install the new eviction notice for this update, if applicable.
+        entry.eviction_notice = eviction_notice.cloned();
+
         // Invalidate the contents of the resource rect in the GPU cache.
         // This ensures that the update_gpu_cache below will add
         // the new information to the GPU cache.
         gpu_cache.invalidate(&entry.uv_rect_handle);
 
         // Upload the resource rect and texture array layer.
         entry.update_gpu_cache(gpu_cache);
 
@@ -493,16 +536,17 @@ impl TextureCache {
         if eviction_candidates.len() > 32 {
             let entries_to_keep = eviction_candidates.split_off(32);
             retained_entries.extend(entries_to_keep);
         }
 
         // Free the selected items
         for handle in eviction_candidates {
             let entry = self.entries.free(handle);
+            entry.evict();
             self.free(entry);
         }
 
         // Keep a record of the remaining handles for next frame.
         self.standalone_entry_handles = retained_entries;
     }
 
     // Expire old shared items. Pass in the allocation size
@@ -547,16 +591,17 @@ impl TextureCache {
         let mut freed_complete_page = false;
         let mut evicted_items = 0;
 
         for handle in eviction_candidates {
             if evicted_items > 512 && (found_matching_slab || freed_complete_page) {
                 retained_entries.push(handle);
             } else {
                 let entry = self.entries.free(handle);
+                entry.evict();
                 if let Some(region) = self.free(entry) {
                     found_matching_slab |= region.slab_size == needed_slab_size;
                     freed_complete_page |= region.is_empty();
                 }
                 evicted_items += 1;
             }
         }
 
@@ -1064,16 +1109,17 @@ impl TextureArray {
                 size: DeviceUintSize::new(width, height),
                 user_data,
                 last_access: frame_id,
                 kind,
                 uv_rect_handle: GpuCacheHandle::new(),
                 format: self.format,
                 filter: self.filter,
                 texture_id: self.texture_id.unwrap(),
+                eviction_notice: None,
             }
         })
     }
 }
 
 impl TextureUpdate {
     // Constructs a TextureUpdate operation to be passed to the
     // rendering thread in order to do an upload to the right
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -413,21 +413,20 @@ pub struct GradientDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GradientStop {
     pub offset: f32,
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradient {
-    pub start_center: LayoutPoint,
+    pub center: LayoutPoint,
+    pub radius: LayoutSize,
     pub start_radius: f32,
-    pub end_center: LayoutPoint,
     pub end_radius: f32,
-    pub ratio_xy: f32,
     pub extend_mode: ExtendMode,
 } // IMPLICIT stops: Vec<GradientStop>
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipChainItem {
     pub id: ClipChainId,
     pub parent: Option<ClipChainId>,
 } // IMPLICIT stops: Vec<ClipId>
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -1183,60 +1183,56 @@ impl DisplayListBuilder {
                     offset: 1.0,
                     color: last_color,
                 },
             ];
 
             self.push_stops(&stops);
 
             return RadialGradient {
-                start_center: center,
+                center,
+                radius: LayoutSize::new(1.0, 1.0),
                 start_radius: 0.0,
-                end_center: center,
                 end_radius: 1.0,
-                ratio_xy: 1.0,
                 extend_mode,
             };
         }
 
         let (start_offset, end_offset) =
             DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
 
         self.push_stops(&stops);
 
         RadialGradient {
-            start_center: center,
+            center,
+            radius,
             start_radius: radius.width * start_offset,
-            end_center: center,
             end_radius: radius.width * end_offset,
-            ratio_xy: radius.width / radius.height,
             extend_mode,
         }
     }
 
     // NOTE: gradients must be pushed in the order they're created
     // because create_gradient stores the stops in anticipation
     pub fn create_complex_radial_gradient(
         &mut self,
-        start_center: LayoutPoint,
+        center: LayoutPoint,
+        radius: LayoutSize,
         start_radius: f32,
-        end_center: LayoutPoint,
         end_radius: f32,
-        ratio_xy: f32,
         stops: Vec<GradientStop>,
         extend_mode: ExtendMode,
     ) -> RadialGradient {
         self.push_stops(&stops);
 
         RadialGradient {
-            start_center,
+            center,
+            radius,
             start_radius,
-            end_center,
             end_radius,
-            ratio_xy,
             extend_mode,
         }
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         widths: BorderWidths,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-486ee5f3aefb0172c2c5703e19f833e63eb295b9
+2083e83d958dd4a230ccae5c518e4bc8fbf88009
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -1,25 +1,25 @@
 [package]
 name = "wrench"
 version = "0.3.0"
 authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
 build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
-base64 = "0.3"
+base64 = "0.6"
 bincode = "0.9"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.17"
 gleam = "0.4"
 glutin = "0.12"
 app_units = "0.6"
-image = "0.17"
+image = "0.18"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
@@ -33,11 +33,12 @@ serde = {version = "1.0", features = ["d
 core-graphics = "0.13"
 core-foundation = "0.5"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
+mozangle = {version = "0.1.5", features = ["egl"]}
 
 [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
 font-loader = "0.6"
new file mode 100644
--- /dev/null
+++ b/gfx/wrench/src/angle.rs
@@ -0,0 +1,72 @@
+/* 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 glutin;
+use glutin::{WindowBuilder, ContextBuilder, EventsLoop, Window, CreationError};
+
+#[cfg(not(windows))]
+pub enum Context {}
+
+#[cfg(windows)]
+pub use ::egl::Context;
+
+impl Context {
+    #[cfg(not(windows))]
+    pub fn with_window(
+        _: WindowBuilder,
+        _: ContextBuilder,
+        _: &EventsLoop,
+    ) -> Result<(Window, Self), CreationError> {
+        Err(CreationError::PlatformSpecific("ANGLE rendering is only supported on Windows".into()))
+    }
+
+    #[cfg(windows)]
+    pub fn with_window(
+        window_builder: WindowBuilder,
+        context_builder: ContextBuilder,
+        events_loop: &EventsLoop,
+    ) -> Result<(Window, Self), CreationError> {
+        use glutin::os::windows::WindowExt;
+
+        // FIXME: &context_builder.pf_reqs  https://github.com/tomaka/glutin/pull/1002
+        let pf_reqs = &glutin::PixelFormatRequirements::default();
+        let gl_attr = &context_builder.gl_attr.map_sharing(|_| unimplemented!());
+        let window = window_builder.build(events_loop)?;
+        Self::new(pf_reqs, gl_attr)
+            .and_then(|p| p.finish(window.get_hwnd() as _))
+            .map(|context| (window, context))
+    }
+}
+
+#[cfg(not(windows))]
+impl glutin::GlContext for Context {
+    unsafe fn make_current(&self) -> Result<(), glutin::ContextError> {
+        match *self {}
+    }
+
+    fn is_current(&self) -> bool {
+        match *self {}
+    }
+
+    fn get_proc_address(&self, _: &str) -> *const () {
+        match *self {}
+    }
+
+    fn swap_buffers(&self) -> Result<(), glutin::ContextError> {
+        match *self {}
+    }
+
+    fn get_api(&self) -> glutin::Api {
+        match *self {}
+    }
+
+    fn get_pixel_format(&self) -> glutin::PixelFormat {
+        match *self {}
+    }
+
+    fn resize(&self, _: u32, _: u32) {
+        match *self {}
+    }
+}
+
--- a/gfx/wrench/src/args.yaml
+++ b/gfx/wrench/src/args.yaml
@@ -39,16 +39,19 @@ args:
       help: Disable subpixel aa
   - slow_subpixel:
       long: slow-subpixel
       help: Disable dual source blending
   - headless:
       short: h
       long: headless
       help: Enable headless rendering
+  - angle:
+      long: angle
+      help: Enable ANGLE rendering (on Windows only)
   - dp_ratio:
       short: p
       long: device-pixel-ratio
       help: Device pixel ratio
       takes_value: true
   - size:
       short: s
       long: size
new file mode 100644
--- /dev/null
+++ b/gfx/wrench/src/egl.rs
@@ -0,0 +1,617 @@
+// Licensed under the Apache License, Version 2.0.
+// This file may not be copied, modified, or distributed except according to those terms.
+
+//! Based on https://github.com/tomaka/glutin/blob/1b2d62c0e9/src/api/egl/mod.rs
+#![cfg(windows)]
+#![allow(unused_variables)]
+
+use glutin::ContextError;
+use glutin::CreationError;
+use glutin::GlAttributes;
+use glutin::GlContext;
+use glutin::GlRequest;
+use glutin::PixelFormat;
+use glutin::PixelFormatRequirements;
+use glutin::ReleaseBehavior;
+use glutin::Robustness;
+use glutin::Api;
+
+use std::ffi::{CStr, CString};
+use std::os::raw::c_int;
+use std::{mem, ptr};
+use std::cell::Cell;
+
+use mozangle::egl::ffi as egl;
+mod ffi {
+    pub use mozangle::egl::ffi as egl;
+    pub use mozangle::egl::ffi::*;
+}
+
+pub struct Context {
+    display: ffi::egl::types::EGLDisplay,
+    context: ffi::egl::types::EGLContext,
+    surface: Cell<ffi::egl::types::EGLSurface>,
+    api: Api,
+    pixel_format: PixelFormat,
+}
+
+impl Context {
+    /// Start building an EGL context.
+    ///
+    /// This function initializes some things and chooses the pixel format.
+    ///
+    /// To finish the process, you must call `.finish(window)` on the `ContextPrototype`.
+    pub fn new<'a>(
+        pf_reqs: &PixelFormatRequirements,
+        opengl: &'a GlAttributes<&'a Context>,
+    ) -> Result<ContextPrototype<'a>, CreationError>
+    {
+        if opengl.sharing.is_some() {
+            unimplemented!()
+        }
+
+        // calling `eglGetDisplay` or equivalent
+        let display = unsafe { egl::GetDisplay(ptr::null_mut()) };
+
+        if display.is_null() {
+            return Err(CreationError::OsError("Could not create EGL display object".to_string()));
+        }
+
+        let egl_version = unsafe {
+            let mut major: ffi::egl::types::EGLint = mem::uninitialized();
+            let mut minor: ffi::egl::types::EGLint = mem::uninitialized();
+
+            if egl::Initialize(display, &mut major, &mut minor) == 0 {
+                return Err(CreationError::OsError(format!("eglInitialize failed")))
+            }
+
+            (major, minor)
+        };
+
+        // the list of extensions supported by the client once initialized is different from the
+        // list of extensions obtained earlier
+        let extensions = if egl_version >= (1, 2) {
+            let p = unsafe { CStr::from_ptr(egl::QueryString(display, ffi::egl::EXTENSIONS as i32)) };
+            let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| format!(""));
+            list.split(' ').map(|e| e.to_string()).collect::<Vec<_>>()
+
+        } else {
+            vec![]
+        };
+
+        // binding the right API and choosing the version
+        let (version, api) = unsafe {
+            match opengl.version {
+                GlRequest::Latest => {
+                    if egl_version >= (1, 4) {
+                        if egl::BindAPI(ffi::egl::OPENGL_API) != 0 {
+                            (None, Api::OpenGl)
+                        } else if egl::BindAPI(ffi::egl::OPENGL_ES_API) != 0 {
+                            (None, Api::OpenGlEs)
+                        } else {
+                            return Err(CreationError::OpenGlVersionNotSupported);
+                        }
+                    } else {
+                        (None, Api::OpenGlEs)
+                    }
+                },
+                GlRequest::Specific(Api::OpenGlEs, version) => {
+                    if egl_version >= (1, 2) {
+                        if egl::BindAPI(ffi::egl::OPENGL_ES_API) == 0 {
+                            return Err(CreationError::OpenGlVersionNotSupported);
+                        }
+                    }
+                    (Some(version), Api::OpenGlEs)
+                },
+                GlRequest::Specific(Api::OpenGl, version) => {
+                    if egl_version < (1, 4) {
+                        return Err(CreationError::OpenGlVersionNotSupported);
+                    }
+                    if egl::BindAPI(ffi::egl::OPENGL_API) == 0 {
+                        return Err(CreationError::OpenGlVersionNotSupported);
+                    }
+                    (Some(version), Api::OpenGl)
+                },
+                GlRequest::Specific(_, _) => return Err(CreationError::OpenGlVersionNotSupported),
+                GlRequest::GlThenGles { opengles_version, opengl_version } => {
+                    if egl_version >= (1, 4) {
+                        if egl::BindAPI(ffi::egl::OPENGL_API) != 0 {
+                            (Some(opengl_version), Api::OpenGl)
+                        } else if egl::BindAPI(ffi::egl::OPENGL_ES_API) != 0 {
+                            (Some(opengles_version), Api::OpenGlEs)
+                        } else {
+                            return Err(CreationError::OpenGlVersionNotSupported);
+                        }
+                    } else {
+                        (Some(opengles_version), Api::OpenGlEs)
+                    }
+                },
+            }
+        };
+
+        let (config_id, pixel_format) = unsafe {
+            try!(choose_fbconfig(display, &egl_version, api, version, pf_reqs))
+        };
+
+        Ok(ContextPrototype {
+            opengl: opengl,
+            display: display,
+            egl_version: egl_version,
+            extensions: extensions,
+            api: api,
+            version: version,
+            config_id: config_id,
+            pixel_format: pixel_format,
+        })
+    }
+}
+
+impl GlContext for Context {
+    unsafe fn make_current(&self) -> Result<(), ContextError> {
+        let ret = egl::MakeCurrent(self.display, self.surface.get(), self.surface.get(), self.context);
+
+        if ret == 0 {
+            match egl::GetError() as u32 {
+                ffi::egl::CONTEXT_LOST => return Err(ContextError::ContextLost),
+                err => panic!("eglMakeCurrent failed (eglGetError returned 0x{:x})", err)
+            }
+
+        } else {
+            Ok(())
+        }
+    }
+
+    #[inline]
+    fn is_current(&self) -> bool {
+        unsafe { egl::GetCurrentContext() == self.context }
+    }
+
+    fn get_proc_address(&self, addr: &str) -> *const () {
+        let addr = CString::new(addr.as_bytes()).unwrap();
+        let addr = addr.as_ptr();
+        unsafe {
+            egl::GetProcAddress(addr) as *const _
+        }
+    }
+
+    #[inline]
+    fn swap_buffers(&self) -> Result<(), ContextError> {
+        if self.surface.get() == ffi::egl::NO_SURFACE {
+            return Err(ContextError::ContextLost);
+        }
+
+        let ret = unsafe {
+            egl::SwapBuffers(self.display, self.surface.get())
+        };
+
+        if ret == 0 {
+            match unsafe { egl::GetError() } as u32 {
+                ffi::egl::CONTEXT_LOST => return Err(ContextError::ContextLost),
+                err => panic!("eglSwapBuffers failed (eglGetError returned 0x{:x})", err)
+            }
+
+        } else {
+            Ok(())
+        }
+    }
+
+    #[inline]
+    fn get_api(&self) -> Api {
+        self.api
+    }
+
+    #[inline]
+    fn get_pixel_format(&self) -> PixelFormat {
+        self.pixel_format.clone()
+    }
+
+    #[inline]
+    fn resize(&self, _: u32, _: u32) {}
+}
+
+unsafe impl Send for Context {}
+unsafe impl Sync for Context {}
+
+impl Drop for Context {
+    fn drop(&mut self) {
+        unsafe {
+            // we don't call MakeCurrent(0, 0) because we are not sure that the context
+            // is still the current one
+            egl::DestroyContext(self.display, self.context);
+            egl::DestroySurface(self.display, self.surface.get());
+            egl::Terminate(self.display);
+        }
+    }
+}
+
+pub struct ContextPrototype<'a> {
+    opengl: &'a GlAttributes<&'a Context>,
+    display: ffi::egl::types::EGLDisplay,
+    egl_version: (ffi::egl::types::EGLint, ffi::egl::types::EGLint),
+    extensions: Vec<String>,
+    api: Api,
+    version: Option<(u8, u8)>,
+    config_id: ffi::egl::types::EGLConfig,
+    pixel_format: PixelFormat,
+}
+
+impl<'a> ContextPrototype<'a> {
+    pub fn get_native_visual_id(&self) -> ffi::egl::types::EGLint {
+        let mut value = unsafe { mem::uninitialized() };
+        let ret = unsafe { egl::GetConfigAttrib(self.display, self.config_id,
+                                                    ffi::egl::NATIVE_VISUAL_ID
+                                                    as ffi::egl::types::EGLint, &mut value) };
+        if ret == 0 { panic!("eglGetConfigAttrib failed") };
+        value
+    }
+
+    pub fn finish(self, native_window: ffi::EGLNativeWindowType)
+                  -> Result<Context, CreationError>
+    {
+        let surface = unsafe {
+            let surface = egl::CreateWindowSurface(self.display, self.config_id, native_window,
+                                                       ptr::null());
+            if surface.is_null() {
+                return Err(CreationError::OsError(format!("eglCreateWindowSurface failed")))
+            }
+            surface
+        };
+
+        self.finish_impl(surface)
+    }
+
+    pub fn finish_pbuffer(self, dimensions: (u32, u32)) -> Result<Context, CreationError> {
+        let attrs = &[
+            ffi::egl::WIDTH as c_int, dimensions.0 as c_int,
+            ffi::egl::HEIGHT as c_int, dimensions.1 as c_int,
+            ffi::egl::NONE as c_int,
+        ];
+
+        let surface = unsafe {
+            let surface = egl::CreatePbufferSurface(self.display, self.config_id,
+                                                        attrs.as_ptr());
+            if surface.is_null() {
+                return Err(CreationError::OsError(format!("eglCreatePbufferSurface failed")))
+            }
+            surface
+        };
+
+        self.finish_impl(surface)
+    }
+
+    fn finish_impl(self, surface: ffi::egl::types::EGLSurface)
+                   -> Result<Context, CreationError>
+    {
+        let context = unsafe {
+            if let Some(version) = self.version {
+                try!(create_context(self.display, &self.egl_version,
+                                    &self.extensions, self.api, version, self.config_id,
+                                    self.opengl.debug, self.opengl.robustness))
+
+            } else if self.api == Api::OpenGlEs {
+                if let Ok(ctxt) = create_context(self.display, &self.egl_version,
+                                                 &self.extensions, self.api, (2, 0), self.config_id,
+                                                 self.opengl.debug, self.opengl.robustness)
+                {
+                    ctxt
+                } else if let Ok(ctxt) = create_context(self.display, &self.egl_version,
+                                                        &self.extensions, self.api, (1, 0),
+                                                        self.config_id, self.opengl.debug,
+                                                        self.opengl.robustness)
+                {
+                    ctxt
+                } else {
+                    return Err(CreationError::OpenGlVersionNotSupported);
+                }
+
+            } else {
+                if let Ok(ctxt) = create_context(self.display, &self.egl_version,
+                                                 &self.extensions, self.api, (3, 2), self.config_id,
+                                                 self.opengl.debug, self.opengl.robustness)
+                {
+                    ctxt
+                } else if let Ok(ctxt) = create_context(self.display, &self.egl_version,
+                                                        &self.extensions, self.api, (3, 1),
+                                                        self.config_id, self.opengl.debug,
+                                                        self.opengl.robustness)
+                {
+                    ctxt
+                } else if let Ok(ctxt) = create_context(self.display, &self.egl_version,
+                                                        &self.extensions, self.api, (1, 0),
+                                                        self.config_id, self.opengl.debug,
+                                                        self.opengl.robustness)
+                {
+                    ctxt
+                } else {
+                    return Err(CreationError::OpenGlVersionNotSupported);
+                }
+            }
+        };
+
+        Ok(Context {
+            display: self.display,
+            context: context,
+            surface: Cell::new(surface),
+            api: self.api,
+            pixel_format: self.pixel_format,
+        })
+    }
+}
+
+unsafe fn choose_fbconfig(display: ffi::egl::types::EGLDisplay,
+                          egl_version: &(ffi::egl::types::EGLint, ffi::egl::types::EGLint),
+                          api: Api, version: Option<(u8, u8)>, reqs: &PixelFormatRequirements)
+                          -> Result<(ffi::egl::types::EGLConfig, PixelFormat), CreationError>
+{
+    let descriptor = {
+        let mut out: Vec<c_int> = Vec::with_capacity(37);
+
+        if egl_version >= &(1, 2) {
+            out.push(ffi::egl::COLOR_BUFFER_TYPE as c_int);
+            out.push(ffi::egl::RGB_BUFFER as c_int);
+        }
+
+        out.push(ffi::egl::SURFACE_TYPE as c_int);
+        // TODO: Some versions of Mesa report a BAD_ATTRIBUTE error
+        // if we ask for PBUFFER_BIT as well as WINDOW_BIT
+        out.push((ffi::egl::WINDOW_BIT) as c_int);
+
+        match (api, version) {
+            (Api::OpenGlEs, Some((3, _))) => {
+                if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); }
+                out.push(ffi::egl::RENDERABLE_TYPE as c_int);
+                out.push(ffi::egl::OPENGL_ES3_BIT as c_int);
+                out.push(ffi::egl::CONFORMANT as c_int);
+                out.push(ffi::egl::OPENGL_ES3_BIT as c_int);
+            },
+            (Api::OpenGlEs, Some((2, _))) => {
+                if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); }
+                out.push(ffi::egl::RENDERABLE_TYPE as c_int);
+                out.push(ffi::egl::OPENGL_ES2_BIT as c_int);
+                out.push(ffi::egl::CONFORMANT as c_int);
+                out.push(ffi::egl::OPENGL_ES2_BIT as c_int);
+            },
+            (Api::OpenGlEs, Some((1, _))) => {
+                if egl_version >= &(1, 3) {
+                    out.push(ffi::egl::RENDERABLE_TYPE as c_int);
+                    out.push(ffi::egl::OPENGL_ES_BIT as c_int);
+                    out.push(ffi::egl::CONFORMANT as c_int);
+                    out.push(ffi::egl::OPENGL_ES_BIT as c_int);
+                }
+            },
+            (Api::OpenGlEs, _) => unimplemented!(),
+            (Api::OpenGl, _) => {
+                if egl_version < &(1, 3) { return Err(CreationError::NoAvailablePixelFormat); }
+                out.push(ffi::egl::RENDERABLE_TYPE as c_int);
+                out.push(ffi::egl::OPENGL_BIT as c_int);
+                out.push(ffi::egl::CONFORMANT as c_int);
+                out.push(ffi::egl::OPENGL_BIT as c_int);
+            },
+            (_, _) => unimplemented!(),
+        };
+
+        if let Some(hardware_accelerated) = reqs.hardware_accelerated {
+            out.push(ffi::egl::CONFIG_CAVEAT as c_int);
+            out.push(if hardware_accelerated {
+                ffi::egl::NONE as c_int
+            } else {
+                ffi::egl::SLOW_CONFIG as c_int
+            });
+        }
+
+        if let Some(color) = reqs.color_bits {
+            out.push(ffi::egl::RED_SIZE as c_int);
+            out.push((color / 3) as c_int);
+            out.push(ffi::egl::GREEN_SIZE as c_int);
+            out.push((color / 3 + if color % 3 != 0 { 1 } else { 0 }) as c_int);
+            out.push(ffi::egl::BLUE_SIZE as c_int);
+            out.push((color / 3 + if color % 3 == 2 { 1 } else { 0 }) as c_int);
+        }
+
+        if let Some(alpha) = reqs.alpha_bits {
+            out.push(ffi::egl::ALPHA_SIZE as c_int);
+            out.push(alpha as c_int);
+        }
+
+        if let Some(depth) = reqs.depth_bits {
+            out.push(ffi::egl::DEPTH_SIZE as c_int);
+            out.push(depth as c_int);
+        }
+
+        if let Some(stencil) = reqs.stencil_bits {
+            out.push(ffi::egl::STENCIL_SIZE as c_int);
+            out.push(stencil as c_int);
+        }
+
+        if let Some(true) = reqs.double_buffer {
+            return Err(CreationError::NoAvailablePixelFormat);
+        }
+
+        if let Some(multisampling) = reqs.multisampling {
+            out.push(ffi::egl::SAMPLES as c_int);
+            out.push(multisampling as c_int);
+        }
+
+        if reqs.stereoscopy {
+            return Err(CreationError::NoAvailablePixelFormat);
+        }
+
+        // FIXME: srgb is not taken into account
+
+        match reqs.release_behavior {
+            ReleaseBehavior::Flush => (),
+            ReleaseBehavior::None => {
+                // TODO: with EGL you need to manually set the behavior
+                unimplemented!()
+            },
+        }
+
+        out.push(ffi::egl::NONE as c_int);
+        out
+    };
+
+    // calling `eglChooseConfig`
+    let mut config_id = mem::uninitialized();
+    let mut num_configs = mem::uninitialized();
+    if egl::ChooseConfig(display, descriptor.as_ptr(), &mut config_id, 1, &mut num_configs) == 0 {
+        return Err(CreationError::OsError(format!("eglChooseConfig failed")));
+    }
+    if num_configs == 0 {
+        return Err(CreationError::NoAvailablePixelFormat);
+    }
+
+    // analyzing each config
+    macro_rules! attrib {
+        ($display:expr, $config:expr, $attr:expr) => (
+            {
+                let mut value = mem::uninitialized();
+                let res = egl::GetConfigAttrib($display, $config,
+                                               $attr as ffi::egl::types::EGLint, &mut value);
+                if res == 0 {
+                    return Err(CreationError::OsError(format!("eglGetConfigAttrib failed")));
+                }
+                value
+            }
+        )
+    };
+
+    let desc = PixelFormat {
+        hardware_accelerated: attrib!(display, config_id, ffi::egl::CONFIG_CAVEAT)
+                                      != ffi::egl::SLOW_CONFIG as i32,
+        color_bits: attrib!(display, config_id, ffi::egl::RED_SIZE) as u8 +
+                    attrib!(display, config_id, ffi::egl::BLUE_SIZE) as u8 +
+                    attrib!(display, config_id, ffi::egl::GREEN_SIZE) as u8,
+        alpha_bits: attrib!(display, config_id, ffi::egl::ALPHA_SIZE) as u8,
+        depth_bits: attrib!(display, config_id, ffi::egl::DEPTH_SIZE) as u8,
+        stencil_bits: attrib!(display, config_id, ffi::egl::STENCIL_SIZE) as u8,
+        stereoscopy: false,
+        double_buffer: true,
+        multisampling: match attrib!(display, config_id, ffi::egl::SAMPLES) {
+            0 | 1 => None,
+            a => Some(a as u16),
+        },
+        srgb: false,        // TODO: use EGL_KHR_gl_colorspace to know that
+    };
+
+    Ok((config_id, desc))
+}
+
+unsafe fn create_context(display: ffi::egl::types::EGLDisplay,
+                         egl_version: &(ffi::egl::types::EGLint, ffi::egl::types::EGLint),
+                         extensions: &[String], api: Api, version: (u8, u8),
+                         config_id: ffi::egl::types::EGLConfig, gl_debug: bool,
+                         gl_robustness: Robustness)
+                         -> Result<ffi::egl::types::EGLContext, CreationError>
+{
+    let mut context_attributes = Vec::with_capacity(10);
+    let mut flags = 0;
+
+    if egl_version >= &(1, 5) || extensions.iter().find(|s| s == &"EGL_KHR_create_context")
+                                                  .is_some()
+    {
+        context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32);
+        context_attributes.push(version.0 as i32);
+        context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32);
+        context_attributes.push(version.1 as i32);
+
+        // handling robustness
+        let supports_robustness = egl_version >= &(1, 5) ||
+                                  extensions.iter()
+                                            .find(|s| s == &"EGL_EXT_create_context_robustness")
+                                            .is_some();
+
+        match gl_robustness {
+            Robustness::NotRobust => (),
+
+            Robustness::NoError => {
+                if extensions.iter().find(|s| s == &"EGL_KHR_create_context_no_error").is_some() {
+                    context_attributes.push(ffi::egl::CONTEXT_OPENGL_NO_ERROR_KHR as c_int);
+                    context_attributes.push(1);
+                }
+            },
+
+            Robustness::RobustNoResetNotification => {
+                if supports_robustness {
+                    context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY
+                                            as c_int);
+                    context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int);
+                    flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int;
+                } else {
+                    return Err(CreationError::RobustnessNotSupported);
+                }
+            },
+
+            Robustness::TryRobustNoResetNotification => {
+                if supports_robustness {
+                    context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY
+                                            as c_int);
+                    context_attributes.push(ffi::egl::NO_RESET_NOTIFICATION as c_int);
+                    flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int;
+                }
+            },
+
+            Robustness::RobustLoseContextOnReset => {
+                if supports_robustness {
+                    context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY
+                                            as c_int);
+                    context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int);
+                    flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int;
+                } else {
+                    return Err(CreationError::RobustnessNotSupported);
+                }
+            },
+
+            Robustness::TryRobustLoseContextOnReset => {
+                if supports_robustness {
+                    context_attributes.push(ffi::egl::CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY
+                                            as c_int);
+                    context_attributes.push(ffi::egl::LOSE_CONTEXT_ON_RESET as c_int);
+                    flags = flags | ffi::egl::CONTEXT_OPENGL_ROBUST_ACCESS as c_int;
+                }
+            },
+        }
+
+        if gl_debug {
+            if egl_version >= &(1, 5) {
+                context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32);
+                context_attributes.push(ffi::egl::TRUE as i32);
+            }
+
+            // TODO: using this flag sometimes generates an error
+            //       there was a change in the specs that added this flag, so it may not be
+            //       supported everywhere ; however it is not possible to know whether it is
+            //       supported or not
+            //flags = flags | ffi::egl::CONTEXT_OPENGL_DEBUG_BIT_KHR as i32;
+        }
+
+        context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32);
+        context_attributes.push(flags);
+
+    } else if egl_version >= &(1, 3) && api == Api::OpenGlEs {
+        // robustness is not supported
+        match gl_robustness {
+            Robustness::RobustNoResetNotification | Robustness::RobustLoseContextOnReset => {
+                return Err(CreationError::RobustnessNotSupported);
+            },
+            _ => ()
+        }
+
+        context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32);
+        context_attributes.push(version.0 as i32);
+    }
+
+    context_attributes.push(ffi::egl::NONE as i32);
+
+    let context = egl::CreateContext(display, config_id, ptr::null(),
+                                    context_attributes.as_ptr());
+
+    if context.is_null() {
+        match egl::GetError() as u32 {
+            ffi::egl::BAD_ATTRIBUTE => return Err(CreationError::OpenGlVersionNotSupported),
+            e => panic!("eglCreateContext failed: 0x{:x}", e),
+        }
+    }
+
+    Ok(context)
+}
+
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -22,28 +22,32 @@ extern crate euclid;
 extern crate font_loader;
 extern crate gleam;
 extern crate glutin;
 extern crate image;
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
+#[cfg(target_os = "windows")]
+extern crate mozangle;
 #[cfg(feature = "headless")]
 extern crate osmesa_sys;
 extern crate ron;
 #[macro_use]
 extern crate serde;
 extern crate serde_json;
 extern crate time;
 extern crate webrender;
 extern crate yaml_rust;
 
+mod angle;
 mod binary_frame_reader;
 mod blob;
+mod egl;
 mod json_frame_writer;
 mod parse_function;
 mod perf;
 mod png;
 mod premultiply;
 mod rawtest;
 mod reftest;
 mod ron_frame_writer;
@@ -154,116 +158,142 @@ impl HeadlessContext {
     #[cfg(not(feature = "headless"))]
     fn get_proc_address(_: &str) -> *const c_void {
         ptr::null() as *const _
     }
 }
 
 pub enum WindowWrapper {
     Window(glutin::GlWindow, Rc<gl::Gl>),
+    Angle(glutin::Window, angle::Context, Rc<gl::Gl>),
     Headless(HeadlessContext, Rc<gl::Gl>),
 }
 
 pub struct HeadlessEventIterater;
 
 impl WindowWrapper {
     fn swap_buffers(&self) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.swap_buffers().unwrap(),
+            WindowWrapper::Angle(_, ref context, _) => context.swap_buffers().unwrap(),
             WindowWrapper::Headless(_, _) => {}
         }
     }
 
     fn get_inner_size(&self) -> DeviceUintSize {
+        //HACK: `winit` needs to figure out its hidpi story...
+        #[cfg(target_os = "macos")]
+        fn inner_size(window: &glutin::Window) -> (u32, u32) {
+            let (w, h) = window.get_inner_size().unwrap();
+            let factor = window.hidpi_factor();
+            ((w as f32 * factor) as _, (h as f32 * factor) as _)
+        }
+        #[cfg(not(target_os = "macos"))]
+        fn inner_size(window: &glutin::Window) -> (u32, u32) {
+            window.get_inner_size().unwrap()
+        }
         let (w, h) = match *self {
-            //HACK: `winit` needs to figure out its hidpi story...
-            #[cfg(target_os = "macos")]
-            WindowWrapper::Window(ref window, _) => {
-                let (w, h) = window.get_inner_size().unwrap();
-                let factor = window.hidpi_factor();
-                ((w as f32 * factor) as _, (h as f32 * factor) as _)
-            },
-            #[cfg(not(target_os = "macos"))]
-            WindowWrapper::Window(ref window, _) => window.get_inner_size().unwrap(),
+            WindowWrapper::Window(ref window, _) => inner_size(window.window()),
+            WindowWrapper::Angle(ref window, ..) => inner_size(window),
             WindowWrapper::Headless(ref context, _) => (context.width, context.height),
         };
         DeviceUintSize::new(w, h)
     }
 
     fn hidpi_factor(&self) -> f32 {
         match *self {
             WindowWrapper::Window(ref window, _) => window.hidpi_factor(),
+            WindowWrapper::Angle(ref window, ..) => window.hidpi_factor(),
             WindowWrapper::Headless(_, _) => 1.0,
         }
     }
 
     fn resize(&mut self, size: DeviceUintSize) {
         match *self {
             WindowWrapper::Window(ref mut window, _) => window.set_inner_size(size.width, size.height),
+            WindowWrapper::Angle(ref mut window, ..) => window.set_inner_size(size.width, size.height),
             WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update
         }
     }
 
     fn set_title(&mut self, title: &str) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.set_title(title),
+            WindowWrapper::Angle(ref window, ..) => window.set_title(title),
             WindowWrapper::Headless(_, _) => (),
         }
     }
 
     pub fn gl(&self) -> &gl::Gl {
         match *self {
-            WindowWrapper::Window(_, ref gl) | WindowWrapper::Headless(_, ref gl) => &**gl,
+            WindowWrapper::Window(_, ref gl) |
+            WindowWrapper::Angle(_, _, ref gl) |
+            WindowWrapper::Headless(_, ref gl) => &**gl,
         }
     }
 
     pub fn clone_gl(&self) -> Rc<gl::Gl> {
         match *self {
-            WindowWrapper::Window(_, ref gl) | WindowWrapper::Headless(_, ref gl) => gl.clone(),
+            WindowWrapper::Window(_, ref gl) |
+            WindowWrapper::Angle(_, _, ref gl) |
+            WindowWrapper::Headless(_, ref gl) => gl.clone(),
         }
     }
 }
 
 fn make_window(
     size: DeviceUintSize,
     dp_ratio: Option<f32>,
     vsync: bool,
     events_loop: &Option<glutin::EventsLoop>,
+    angle: bool,
 ) -> WindowWrapper {
     let wrapper = match *events_loop {
         Some(ref events_loop) => {
             let context_builder = glutin::ContextBuilder::new()
                 .with_gl(glutin::GlRequest::GlThenGles {
                     opengl_version: (3, 2),
                     opengles_version: (3, 0),
                 })
                 .with_vsync(vsync);
             let window_builder = glutin::WindowBuilder::new()
                 .with_title("WRech")
                 .with_multitouch()
                 .with_dimensions(size.width, size.height);
-            let window = glutin::GlWindow::new(window_builder, context_builder, events_loop)
-                .unwrap();
 
-            unsafe {
-                window
-                    .make_current()
-                    .expect("unable to make context current!");
-            }
+            let init = |context: &glutin::GlContext| {
+                unsafe {
+                    context
+                        .make_current()
+                        .expect("unable to make context current!");
+                }
 
-            let gl = match window.get_api() {
-                glutin::Api::OpenGl => unsafe {
-                    gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
-                },
-                glutin::Api::OpenGlEs => unsafe {
-                    gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
-                },
-                glutin::Api::WebGl => unimplemented!(),
+                match context.get_api() {
+                    glutin::Api::OpenGl => unsafe {
+                        gl::GlFns::load_with(|symbol| context.get_proc_address(symbol) as *const _)
+                    },
+                    glutin::Api::OpenGlEs => unsafe {
+                        gl::GlesFns::load_with(|symbol| context.get_proc_address(symbol) as *const _)
+                    },
+                    glutin::Api::WebGl => unimplemented!(),
+                }
             };
-            WindowWrapper::Window(window, gl)
+
+            if angle {
+                let (window, context) = angle::Context::with_window(
+                    window_builder, context_builder, events_loop
+                ).unwrap();
+                let gl = init(&context);
+                WindowWrapper::Angle(window, context, gl)
+            } else {
+                let window = glutin::GlWindow::new(window_builder, context_builder, events_loop)
+                    .unwrap();
+                let gl = init(&window);
+                WindowWrapper::Window(window, gl)
+            }
         }
         None => {
             let gl = match gl::GlType::default() {
                 gl::GlType::Gl => unsafe {
                     gl::GlFns::load_with(|symbol| {
                         HeadlessContext::get_proc_address(symbol) as *const _
                     })
                 },
@@ -288,40 +318,50 @@ fn make_window(
         "hidpi factor: {} (native {})",
         dp_ratio,
         wrapper.hidpi_factor()
     );
 
     wrapper
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum NotifierEvent {
+    WakeUp,
+    ShutDown,
+}
+
 struct Notifier {
-    tx: Sender<()>,
+    tx: Sender<NotifierEvent>,
 }
 
 // setup a notifier so we can wait for frames to be finished
 impl RenderNotifier for Notifier {
     fn clone(&self) -> Box<RenderNotifier> {
         Box::new(Notifier {
             tx: self.tx.clone(),
         })
     }
 
     fn wake_up(&self) {
-        self.tx.send(()).unwrap();
+        self.tx.send(NotifierEvent::WakeUp).unwrap();
+    }
+
+    fn shut_down(&self) {
+        self.tx.send(NotifierEvent::ShutDown).unwrap();
     }
 
     fn new_document_ready(&self, _: DocumentId, scrolled: bool, _composite_needed: bool) {
         if !scrolled {
             self.wake_up();
         }
     }
 }
 
-fn create_notifier() -> (Box<RenderNotifier>, Receiver<()>) {
+fn create_notifier() -> (Box<RenderNotifier>, Receiver<NotifierEvent>) {
     let (tx, rx) = channel();
     (Box::new(Notifier { tx: tx }), rx)
 }
 
 fn main() {
     #[cfg(feature = "logging")]
     env_logger::init();
 
@@ -359,17 +399,19 @@ fn main() {
     let zoom_factor = args.value_of("zoom").map(|z| z.parse::<f32>().unwrap());
 
     let mut events_loop = if args.is_present("headless") {
         None
     } else {
         Some(glutin::EventsLoop::new())
     };
 
-    let mut window = make_window(size, dp_ratio, args.is_present("vsync"), &events_loop);
+    let mut window = make_window(
+        size, dp_ratio, args.is_present("vsync"), &events_loop, args.is_present("angle"),
+    );
     let dp_ratio = dp_ratio.unwrap_or(window.hidpi_factor());
     let dim = window.get_inner_size();
 
     let needs_frame_notifier = ["perf", "reftest", "png", "rawtest"]
         .iter()
         .any(|s| args.subcommand_matches(s).is_some());
     let (notifier, rx) = if needs_frame_notifier {
         let (notifier, rx) = create_notifier();
@@ -415,27 +457,29 @@ fn main() {
         let dim = window.get_inner_size();
         let base_manifest = Path::new("reftests/reftest.list");
         let specific_reftest = subargs.value_of("REFTEST").map(|x| Path::new(x));
         let mut reftest_options = ReftestOptions::default();
         if let Some(allow_max_diff) = subargs.value_of("fuzz_tolerance") {
             reftest_options.allow_max_difference = allow_max_diff.parse().unwrap_or(1);
             reftest_options.allow_num_differences = dim.width as usize * dim.height as usize;
         }
-        let num_failures = ReftestHarness::new(&mut wrench, &mut window, rx.unwrap())
+        let rx = rx.unwrap();
+        let num_failures = ReftestHarness::new(&mut wrench, &mut window, &rx)
             .run(base_manifest, specific_reftest, &reftest_options);
-        wrench.renderer.deinit();
+        wrench.shut_down(rx);
         // exit with an error code to fail on CI
         process::exit(num_failures as _);
     } else if let Some(_) = args.subcommand_matches("rawtest") {
+        let rx = rx.unwrap();
         {
-            let harness = RawtestHarness::new(&mut wrench, &mut window, rx.unwrap());
+            let harness = RawtestHarness::new(&mut wrench, &mut window, &rx);
             harness.run();
         }
-        wrench.renderer.deinit();
+        wrench.shut_down(rx);
         return;
     } else if let Some(subargs) = args.subcommand_matches("perf") {
         // Perf mode wants to benchmark the total cost of drawing
         // a new displaty list each frame.
         wrench.rebuild_display_lists = true;
         let harness = PerfHarness::new(&mut wrench, &mut window, rx.unwrap());
         let base_manifest = Path::new("benchmarks/benchmarks.list");
         let filename = subargs.value_of("filename").unwrap();
--- a/gfx/wrench/src/perf.rs
+++ b/gfx/wrench/src/perf.rs
@@ -1,12 +1,13 @@
 /* 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 NotifierEvent;
 use WindowWrapper;
 use serde_json;
 use std::collections::{HashMap, HashSet};
 use std::fs::File;
 use std::io::{BufRead, BufReader};
 use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 use std::sync::mpsc::Receiver;
@@ -118,21 +119,21 @@ impl Profile {
 
         (hash_set, hash_map)
     }
 }
 
 pub struct PerfHarness<'a> {
     wrench: &'a mut Wrench,
     window: &'a mut WindowWrapper,
-    rx: Receiver<()>,
+    rx: Receiver<NotifierEvent>,
 }
 
 impl<'a> PerfHarness<'a> {
-    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
+    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<NotifierEvent>) -> Self {
         PerfHarness { wrench, window, rx }
     }
 
     pub fn run(mut self, base_manifest: &Path, filename: &str) {
         let manifest = BenchmarkManifest::new(base_manifest);
 
         let mut profile = Profile::new();
 
--- a/gfx/wrench/src/png.rs
+++ b/gfx/wrench/src/png.rs
@@ -1,13 +1,13 @@
 /* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
 use image::png::PNGEncoder;
 use image::{self, ColorType, GenericImage};
 use std::fs::File;
 use std::path::Path;
 use std::sync::mpsc::Receiver;
 use webrender::api::*;
 use wrench::{Wrench, WrenchThing};
 use yaml_frame_reader::YamlFrameReader;
@@ -72,17 +72,17 @@ pub fn save_flipped<P: Clone + AsRef<Pat
     })
 }
 
 pub fn png(
     wrench: &mut Wrench,
     surface: ReadSurface,
     window: &mut WindowWrapper,
     mut reader: YamlFrameReader,
-    rx: Receiver<()>,
+    rx: Receiver<NotifierEvent>,
 ) {
     reader.do_frame(wrench);
 
     // wait for the frame
     rx.recv().unwrap();
     wrench.render();
 
     let (device_size, data, settings) = match surface {
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -1,41 +1,41 @@
 /* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
 use blob;
 use euclid::{TypedRect, TypedSize2D, TypedPoint2D};
 use std::sync::Arc;
 use std::sync::atomic::{AtomicIsize, Ordering};
 use std::sync::mpsc::Receiver;
 use webrender::api::*;
 use wrench::Wrench;
 
 pub struct RawtestHarness<'a> {
     wrench: &'a mut Wrench,
-    rx: Receiver<()>,
+    rx: &'a Receiver<NotifierEvent>,
     window: &'a mut WindowWrapper,
 }
 
 fn point<T: Copy, U>(x: T, y: T) -> TypedPoint2D<T, U> {
     TypedPoint2D::new(x, y)
 }
 
 fn size<T: Copy, U>(x: T, y: T) -> TypedSize2D<T, U> {
     TypedSize2D::new(x, y)
 }
 
 fn rect<T: Copy, U>(x: T, y: T, width: T, height: T) -> TypedRect<T, U> {
     TypedRect::new(point(x, y), size(width, height))
 }
 
 impl<'a> RawtestHarness<'a> {
-    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
+    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self {
         RawtestHarness {
             wrench,
             rx,
             window,
         }
     }
 
     pub fn run(mut self) {
--- a/gfx/wrench/src/reftest.rs
+++ b/gfx/wrench/src/reftest.rs
@@ -1,13 +1,13 @@
 /* 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 WindowWrapper;
+use {WindowWrapper, NotifierEvent};
 use base64;
 use image::load as load_piston_image;
 use image::png::PNGEncoder;
 use image::{ColorType, ImageFormat};
 use parse_function::parse_function;
 use png::save_flipped;
 use std::cmp;
 use std::fmt::{Display, Error, Formatter};
@@ -287,20 +287,20 @@ impl ReftestManifest {
             })
             .collect()
     }
 }
 
 pub struct ReftestHarness<'a> {
     wrench: &'a mut Wrench,
     window: &'a mut WindowWrapper,
-    rx: Receiver<()>,
+    rx: &'a Receiver<NotifierEvent>,
 }
 impl<'a> ReftestHarness<'a> {
-    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
+    pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self {
         ReftestHarness { wrench, window, rx }
     }
 
     pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize {
         let manifest = ReftestManifest::new(base_manifest, options);
         let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));
 
         let mut total_passing = 0;
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -11,22 +11,23 @@ use dwrote;
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use font_loader::system_fonts;
 use glutin::EventsLoopProxy;
 use json_frame_writer::JsonFrameWriter;
 use ron_frame_writer::RonFrameWriter;
 use std::collections::HashMap;
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex};
+use std::sync::mpsc::Receiver;
 use time;
 use webrender;
 use webrender::api::*;
 use webrender::{DebugFlags, RendererStats};
 use yaml_frame_writer::YamlFrameWriterReceiver;
-use {WindowWrapper, BLACK_COLOR, WHITE_COLOR};
+use {WindowWrapper, NotifierEvent, BLACK_COLOR, WHITE_COLOR};
 
 // TODO(gw): This descriptor matches what we currently support for fonts
 //           but is quite a mess. We should at least document and
 //           use better types for things like the style and stretch.
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum FontDescriptor {
     Path { path: PathBuf, font_index: u32 },
     Family { name: String },
@@ -585,9 +586,23 @@ impl Wrench {
             let x = self.device_pixel_ratio * (15.0 + co.1);
             let mut y = self.device_pixel_ratio * (15.0 + co.1 + dr.line_height());
             for ref line in &help_lines {
                 dr.add_text(x, y, line, co.0.into());
                 y += self.device_pixel_ratio * dr.line_height();
             }
         }
     }
+
+    pub fn shut_down(self, rx: Receiver<NotifierEvent>) {
+        self.api.shut_down();
+
+        loop {
+            match rx.recv() {
+                Ok(NotifierEvent::ShutDown) => { break; }
+                Ok(_) => {}
+                Err(e) => { panic!("Did not shut down properly: {:?}.", e); }
+            }
+        }
+
+        self.renderer.deinit();
+    }
 }
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -594,23 +594,20 @@ impl YamlFrameReader {
         } else {
             ExtendMode::Clamp
         };
 
         dl.create_gradient(start, end, stops, extend_mode)
     }
 
     fn to_radial_gradient(&mut self, dl: &mut DisplayListBuilder, item: &Yaml) -> RadialGradient {
-        if item["start-center"].is_badvalue() {
-            let center = item["center"]
-                .as_point()
-                .expect("radial gradient must have start center");
-            let radius = item["radius"]
-                .as_size()
-                .expect("radial gradient must have start radius");
+        let center = item["center"].as_point().expect("radial gradient must have center");
+        let radius = item["radius"].as_size().expect("radial gradient must have a radius");
+
+        if item["start-radius"].is_badvalue() {
             let stops = item["stops"]
                 .as_vec()
                 .expect("radial gradient must have stops")
                 .chunks(2)
                 .map(|chunk| {
                     GradientStop {
                         offset: chunk[0]
                             .as_force_f32()
@@ -624,29 +621,22 @@ impl YamlFrameReader {
             let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
                 ExtendMode::Repeat
             } else {
                 ExtendMode::Clamp
             };
 
             dl.create_radial_gradient(center, radius, stops, extend_mode)
         } else {
-            let start_center = item["start-center"]
-                .as_point()
-                .expect("radial gradient must have start center");
             let start_radius = item["start-radius"]
                 .as_force_f32()
                 .expect("radial gradient must have start radius");
-            let end_center = item["end-center"]
-                .as_point()
-                .expect("radial gradient must have end center");
             let end_radius = item["end-radius"]
                 .as_force_f32()
                 .expect("radial gradient must have end radius");
-            let ratio_xy = item["ratio-xy"].as_force_f32().unwrap_or(1.0);
             let stops = item["stops"]
                 .as_vec()
                 .expect("radial gradient must have stops")
                 .chunks(2)
                 .map(|chunk| {
                     GradientStop {
                         offset: chunk[0]
                             .as_force_f32()
@@ -659,21 +649,20 @@ impl YamlFrameReader {
                 .collect::<Vec<_>>();
             let extend_mode = if item["repeat"].as_bool().unwrap_or(false) {
                 ExtendMode::Repeat
             } else {
                 ExtendMode::Clamp
             };
 
             dl.create_complex_radial_gradient(
-                start_center,
+                center,
+                radius,
                 start_radius,
-                end_center,
                 end_radius,
-                ratio_xy,
                 stops,
                 extend_mode,
             )
         }
     }
 
     fn handle_rect(
         &mut self,
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -900,21 +900,20 @@ impl YamlFrameWriter {
                             let outset: Vec<f32> = vec![
                                 details.outset.top,
                                 details.outset.right,
                                 details.outset.bottom,
                                 details.outset.left,
                             ];
                             yaml_node(&mut v, "width", f32_vec_yaml(&widths, true));
                             str_node(&mut v, "border-type", "radial-gradient");
-                            point_node(&mut v, "start-center", &details.gradient.start_center);
+                            point_node(&mut v, "center", &details.gradient.center);
+                            size_node(&mut v, "radius", &details.gradient.radius);
                             f32_node(&mut v, "start-radius", details.gradient.start_radius);
-                            point_node(&mut v, "end-center", &details.gradient.end_center);
                             f32_node(&mut v, "end-radius", details.gradient.end_radius);
-                            f32_node(&mut v, "ratio-xy", details.gradient.ratio_xy);
                             let mut stops = vec![];
                             for stop in display_list.get(base.gradient_stops()) {
                                 stops.push(Yaml::Real(stop.offset.to_string()));
                                 stops.push(Yaml::String(color_to_string(stop.color)));
                             }
                             yaml_node(&mut v, "stops", Yaml::Array(stops));
                             bool_node(
                                 &mut v,
@@ -956,21 +955,20 @@ impl YamlFrameWriter {
                     bool_node(
                         &mut v,
                         "repeat",
                         item.gradient.extend_mode == ExtendMode::Repeat,
                     );
                 }
                 RadialGradient(item) => {
                     str_node(&mut v, "type", "radial-gradient");
-                    point_node(&mut v, "start-center", &item.gradient.start_center);
+                    point_node(&mut v, "center", &item.gradient.center);
+                    size_node(&mut v, "center", &item.gradient.radius);
                     f32_node(&mut v, "start-radius", item.gradient.start_radius);
-                    point_node(&mut v, "end-center", &item.gradient.end_center);
                     f32_node(&mut v, "end-radius", item.gradient.end_radius);
-                    f32_node(&mut v, "ratio-xy", item.gradient.ratio_xy);
                     size_node(&mut v, "tile-size", &item.tile_size);
                     size_node(&mut v, "tile-spacing", &item.tile_spacing);
                     let mut stops = vec![];
                     for stop in display_list.get(base.gradient_stops()) {
                         stops.push(Yaml::Real(stop.offset.to_string()));
                         stops.push(Yaml::String(color_to_string(stop.color)));
                     }
                     yaml_node(&mut v, "stops", Yaml::Array(stops));