Bug 1485691. Update webrender to commit 93997662842b6d8bafbdb3dde79009c930db66ca
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 23 Aug 2018 15:47:39 -0400
changeset 433111 4a57ba156ecf7f94641e860bb42e7113c54d788e
parent 433110 010c8880c277c04fb99b5f6d135c99f36e6c3734
child 433112 9ffc581e5e174d892acb22cc2b3f81c9b2826846
push id106975
push userjmuizelaar@mozilla.com
push dateThu, 23 Aug 2018 19:48:49 +0000
treeherdermozilla-inbound@b8573b6390b1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1485691
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1485691. Update webrender to commit 93997662842b6d8bafbdb3dde79009c930db66ca
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/glyph_rasterizer/mod.rs
gfx/webrender/src/prim_store.rs
gfx/webrender_bindings/revision.txt
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -2,24 +2,23 @@
  * 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::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, LayoutTransform};
 use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
-use clip_scroll_tree::{CoordinateSystemId, SpatialNodeIndex};
+use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::BoxShadowStretchMode;
 use prim_store::{BrushClipMaskKind, ClipData, ImageMaskData};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
-use spatial_node::SpatialNode;
 use std::u32;
 use util::{extract_inner_rect_safe, pack_as_float, recycle_vec, MatrixHelpers};
 
 /*
 
  Module Overview
 
  There are a number of data structures involved in the clip module:
@@ -444,28 +443,29 @@ impl ClipStore {
     // The main interface other code uses. Given a local primitive, positioning
     // information, and a clip chain id, build an optimized clip chain instance.
     pub fn build_clip_chain_instance(
         &mut self,
         clip_chain_id: ClipChainId,
         local_prim_rect: LayoutRect,
         local_prim_clip_rect: LayoutRect,
         spatial_node_index: SpatialNodeIndex,
-        spatial_nodes: &[SpatialNode],
+        clip_scroll_tree: &ClipScrollTree,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
     ) -> Option<ClipChainInstance> {
         // Trivial check to see if the primitive is clipped out by the
         // local clip rect of the primitive itself.
         let mut local_bounding_rect = match local_prim_rect.intersection(&local_prim_clip_rect) {
             Some(rect) => rect,
             None => return None,
         };
         let mut current_local_clip_rect = local_prim_clip_rect;
+        let spatial_nodes = &clip_scroll_tree.spatial_nodes;
 
         // Walk the clip chain to build local rects, and collect the
         // smallest possible local clip area.
 
         self.clip_node_info.clear();
         let ref_spatial_node = &spatial_nodes[spatial_node_index.0];
         let mut current_clip_chain_id = clip_chain_id;
 
@@ -484,36 +484,26 @@ impl ClipStore {
                 // systems of the primitive and clip node.
                 let conversion = if spatial_node_index == clip_node.spatial_node_index {
                     Some(ClipSpaceConversion::Local)
                 } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
                     let offset = clip_spatial_node.coordinate_system_relative_offset -
                                  ref_spatial_node.coordinate_system_relative_offset;
                     Some(ClipSpaceConversion::Offset(offset))
                 } else {
-                    // TODO(gw): We still have issues with clip nodes and primitives where
-                    //           there is a perspective transform. We intend to fix these
-                    //           cases as a follow up.
-                    let relative_transform = ref_spatial_node
-                        .world_content_transform
-                        .to_transform()
-                        .inverse()
-                        .map(|inv| {
-                            inv.pre_mul(&clip_spatial_node.world_content_transform.to_transform())
-                        });
-                    let inv_relative_transform = relative_transform
-                        .and_then(|rt| rt.inverse());
-                    match (relative_transform, inv_relative_transform) {
-                        (Some(relative_transform), Some(inv_relative_transform)) => {
-                            Some(ClipSpaceConversion::Transform(relative_transform, inv_relative_transform))
-                        }
-                        _ => {
-                            None
-                        }
-                    }
+                    let xf = clip_scroll_tree.get_relative_transform(
+                        clip_node.spatial_node_index,
+                        spatial_node_index,
+                    );
+
+                    xf.and_then(|xf| {
+                        xf.inverse().map(|inv| {
+                            ClipSpaceConversion::Transform(xf, inv)
+                        })
+                    })
                 };
 
                 // If we can convert spaces, try to reduce the size of the region
                 // requested, and cache the conversion information for the next step.
                 if let Some(conversion) = conversion {
                     if let Some(clip_rect) = clip_node.item.get_local_clip_rect() {
                         let clip_rect = conversion.transform_to_prim_space(&clip_rect);
                         if let Some(clip_rect) = clip_rect {
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -5,16 +5,17 @@
 use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D, LayoutVector3D};
 use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
 use clip::{ClipStore};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use scene::SceneProperties;
+use smallvec::SmallVec;
 use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
 use util::LayoutToWorldFastTransform;
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
@@ -107,45 +108,64 @@ impl ClipScrollTree {
     }
 
     /// Calculate the relative transform from `ref_node_index`
     /// to `target_node_index`. It's assumed that `ref_node_index`
     /// is a parent of `target_node_index`. This method will
     /// panic if that invariant isn't true!
     pub fn get_relative_transform(
         &self,
-        ref_node_index: SpatialNodeIndex,
-        target_node_index: SpatialNodeIndex,
-    ) -> LayoutTransform {
-        let ref_node = &self.spatial_nodes[ref_node_index.0];
-        let target_node = &self.spatial_nodes[target_node_index.0];
+        from_node_index: SpatialNodeIndex,
+        to_node_index: SpatialNodeIndex,
+    ) -> Option<LayoutTransform> {
+        let from_node = &self.spatial_nodes[from_node_index.0];
+        let to_node = &self.spatial_nodes[to_node_index.0];
+
+        let (child, parent, inverse) = if from_node_index.0 > to_node_index.0 {
+            (from_node, to_node, false)
+        } else {
+            (to_node, from_node, true)
+        };
 
-        let mut offset = LayoutVector3D::new(
-            target_node.coordinate_system_relative_offset.x,
-            target_node.coordinate_system_relative_offset.y,
+        let mut coordinate_system_id = child.coordinate_system_id;
+        let mut nodes: SmallVec<[_; 16]> = SmallVec::new();
+
+        while coordinate_system_id != parent.coordinate_system_id {
+            nodes.push(coordinate_system_id);
+            let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
+            coordinate_system_id = coord_system.parent.expect("invalid parent!");
+        }
+
+        nodes.reverse();
+
+        let mut transform = LayoutTransform::create_translation(
+            -parent.coordinate_system_relative_offset.x,
+            -parent.coordinate_system_relative_offset.y,
             0.0,
         );
-        let mut transform = LayoutTransform::identity();
 
-        // Walk up the tree of coordinate systems, accumulating each
-        // relative transform.
-        let mut current_coordinate_system_id = target_node.coordinate_system_id;
-        while current_coordinate_system_id != ref_node.coordinate_system_id {
-            let coord_system = &self.coord_systems[current_coordinate_system_id.0 as usize];
-
-            let relative_transform = coord_system
-                .transform
-                .post_translate(offset);
-            transform = transform.pre_mul(&relative_transform);
-
-            offset = coord_system.offset;
-            current_coordinate_system_id = coord_system.parent.expect("invalid parent!");
+        for node in nodes {
+            let coord_system = &self.coord_systems[node.0 as usize];
+            transform = transform.pre_translate(coord_system.offset)
+                                 .pre_mul(&coord_system.transform);
         }
 
-        transform
+        let transform = transform.post_translate(
+            LayoutVector3D::new(
+                child.coordinate_system_relative_offset.x,
+                child.coordinate_system_relative_offset.y,
+                0.0,
+            )
+        );
+
+        if inverse {
+            transform.inverse()
+        } else {
+            Some(transform)
+        }
     }
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```spatial_nodes``` being empty.
     pub fn root_reference_frame_index(&self) -> SpatialNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.spatial_nodes.is_empty());
         ROOT_REFERENCE_FRAME_INDEX
@@ -437,8 +457,224 @@ impl ClipScrollTree {
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.spatial_nodes.is_empty() {
             self.print_node(self.root_reference_frame_index(), pt, clip_store);
         }
     }
 }
+
+#[cfg(test)]
+fn add_reference_frame(
+    cst: &mut ClipScrollTree,
+    parent: Option<SpatialNodeIndex>,
+    transform: LayoutTransform,
+    origin_in_parent_reference_frame: LayoutVector2D,
+) -> SpatialNodeIndex {
+    cst.add_reference_frame(
+        parent,
+        Some(PropertyBinding::Value(transform)),
+        None,
+        origin_in_parent_reference_frame,
+        PipelineId::dummy(),
+    )
+}
+
+#[cfg(test)]
+fn test_pt(
+    px: f32,
+    py: f32,
+    cst: &ClipScrollTree,
+    from: SpatialNodeIndex,
+    to: SpatialNodeIndex,
+    expected_x: f32,
+    expected_y: f32,
+) {
+    use euclid::approxeq::ApproxEq;
+    const EPSILON: f32 = 0.0001;
+
+    let p = LayoutPoint::new(px, py);
+    let m = cst.get_relative_transform(from, to).unwrap();
+    let pt = m.transform_point2d(&p).unwrap();
+    assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) &&
+            pt.y.approx_eq_eps(&expected_y, &EPSILON),
+            "p: {:?} -> {:?}\nm={:?}",
+            p, pt, m,
+            );
+}
+
+#[test]
+fn test_cst_simple_translation() {
+    // Basic translations only
+
+    let mut cst = ClipScrollTree::new();
+
+    let root = add_reference_frame(
+        &mut cst,
+        None,
+        LayoutTransform::identity(),
+        LayoutVector2D::zero(),
+    );
+
+    let child1 = add_reference_frame(
+        &mut cst,
+        Some(root),
+        LayoutTransform::create_translation(100.0, 0.0, 0.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child2 = add_reference_frame(
+        &mut cst,
+        Some(child1),
+        LayoutTransform::create_translation(0.0, 50.0, 0.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child3 = add_reference_frame(
+        &mut cst,
+        Some(child2),
+        LayoutTransform::create_translation(200.0, 200.0, 0.0),
+        LayoutVector2D::zero(),
+    );
+
+    cst.update_tree(WorldPoint::zero(), &SceneProperties::new());
+
+    test_pt(100.0, 100.0, &cst, child1, root, 200.0, 100.0);
+    test_pt(100.0, 100.0, &cst, root, child1, 0.0, 100.0);
+    test_pt(100.0, 100.0, &cst, child2, root, 200.0, 150.0);
+    test_pt(100.0, 100.0, &cst, root, child2, 0.0, 50.0);
+    test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 150.0);
+    test_pt(100.0, 100.0, &cst, child1, child2, 100.0, 50.0);
+    test_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0);
+}
+
+#[test]
+fn test_cst_simple_scale() {
+    // Basic scale only
+
+    let mut cst = ClipScrollTree::new();
+
+    let root = add_reference_frame(
+        &mut cst,
+        None,
+        LayoutTransform::identity(),
+        LayoutVector2D::zero(),
+    );
+
+    let child1 = add_reference_frame(
+        &mut cst,
+        Some(root),
+        LayoutTransform::create_scale(4.0, 1.0, 1.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child2 = add_reference_frame(
+        &mut cst,
+        Some(child1),
+        LayoutTransform::create_scale(1.0, 2.0, 1.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child3 = add_reference_frame(
+        &mut cst,
+        Some(child2),
+        LayoutTransform::create_scale(2.0, 2.0, 1.0),
+        LayoutVector2D::zero(),
+    );
+
+    cst.update_tree(WorldPoint::zero(), &SceneProperties::new());
+
+    test_pt(100.0, 100.0, &cst, child1, root, 400.0, 100.0);
+    test_pt(100.0, 100.0, &cst, root, child1, 25.0, 100.0);
+    test_pt(100.0, 100.0, &cst, child2, root, 400.0, 200.0);
+    test_pt(100.0, 100.0, &cst, root, child2, 25.0, 50.0);
+    test_pt(100.0, 100.0, &cst, child3, root, 800.0, 400.0);
+    test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 200.0);
+    test_pt(100.0, 100.0, &cst, child1, child2, 100.0, 50.0);
+    test_pt(100.0, 100.0, &cst, child3, child1, 200.0, 400.0);
+    test_pt(100.0, 100.0, &cst, child1, child3, 50.0, 25.0);
+}
+
+#[test]
+fn test_cst_scale_translation() {
+    // Scale + translation
+
+    let mut cst = ClipScrollTree::new();
+
+    let root = add_reference_frame(
+        &mut cst,
+        None,
+        LayoutTransform::identity(),
+        LayoutVector2D::zero(),
+    );
+
+    let child1 = add_reference_frame(
+        &mut cst,
+        Some(root),
+        LayoutTransform::create_translation(100.0, 50.0, 0.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child2 = add_reference_frame(
+        &mut cst,
+        Some(child1),
+        LayoutTransform::create_scale(2.0, 4.0, 1.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child3 = add_reference_frame(
+        &mut cst,
+        Some(child2),
+        LayoutTransform::create_translation(200.0, -100.0, 0.0),
+        LayoutVector2D::zero(),
+    );
+
+    let child4 = add_reference_frame(
+        &mut cst,
+        Some(child3),
+        LayoutTransform::create_scale(3.0, 2.0, 1.0),
+        LayoutVector2D::zero(),
+    );
+
+    cst.update_tree(WorldPoint::zero(), &SceneProperties::new());
+
+    test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0);
+    test_pt(100.0, 100.0, &cst, child2, root, 300.0, 450.0);
+    test_pt(100.0, 100.0, &cst, root, child1, 0.0, 50.0);
+    test_pt(100.0, 100.0, &cst, root, child2, 0.0, 12.5);
+    test_pt(100.0, 100.0, &cst, child4, root, 1100.0, 450.0);
+    test_pt(1100.0, 450.0, &cst, root, child4, 100.0, 100.0);
+
+    test_pt(0.0, 0.0, &cst, child4, child1, 400.0, -400.0);
+    test_pt(100.0, 100.0, &cst, child4, child1, 1000.0, 400.0);
+    test_pt(100.0, 100.0, &cst, child2, child1, 200.0, 400.0);
+    test_pt(200.0, 400.0, &cst, child1, child2, 100.0, 100.0);
+
+    test_pt(100.0, 100.0, &cst, child3, child1, 400.0, 300.0);
+    test_pt(400.0, 300.0, &cst, child1, child3, 100.0, 100.0);
+}
+
+#[test]
+fn test_cst_translation_rotate() {
+    // Rotation + translation
+    use euclid::Angle;
+
+    let mut cst = ClipScrollTree::new();
+
+    let root = add_reference_frame(
+        &mut cst,
+        None,
+        LayoutTransform::identity(),
+        LayoutVector2D::zero(),
+    );
+
+    let child1 = add_reference_frame(
+        &mut cst,
+        Some(root),
+        LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::degrees(90.0)),
+        LayoutVector2D::zero(),
+    );
+
+    cst.update_tree(WorldPoint::zero(), &SceneProperties::new());
+
+    test_pt(100.0, 0.0, &cst, child1, root, 0.0, -100.0);
+}
--- a/gfx/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -10,17 +10,17 @@ use api::{LayoutPoint, LayoutToWorldTran
 use app_units::Au;
 use euclid::approxeq::ApproxEq;
 use internal_types::ResourceCacheError;
 use platform::font::FontContext;
 use rayon::ThreadPool;
 use std::cmp;
 use std::hash::{Hash, Hasher};
 use std::mem;
-use std::sync::{Arc, Mutex, MutexGuard};
+use std::sync::{Arc, Condvar, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
 
 #[cfg(feature = "pathfinder")]
 mod pathfinder;
 #[cfg(feature = "pathfinder")]
 use self::pathfinder::create_pathfinder_font_context;
 #[cfg(feature = "pathfinder")]
 pub use self::pathfinder::{ThreadSafePathfinderFontContext, NativeFontHandleWrapper};
@@ -446,16 +446,18 @@ pub struct FontContexts {
     // This worker should be accessed by threads that don't belong to the thread pool
     // (in theory that's only the render backend thread so no contention expected either).
     shared_context: Mutex<FontContext>,
     #[cfg(feature = "pathfinder")]
     pathfinder_context: Box<ThreadSafePathfinderFontContext>,
     // Stored here as a convenience to get the current thread index.
     #[allow(dead_code)]
     workers: Arc<ThreadPool>,
+    locked_mutex: Mutex<bool>,
+    locked_cond: Condvar,
 }
 
 impl FontContexts {
 
     /// Get access to any particular font context.
     ///
     /// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts
     /// for font contexts associated to the thread pool, and None for the shared
@@ -473,16 +475,56 @@ impl FontContexts {
     }
 
     // number of contexts associated to workers
     pub fn num_worker_contexts(&self) -> usize {
         self.worker_contexts.len()
     }
 }
 
+pub trait ForEach<T> {
+    fn for_each<F: Fn(MutexGuard<T>) + Send + 'static>(&self, f: F);
+}
+
+impl ForEach<FontContext> for Arc<FontContexts> {
+    fn for_each<F: Fn(MutexGuard<FontContext>) + Send + 'static>(&self, f: F) {
+        // Reset the locked condition.
+        let mut locked = self.locked_mutex.lock().unwrap();
+        *locked = false;
+
+        // Arc that can be safely moved into a spawn closure.
+        let font_contexts = self.clone();
+        // Spawn a new thread on which to run the for-each off the main thread.
+        self.workers.spawn(move || {
+            // Lock the shared and worker contexts up front.
+            let mut locks = Vec::with_capacity(font_contexts.num_worker_contexts() + 1);
+            locks.push(font_contexts.lock_shared_context());
+            for i in 0 .. font_contexts.num_worker_contexts() {
+                locks.push(font_contexts.lock_context(Some(i)));
+            }
+
+            // Signal the locked condition now that all contexts are locked.
+            *font_contexts.locked_mutex.lock().unwrap() = true;
+            font_contexts.locked_cond.notify_all();
+
+            // Now that everything is locked, proceed to processing each locked context.
+            for context in locks {
+                f(context);
+            }
+        });
+
+        // Wait for locked condition before resuming. Safe to proceed thereafter
+        // since any other thread that needs to use a FontContext will try to lock
+        // it first.
+        while !*locked {
+            locked = self.locked_cond.wait(locked).unwrap();
+        }
+    }
+}
+
 pub struct GlyphRasterizer {
     #[allow(dead_code)]
     workers: Arc<ThreadPool>,
     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
@@ -522,51 +564,38 @@ impl GlyphRasterizer {
         }
 
         let font_context = FontContexts {
                 worker_contexts: contexts,
                 shared_context: Mutex::new(shared_context),
                 #[cfg(feature = "pathfinder")]
                 pathfinder_context: create_pathfinder_font_context()?,
                 workers: Arc::clone(&workers),
+                locked_mutex: Mutex::new(false),
+                locked_cond: Condvar::new(),
         };
 
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(font_context),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
             next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
-        let font_contexts = Arc::clone(&self.font_contexts);
-        // It's important to synchronously add the font for the shared context because
-        // we use it to check that fonts have been properly added when requesting glyphs.
-        font_contexts
-            .lock_shared_context()
-            .add_font(&font_key, &template);
-
-        // TODO: this locks each font context while adding the font data, probably not a big deal,
-        // but if there is contention on this lock we could easily have a queue of per-context
-        // operations to add and delete fonts, and have these queues lazily processed by each worker
-        // before rendering a glyph.
-        // We can also move this into a worker to free up some cycles in the calling (render backend)
-        // thread.
-        for i in 0 .. font_contexts.num_worker_contexts() {
-            font_contexts
-                .lock_context(Some(i))
-                .add_font(&font_key, &template);
-        }
-
         #[cfg(feature = "pathfinder")]
         self.add_font_to_pathfinder(&font_key, &template);
+
+        self.font_contexts.for_each(move |mut context| {
+            context.add_font(&font_key, &template);
+        });
     }
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
@@ -594,28 +623,20 @@ impl GlyphRasterizer {
             .get_glyph_index(font_key, ch)
     }
 
     fn remove_dead_fonts(&mut self) {
         if self.fonts_to_remove.is_empty() {
             return
         }
 
-        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 || {
+        self.font_contexts.for_each(move |mut context| {
             for font_key in &fonts_to_remove {
-                font_contexts.lock_shared_context().delete_font(font_key);
-            }
-            for i in 0 .. font_contexts.num_worker_contexts() {
-                let mut context = font_contexts.lock_context(Some(i));
-                for font_key in &fonts_to_remove {
-                    context.delete_font(font_key);
-                }
+                context.delete_font(font_key);
             }
         });
     }
 
     #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
         self.pending_glyphs = 0;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -157,19 +157,19 @@ impl LocalRectBuilder {
             self.kind = if self.ref_spatial_node_index == target_node_index {
                 CoordinateSpaceMapping::Local
             } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
                 let offset = target_spatial_node.coordinate_system_relative_offset -
                              ref_spatial_node.coordinate_system_relative_offset;
                 CoordinateSpaceMapping::Offset(offset)
             } else {
                 let transform = clip_scroll_tree.get_relative_transform(
+                    target_node_index,
                     self.ref_spatial_node_index,
-                    target_node_index,
-                );
+                ).expect("bug: should have already been culled");
                 CoordinateSpaceMapping::Transform(transform)
             };
         }
     }
 
     pub fn accumulate(&mut self, rect: &LayoutRect) {
         match self.kind {
             CoordinateSpaceMapping::Local => {
@@ -1615,17 +1615,17 @@ impl PrimitiveStore {
 
         let clip_chain = frame_state
             .clip_store
             .build_clip_chain_instance(
                 prim.metadata.clip_chain_id,
                 local_rect,
                 prim.metadata.local_clip_rect,
                 prim_context.spatial_node_index,
-                &frame_context.clip_scroll_tree.spatial_nodes,
+                &frame_context.clip_scroll_tree,
                 frame_state.gpu_cache,
                 frame_state.resource_cache,
                 frame_context.device_pixel_scale,
             );
 
         let clip_chain = match clip_chain {
             Some(clip_chain) => clip_chain,
             None => {
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-e70bae07664def86aefd11c86dac818ab7ea64ea
+93997662842b6d8bafbdb3dde79009c930db66ca