Test xf AA change. try: -b do -p linux64 -u all[linux64-qr] -t none
authorGlenn Watson <github@intuitionlibrary.com>
Mon, 27 Nov 2017 16:07:11 +1000
changeset 1361769 81182a405c3edab9fa88e4722a56da1a8367aa90
parent 1361651 da90245d47b17c750560dedb5cbe1973181166e3
child 1593022 db1177b38ba08929b6ea9ecdd2665d94a9a92bdf
push id237897
push usergwatson@mozilla.com
push dateMon, 27 Nov 2017 06:07:48 +0000
treeherdertry@81182a405c3e [default view] [failures only]
milestone59.0a1
Test xf AA change. try: -b do -p linux64 -u all[linux64-qr] -t none
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/ps_composite.glsl
gfx/webrender/res/ps_gradient.glsl
gfx/webrender/res/ps_rectangle.glsl
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/device.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -62,32 +62,51 @@ void main(void) {
         // Right now - pictures only support local positions. In the future, this
         // will be expanded to support transform picture types (the common kind).
         device_pos = pic_task.common_data.task_rect.p0 +
                      uDevicePixelRatio * (local_pos - pic_task.content_origin);
 
         // Write the final position transformed by the orthographic device-pixel projection.
         gl_Position = uTransform * vec4(device_pos, 0.0, 1.0);
     } else {
+        VertexInfo vi;
         Layer layer = fetch_layer(brush.clip_node_id, brush.scroll_node_id);
         ClipArea clip_area = fetch_clip_area(brush.clip_address);
 
         // Write the normal vertex information out.
-        // TODO(gw): Support transform types in brushes. For now,
-        //           the old cache image shader didn't support
-        //           them yet anyway, so we're not losing any
-        //           existing functionality.
-        VertexInfo vi = write_vertex(
-            geom.local_rect,
-            geom.local_clip_rect,
-            float(brush.z),
-            layer,
-            pic_task,
-            geom.local_rect
-        );
+        if (layer.is_axis_aligned) {
+            vi = write_vertex(
+                geom.local_rect,
+                geom.local_clip_rect,
+                float(brush.z),
+                layer,
+                pic_task,
+                geom.local_rect
+            );
+
+            // TODO(gw): vLocalBounds may be referenced by
+            //           the fragment shader when running in
+            //           the alpha pass, even on non-transformed
+            //           items. For now, just ensure it has no
+            //           effect. We can tidy this up as we move
+            //           more items to be brush shaders.
+            vLocalBounds = vec4(
+                geom.local_clip_rect.p0,
+                geom.local_clip_rect.p0 + geom.local_clip_rect.size
+            );
+        } else {
+            vi = write_transform_vertex(geom.local_rect,
+                geom.local_rect,
+                geom.local_clip_rect,
+                vec4(1.0),
+                float(brush.z),
+                layer,
+                pic_task
+            );
+        }
 
         local_pos = vi.local_pos;
 
         // For brush instances in the alpha pass, always write
         // out clip information.
         // TODO(gw): It's possible that we might want alpha
         //           shaders that don't clip in the future,
         //           but it's reasonable to assume that one
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,14 +1,18 @@
 /* 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/. */
 
 #include shared,prim_shared,brush
 
+#ifdef WR_FEATURE_ALPHA_PASS
+varying vec2 vLocalPos;
+#endif
+
 varying vec3 vUv;
 flat varying int vImageKind;
 flat varying vec4 vUvBounds;
 flat varying vec4 vUvBounds_NoClamp;
 flat varying vec4 vParams;
 
 #if defined WR_FEATURE_ALPHA_TARGET
 flat varying vec4 vColor;
@@ -71,16 +75,20 @@ void brush_vs(
             vUv.xy = (local_pos - local_rect.p0) / local_src_size;
             vParams.xy = 0.5 * local_rect.size / local_src_size;
             break;
         }
     }
 
     vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
     vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
+
+#ifdef WR_FEATURE_ALPHA_PASS
+    vLocalPos = local_pos;
+#endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
     vec2 uv;
 
     switch (vImageKind) {
@@ -111,11 +119,15 @@ vec4 brush_fs() {
     }
 
 #if defined WR_FEATURE_COLOR_TARGET
     vec4 color = texture(sColor0, vec3(uv, vUv.z));
 #else
     vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
 #endif
 
+#ifdef WR_FEATURE_ALPHA_PASS
+    color *= init_transform_fs(vLocalPos);
+#endif
+
     return color;
 }
 #endif
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -31,19 +31,17 @@ vec2 clamp_rect(vec2 point, RectWithSize
 float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
     vec2 dir_to_p0 = p0 - p;
     return dot(normalize(perp_dir), dir_to_p0);
 }
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
-#ifdef WR_FEATURE_TRANSFORM
-    flat varying vec4 vLocalBounds;
-#endif
+flat varying vec4 vLocalBounds;
 
 // TODO(gw): This is here temporarily while we have
 //           both GPU store and cache. When the GPU
 //           store code is removed, we can change the
 //           PrimitiveInstance instance structure to
 //           use 2x unsigned shorts as vertex attributes
 //           instead of an int, and encode the UV directly
 //           in the vertices.
@@ -66,17 +64,17 @@ vec4[2] fetch_from_resource_cache_2(int 
     return vec4[2](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0))
     );
 }
 
 #ifdef WR_VERTEX_SHADER
 
-#define VECS_PER_LAYER              10
+#define VECS_PER_LAYER              11
 #define VECS_PER_RENDER_TASK        3
 #define VECS_PER_PRIM_HEADER        2
 #define VECS_PER_TEXT_RUN           3
 #define VECS_PER_GRADIENT           3
 #define VECS_PER_GRADIENT_STOP      2
 
 uniform HIGHP_SAMPLER_FLOAT sampler2D sClipScrollNodes;
 uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks;
@@ -144,16 +142,17 @@ vec4 fetch_from_resource_cache_1(int add
 }
 
 struct ClipScrollNode {
     mat4 transform;
     mat4 inv_transform;
     vec4 local_clip_rect;
     vec2 reference_frame_relative_scroll_offset;
     vec2 scroll_offset;
+    bool is_axis_aligned;
 };
 
 ClipScrollNode fetch_clip_scroll_node(int index) {
     ClipScrollNode node;
 
     // Create a UV base coord for each 8 texels.
     // This is required because trying to use an offset
     // of more than 8 texels doesn't work on some versions
@@ -174,39 +173,44 @@ ClipScrollNode fetch_clip_scroll_node(in
 
     vec4 clip_rect = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(0, 0));
     node.local_clip_rect = clip_rect;
 
     vec4 offsets = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(1, 0));
     node.reference_frame_relative_scroll_offset = offsets.xy;
     node.scroll_offset = offsets.zw;
 
+    vec4 misc = TEXEL_FETCH(sClipScrollNodes, uv1, 0, ivec2(2, 0));
+    node.is_axis_aligned = misc.x == 0.0;
+
     return node;
 }
 
 struct Layer {
     mat4 transform;
     mat4 inv_transform;
     RectWithSize local_clip_rect;
+    bool is_axis_aligned;
 };
 
 Layer fetch_layer(int clip_node_id, int scroll_node_id) {
     ClipScrollNode clip_node = fetch_clip_scroll_node(clip_node_id);
     ClipScrollNode scroll_node = fetch_clip_scroll_node(scroll_node_id);
 
     Layer layer;
     layer.transform = scroll_node.transform;
     layer.inv_transform = scroll_node.inv_transform;
 
     vec4 local_clip_rect = clip_node.local_clip_rect;
     local_clip_rect.xy += clip_node.reference_frame_relative_scroll_offset;
     local_clip_rect.xy -= scroll_node.reference_frame_relative_scroll_offset;
     local_clip_rect.xy -= scroll_node.scroll_offset;
 
     layer.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw);
+    layer.is_axis_aligned = scroll_node.is_axis_aligned;
 
     return layer;
 }
 
 struct RenderTaskCommonData {
     RectWithSize task_rect;
     float texture_layer_index;
 };
@@ -610,18 +614,16 @@ VertexInfo write_vertex(RectWithSize ins
                      task.common_data.task_rect.p0;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     VertexInfo vi = VertexInfo(clamped_local_pos, device_pos);
     return vi;
 }
 
-#ifdef WR_FEATURE_TRANSFORM
-
 float cross2(vec2 v0, vec2 v1) {
     return v0.x * v1.y - v0.y * v1.x;
 }
 
 // Return intersection of line (p0,p1) and line (p2,p3)
 vec2 intersect_lines(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
     vec2 d0 = p0 - p1;
     vec2 d1 = p2 - p3;
@@ -631,46 +633,52 @@ vec2 intersect_lines(vec2 p0, vec2 p1, v
 
     float d = cross2(d0, d1);
     float nx = s0 * d1.x - d0.x * s1;
     float ny = s0 * d1.y - d0.y * s1;
 
     return vec2(nx / d, ny / d);
 }
 
-VertexInfo write_transform_vertex(RectWithSize instance_rect,
+VertexInfo write_transform_vertex(RectWithSize local_segment_rect,
+                                  RectWithSize local_prim_rect,
                                   RectWithSize local_clip_rect,
                                   vec4 clip_edge_mask,
                                   float z,
                                   Layer layer,
                                   PictureTask task) {
     // Calculate a clip rect from local clip + layer clip.
     RectWithEndpoint clip_rect = to_rect_with_endpoint(local_clip_rect);
     clip_rect.p0 = clamp_rect(clip_rect.p0, layer.local_clip_rect);
     clip_rect.p1 = clamp_rect(clip_rect.p1, layer.local_clip_rect);
 
     // Calculate a clip rect from local_rect + local clip + layer clip.
-    RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
-    local_rect.p0 = clamp(local_rect.p0, clip_rect.p0, clip_rect.p1);
-    local_rect.p1 = clamp(local_rect.p1, clip_rect.p0, clip_rect.p1);
+    RectWithEndpoint segment_rect = to_rect_with_endpoint(local_segment_rect);
+    segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1);
+    segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1);
+
+    // Calculate a clip rect from local_rect + local clip + layer clip.
+    RectWithEndpoint prim_rect = to_rect_with_endpoint(local_prim_rect);
+    prim_rect.p0 = clamp(prim_rect.p0, clip_rect.p0, clip_rect.p1);
+    prim_rect.p1 = clamp(prim_rect.p1, clip_rect.p0, clip_rect.p1);
 
     // As this is a transform shader, extrude by 2 (local space) pixels
     // in each direction. This gives enough space around the edge to
     // apply distance anti-aliasing. Technically, it:
     // (a) slightly over-estimates the number of required pixels in the simple case.
     // (b) might not provide enough edge in edge case perspective projections.
     // However, it's fast and simple. If / when we ever run into issues, we
     // can do some math on the projection matrix to work out a variable
     // amount to extrude.
     float extrude_distance = 2.0;
-    instance_rect.p0 -= vec2(extrude_distance);
-    instance_rect.size += vec2(2.0 * extrude_distance);
+    local_segment_rect.p0 -= vec2(extrude_distance);
+    local_segment_rect.size += vec2(2.0 * extrude_distance);
 
     // Select the corner of the local rect that we are processing.
-    vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
+    vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy;
 
     // Transform the current vertex to the world cpace.
     vec4 world_pos = layer.transform * vec4(local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     // We want the world space coords to be perspective divided by W.
@@ -678,38 +686,37 @@ VertexInfo write_transform_vertex(RectWi
     // want a constant Z across the primitive, since we're using it
     // for draw ordering - so scale by the W coord to ensure this.
     vec4 final_pos = vec4(world_pos.xy + task.common_data.task_rect.p0 - task.content_origin,
                           z * world_pos.w,
                           world_pos.w);
     gl_Position = uTransform * final_pos;
 
     vLocalBounds = mix(
-        vec4(clip_rect.p0, clip_rect.p1),
-        vec4(local_rect.p0, local_rect.p1),
+        vec4(prim_rect.p0, prim_rect.p1),
+        vec4(segment_rect.p0, segment_rect.p1),
         clip_edge_mask
     );
 
     VertexInfo vi = VertexInfo(local_pos, device_pos);
     return vi;
 }
 
 VertexInfo write_transform_vertex_primitive(Primitive prim) {
     return write_transform_vertex(
         prim.local_rect,
+        prim.local_rect,
         prim.local_clip_rect,
-        vec4(1.0),
+        vec4(0.0),
         prim.z,
         prim.layer,
         prim.task
     );
 }
 
-#endif //WR_FEATURE_TRANSFORM
-
 struct GlyphResource {
     vec4 uv_rect;
     float layer;
     vec2 offset;
     float scale;
 };
 
 GlyphResource fetch_glyph_resource(int address) {
@@ -804,17 +811,16 @@ float compute_aa_range(vec2 position) {
 
 /// Return the blending coefficient to for distance antialiasing.
 ///
 /// 0.0 means inside the shape, 1.0 means outside.
 float distance_aa(float aa_range, float signed_distance) {
     return 1.0 - smoothstep(-aa_range, aa_range, signed_distance);
 }
 
-#ifdef WR_FEATURE_TRANSFORM
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
 float init_transform_fs(vec2 local_pos) {
     // Get signed distance from local rect bounds.
     float d = signed_distance_rect(
@@ -824,17 +830,16 @@ float init_transform_fs(vec2 local_pos) 
     );
 
     // Find the appropriate distance to apply the AA smoothstep over.
     float aa_range = compute_aa_range(local_pos);
 
     // Only apply AA to fragments outside the signed distance field.
     return distance_aa(aa_range, d);
 }
-#endif //WR_FEATURE_TRANSFORM
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -294,16 +294,17 @@ void main(void) {
     write_color(color0, color1, style, color_delta, prim.user_data1);
 
     RectWithSize segment_rect;
     segment_rect.p0 = p0;
     segment_rect.size = p1 - p0;
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
+                                           prim.local_rect,
                                            prim.local_clip_rect,
                                            vec4(1.0),
                                            prim.z,
                                            prim.layer,
                                            prim.task);
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -212,16 +212,17 @@ void main(void) {
     }
 
     write_alpha_select(style);
     write_color0(color, style, color_flip);
     write_color1(color, style, color_flip);
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
+                                           prim.local_rect,
                                            prim.local_clip_rect,
                                            vec4(1.0),
                                            prim.z,
                                            prim.layer,
                                            prim.task);
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
--- a/gfx/webrender/res/ps_composite.glsl
+++ b/gfx/webrender/res/ps_composite.glsl
@@ -202,29 +202,29 @@ const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 void main(void) {
     vec4 Cb = texture(sCacheRGBA8, vUv0);
     vec4 Cs = texture(sCacheRGBA8, vUv1);
 
-    // The mix-blend-mode functions assume no premultiplied alpha
-    Cb.rgb /= Cb.a;
-    Cs.rgb /= Cs.a;
-
     if (Cb.a == 0.0) {
         oFragColor = Cs;
         return;
     }
     if (Cs.a == 0.0) {
         oFragColor = vec4(0.0, 0.0, 0.0, 0.0);
         return;
     }
 
+    // The mix-blend-mode functions assume no premultiplied alpha
+    Cb.rgb /= Cb.a;
+    Cs.rgb /= Cs.a;
+
     // Return yellow if none of the branches match (shouldn't happen).
     vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
 
     switch (vOp) {
         case MixBlendMode_Multiply:
             result.rgb = Multiply(Cb.rgb, Cs.rgb);
             break;
         case MixBlendMode_Screen:
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -63,16 +63,17 @@ void main(void) {
         // Adjust the stop colors by how much they were clamped
         vec2 adjusted_offset = (g01_y_clamped - g01_y.xx) / (g01_y.y - g01_y.x);
         adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(segment_rect,
+                                           prim.local_rect,
                                            prim.local_clip_rect,
                                            vec4(1.0),
                                            prim.z,
                                            prim.layer,
                                            prim.task);
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
 #else
--- a/gfx/webrender/res/ps_rectangle.glsl
+++ b/gfx/webrender/res/ps_rectangle.glsl
@@ -12,16 +12,17 @@ varying vec2 vLocalPos;
 
 #ifdef WR_VERTEX_SHADER
 void main(void) {
     Primitive prim = load_primitive();
     Rectangle rect = fetch_rectangle(prim.specific_prim_address);
     vColor = rect.color;
 #ifdef WR_FEATURE_TRANSFORM
     VertexInfo vi = write_transform_vertex(prim.local_rect,
+                                           prim.local_rect,
                                            prim.local_clip_rect,
                                            rect.edge_aa_segment_mask,
                                            prim.z,
                                            prim.layer,
                                            prim.task);
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -98,25 +98,27 @@ impl From<ClipRegion> for ClipSources {
         }
 
         ClipSources::new(clips)
     }
 }
 
 impl ClipSource {
     pub fn contains(&self, point: &LayerPoint) -> bool {
-        // We currently do not handle all types of clip sources, because they
-        // aren't used for ClipScrollNodes and this method is only used during hit testing.
+        // We currently do not handle all BorderCorners, because they aren't used for
+        // ClipScrollNodes and this method is only used during hit testing.
         match self {
             &ClipSource::Rectangle(ref rectangle) => rectangle.contains(point),
             &ClipSource::RoundedRectangle(rect, radii, ClipMode::Clip) =>
                 rounded_rectangle_contains_point(point, &rect, &radii),
             &ClipSource::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                 !rounded_rectangle_contains_point(point, &rect, &radii),
-            _ => unreachable!("Tried to call contains on an unsupported ClipSource."),
+            &ClipSource::Image(mask) => mask.rect.contains(point),
+            &ClipSource::BorderCorner(_) =>
+                unreachable!("Tried to call contains on a BorderCornerr."),
         }
     }
 
 }
 
 #[derive(Debug)]
 pub struct ClipSources {
     pub clips: Vec<(ClipSource, GpuCacheHandle)>,
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -12,17 +12,17 @@ use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
 use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
 use render_task::{ClipChain, ClipChainNode, ClipWorkItem};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use spring::{DAMPING, STIFFNESS, Spring};
 use std::rc::Rc;
-use util::{MatrixHelpers, MaxRect};
+use util::{MatrixHelpers, MaxRect, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 const MAX_LOCAL_VIEWPORT: f32 = 1000000.0;
@@ -299,23 +299,31 @@ impl ClipScrollNode {
                 LayerSize::new(2.0 * MAX_LOCAL_VIEWPORT, 2.0 * MAX_LOCAL_VIEWPORT)
             )
         } else {
             self.combined_local_viewport_rect
         };
 
         let data = match self.world_content_transform.inverse() {
             Some(inverse) => {
+                let transform_kind = if self.world_content_transform.preserves_2d_axis_alignment() {
+                    TransformedRectKind::AxisAligned
+                } else {
+                    TransformedRectKind::Complex
+                };
+
                 ClipScrollNodeData {
                     transform: self.world_content_transform,
                     inv_transform: inverse,
                     local_clip_rect,
                     reference_frame_relative_scroll_offset:
                         self.reference_frame_relative_scroll_offset,
                     scroll_offset: self.scroll_offset(),
+                    transform_kind: transform_kind as u32 as f32,
+                    padding: [0.0; 3],
                 }
             }
             None => {
                 state.combined_outer_clip_bounds = DeviceIntRect::zero();
                 self.combined_clip_outer_bounds = DeviceIntRect::zero();
                 ClipScrollNodeData::invalid()
             }
         };
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -386,17 +386,16 @@ impl ExternalTexture {
 
 pub struct Texture {
     id: gl::GLuint,
     target: gl::GLuint,
     layer_count: i32,
     format: ImageFormat,
     width: u32,
     height: u32,
-
     filter: TextureFilter,
     render_target: Option<RenderTargetInfo>,
     fbo_ids: Vec<FBOId>,
     depth_rb: Option<RBOId>,
 }
 
 impl Texture {
     pub fn get_dimensions(&self) -> DeviceUintSize {
@@ -406,16 +405,20 @@ impl Texture {
     pub fn get_render_target_layer_count(&self) -> usize {
         self.fbo_ids.len()
     }
 
     pub fn get_layer_count(&self) -> i32 {
         self.layer_count
     }
 
+    pub fn get_format(&self) -> ImageFormat {
+        self.format
+    }
+
     pub fn get_bpp(&self) -> u32 {
         match self.format {
             ImageFormat::A8 => 1,
             ImageFormat::RGB8 => 3,
             ImageFormat::BGRA8 => 4,
             ImageFormat::RG8 => 2,
             ImageFormat::RGBAF32 => 16,
             ImageFormat::Invalid => unreachable!(),
@@ -880,37 +883,39 @@ impl Device {
         format: ImageFormat,
         filter: TextureFilter,
         render_target: Option<RenderTargetInfo>,
         layer_count: i32,
         pixels: Option<&[u8]>,
     ) {
         debug_assert!(self.inside_frame);
 
-        let resized = texture.width != width || texture.height != height;
+        let resized = texture.width != width ||
+            texture.height != height ||
+            texture.format != format;
 
         texture.format = format;
         texture.width = width;
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.render_target = render_target;
 
-        let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
-        let type_ = gl_type_for_texture_format(format);
-
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, filter);
 
         match render_target {
             Some(info) => {
                 assert!(pixels.is_none());
                 self.update_texture_storage(texture, &info, resized);
             }
             None => {
+                let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format);
+                let type_ = gl_type_for_texture_format(format);
+
                 let expanded_data: Vec<u8>;
                 let actual_pixels = if pixels.is_some() && format == ImageFormat::A8 &&
                     cfg!(any(target_arch = "arm", target_arch = "aarch64"))
                 {
                     expanded_data = pixels
                         .unwrap()
                         .iter()
                         .flat_map(|&byte| repeat(byte).take(4))
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -174,33 +174,35 @@ impl FontInstance {
     pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) {
         match self.subpx_dir {
             SubpixelDirection::None => (0.0, 0.0),
             SubpixelDirection::Horizontal => (glyph.subpixel_offset.into(), 0.0),
             SubpixelDirection::Vertical => (0.0, glyph.subpixel_offset.into()),
         }
     }
 
-    pub fn get_subpixel_glyph_format(&self) -> GlyphFormat {
-        if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
-    }
-
-    #[allow(dead_code)]
-    pub fn get_glyph_format(&self) -> GlyphFormat {
+    pub fn get_glyph_format(&self, color_bitmaps: bool) -> GlyphFormat {
         match self.render_mode {
-            FontRenderMode::Mono | FontRenderMode::Alpha => GlyphFormat::Alpha,
-            FontRenderMode::Subpixel => self.get_subpixel_glyph_format(),
-            FontRenderMode::Bitmap => GlyphFormat::ColorBitmap,
+            FontRenderMode::Mono | FontRenderMode::Alpha => {
+                if self.transform.is_identity() { GlyphFormat::Alpha } else { GlyphFormat::TransformedAlpha }
+            }
+            FontRenderMode::Subpixel => {
+                if self.transform.is_identity() { GlyphFormat::Subpixel } else { GlyphFormat::TransformedSubpixel }
+            }
+            FontRenderMode::Bitmap => {
+                if color_bitmaps { GlyphFormat::ColorBitmap } else { GlyphFormat::Alpha }
+            }
         }
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum GlyphFormat {
     Alpha,
+    TransformedAlpha,
     Subpixel,
     TransformedSubpixel,
     ColorBitmap,
 }
 
 pub struct RasterizedGlyph {
     pub top: f32,
     pub left: f32,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -193,21 +193,25 @@ pub struct ClipScrollNodeIndex(pub u32);
 #[derive(Debug)]
 #[repr(C)]
 pub struct ClipScrollNodeData {
     pub transform: LayerToWorldTransform,
     pub inv_transform: WorldToLayerTransform,
     pub local_clip_rect: LayerRect,
     pub reference_frame_relative_scroll_offset: LayerVector2D,
     pub scroll_offset: LayerVector2D,
+    pub transform_kind: f32,
+    pub padding: [f32; 3],
 }
 
 impl ClipScrollNodeData {
     pub fn invalid() -> ClipScrollNodeData {
         ClipScrollNodeData {
             transform: LayerToWorldTransform::identity(),
             inv_transform: WorldToLayerTransform::identity(),
             local_clip_rect: LayerRect::zero(),
             reference_frame_relative_scroll_offset: LayerVector2D::zero(),
             scroll_offset: LayerVector2D::zero(),
+            transform_kind: 0.0,
+            padding: [0.0; 3],
         }
     }
 }
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -35,17 +35,17 @@ pub enum PictureCompositeMode {
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit,
 }
 
 /// Configure whether the primitives on this picture
 /// should be rasterized in screen space or local space.
 #[repr(C)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum RasterizationSpace {
     Local = 0,
     Screen = 1,
 }
 
 #[derive(Debug)]
 pub enum PictureKind {
     TextShadow {
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -652,13 +652,13 @@ impl FontContext {
         }
 
         Some(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             scale: 1.0,
-            format: font.get_glyph_format(),
+            format: font.get_glyph_format(true),
             bytes: rasterized_pixels,
         })
     }
 }
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -14,17 +14,17 @@ use freetype::freetype::{FT_F26Dot6, FT_
 use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size};
 use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform};
 use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT};
 use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT};
 use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING, FT_LOAD_VERTICAL_LAYOUT};
 use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES, FT_Err_Cannot_Render_Glyph};
-use glyph_rasterizer::{FontInstance, GlyphFormat, RasterizedGlyph};
+use glyph_rasterizer::{FontInstance, RasterizedGlyph};
 use internal_types::FastHashMap;
 use std::{cmp, mem, ptr, slice};
 use std::cmp::max;
 use std::ffi::CString;
 use std::sync::Arc;
 
 // These constants are not present in the freetype
 // bindings due to bindgen not handling the way
@@ -529,33 +529,29 @@ impl FontContext {
         let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
         info!(
             "Rasterizing {:?} as {:?} with dimensions {:?}",
             key,
             font.render_mode,
             dimensions
         );
 
-        let (format, actual_width, actual_height) = match pixel_mode {
+        let (actual_width, actual_height) = match pixel_mode {
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
                 assert!(bitmap.width % 3 == 0);
-                (font.get_subpixel_glyph_format(), (bitmap.width / 3) as i32, bitmap.rows as i32)
+                ((bitmap.width / 3) as i32, bitmap.rows as i32)
             }
             FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => {
                 assert!(bitmap.rows % 3 == 0);
-                (font.get_subpixel_glyph_format(), bitmap.width as i32, (bitmap.rows / 3) as i32)
-            }
-            FT_Pixel_Mode::FT_PIXEL_MODE_MONO => {
-                (GlyphFormat::Alpha, bitmap.width as i32, bitmap.rows as i32)
+                (bitmap.width as i32, (bitmap.rows / 3) as i32)
             }
-            FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
-                (GlyphFormat::Alpha, bitmap.width as i32, bitmap.rows as i32)
-            }
+            FT_Pixel_Mode::FT_PIXEL_MODE_MONO |
+            FT_Pixel_Mode::FT_PIXEL_MODE_GRAY |
             FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
-                (GlyphFormat::ColorBitmap, bitmap.width as i32, bitmap.rows as i32)
+                (bitmap.width as i32, bitmap.rows as i32)
             }
             _ => panic!("Unsupported {:?}", pixel_mode),
         };
         let (left, top) = unsafe { ((*slot).bitmap_left, (*slot).bitmap_top) };
         let mut final_buffer = vec![0; (actual_width * actual_height * 4) as usize];
 
         // Extract the final glyph from FT format into RGBA8 format, which is
         // what WR expects.
@@ -639,17 +635,17 @@ impl FontContext {
         }
 
         Some(RasterizedGlyph {
             left: (dimensions.left + left) as f32,
             top: (dimensions.top + top - actual_height) as f32,
             width: actual_width as u32,
             height: actual_height as u32,
             scale,
-            format,
+            format: font.get_glyph_format(pixel_mode == FT_Pixel_Mode::FT_PIXEL_MODE_BGRA),
             bytes: final_buffer,
         })
     }
 }
 
 impl Drop for FontContext {
     fn drop(&mut self) {
         unsafe {
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -26,51 +26,53 @@ pub struct FontContext {
 }
 
 // DirectWrite is safe to use on multiple threads and non-shareable resources are
 // all hidden inside their font context.
 unsafe impl Send for FontContext {}
 
 fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
     match render_mode {
-        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
-        FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
+        FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
+        FontRenderMode::Bitmap |
+        FontRenderMode::Alpha |
+        FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
     }
 }
 
 fn dwrite_measure_mode(
     render_mode: FontRenderMode,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_MEASURING_MODE {
-    let FontInstancePlatformOptions { force_gdi_rendering, use_embedded_bitmap, .. } =
+    let FontInstancePlatformOptions { force_gdi_rendering, .. } =
         options.unwrap_or_default();
-    if force_gdi_rendering || use_embedded_bitmap {
-        return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
-    }
-
-    match render_mode {
-        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_MEASURING_MODE_GDI_NATURAL,
-        FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
+    if force_gdi_rendering {
+        dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC
+    } else {
+      match render_mode {
+          FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC,
+          FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
+      }
     }
 }
 
 fn dwrite_render_mode(
     font_face: &dwrote::FontFace,
     render_mode: FontRenderMode,
     em_size: f32,
     measure_mode: dwrote::DWRITE_MEASURING_MODE,
     options: Option<FontInstancePlatformOptions>,
 ) -> dwrote::DWRITE_RENDERING_MODE {
-    let FontInstancePlatformOptions { force_gdi_rendering, use_embedded_bitmap, .. } =
-        options.unwrap_or_default();
-
     let dwrite_render_mode = match render_mode {
-        FontRenderMode::Mono | FontRenderMode::Bitmap => dwrote::DWRITE_RENDERING_MODE_ALIASED,
+        FontRenderMode::Bitmap => dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC,
+        FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
         FontRenderMode::Alpha | FontRenderMode::Subpixel => {
-            if force_gdi_rendering || use_embedded_bitmap {
+            let FontInstancePlatformOptions { force_gdi_rendering, .. } =
+                options.unwrap_or_default();
+            if force_gdi_rendering {
                 dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC
             } else {
                 font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
             }
         }
     };
 
     if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
@@ -252,31 +254,28 @@ impl FontContext {
                     advance: advance,
                 }
             })
     }
 
     // DWrite ClearType gives us values in RGB, but WR expects BGRA.
     fn convert_to_bgra(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
-            FontRenderMode::Bitmap => {
-                unreachable!("TODO: bitmap fonts");
-            }
             FontRenderMode::Mono => {
                 let mut bgra_pixels: Vec<u8> = vec![0; pixels.len() * 4];
                 for i in 0 .. pixels.len() {
                     let alpha = pixels[i];
                     bgra_pixels[i * 4 + 0] = alpha;
                     bgra_pixels[i * 4 + 1] = alpha;
                     bgra_pixels[i * 4 + 2] = alpha;
                     bgra_pixels[i * 4 + 3] = alpha;
                 }
                 bgra_pixels
             }
-            FontRenderMode::Alpha => {
+            FontRenderMode::Alpha | FontRenderMode::Bitmap => {
                 let length = pixels.len() / 3;
                 let mut bgra_pixels: Vec<u8> = vec![0; length * 4];
                 for i in 0 .. length {
                     // Only take the G channel, as its closest to D2D
                     let alpha = pixels[i * 3 + 1] as u8;
                     bgra_pixels[i * 4 + 0] = alpha;
                     bgra_pixels[i * 4 + 1] = alpha;
                     bgra_pixels[i * 4 + 2] = alpha;
@@ -293,19 +292,21 @@ impl FontContext {
                     bgra_pixels[i * 4 + 2] = pixels[i * 3 + 0];
                     bgra_pixels[i * 4 + 3] = 0xff;
                 }
                 bgra_pixels
             }
         }
     }
 
-    pub fn is_bitmap_font(&mut self, _font: &FontInstance) -> bool {
-        // TODO(gw): Support bitmap fonts in DWrite.
-        false
+    pub fn is_bitmap_font(&mut self, font: &FontInstance) -> bool {
+        // If bitmaps are requested, then treat as a bitmap font to disable transforms.
+        // If mono AA is requested, let that take priority over using bitmaps.
+        font.render_mode != FontRenderMode::Mono &&
+            font.platform_options.unwrap_or_default().use_embedded_bitmap
     }
 
     pub fn prepare_font(font: &mut FontInstance) {
         match font.render_mode {
             FontRenderMode::Mono | FontRenderMode::Bitmap => {
                 // In mono/bitmap modes the color of the font is irrelevant.
                 font.color = ColorU::new(255, 255, 255, 255);
                 // Subpixel positioning is disabled in mono and bitmap modes.
@@ -336,35 +337,33 @@ impl FontContext {
         // Such as for spaces
         if width == 0 || height == 0 {
             return None;
         }
 
         let pixels = analysis.create_alpha_texture(texture_type, bounds);
         let mut bgra_pixels = self.convert_to_bgra(&pixels, font.render_mode);
 
-        match font.render_mode {
-            FontRenderMode::Mono | FontRenderMode::Bitmap => {}
+        let lut_correction = match font.render_mode {
+            FontRenderMode::Mono | FontRenderMode::Bitmap => &self.gdi_gamma_lut,
             FontRenderMode::Alpha | FontRenderMode::Subpixel => {
-                let lut_correction = match font.platform_options {
-                    Some(option) => if option.force_gdi_rendering {
-                        &self.gdi_gamma_lut
-                    } else {
-                        &self.gamma_lut
-                    },
-                    None => &self.gamma_lut,
-                };
-
-                lut_correction.preblend(&mut bgra_pixels, font.color);
+                let FontInstancePlatformOptions { force_gdi_rendering, .. } =
+                    font.platform_options.unwrap_or_default();
+                if force_gdi_rendering {
+                    &self.gdi_gamma_lut
+                } else {
+                    &self.gamma_lut
+                }
             }
-        }
+        };
+        lut_correction.preblend(&mut bgra_pixels, font.color);
 
         Some(RasterizedGlyph {
             left: bounds.left as f32,
             top: -bounds.top as f32,
             width,
             height,
             scale: 1.0,
-            format: font.get_glyph_format(),
+            format: font.get_glyph_format(false),
             bytes: bgra_pixels,
         })
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -594,25 +594,23 @@ impl TextRunPrimitiveCpu {
     pub fn get_font(
         &self,
         device_pixel_ratio: f32,
         transform: &LayerToWorldTransform,
         rasterization_kind: RasterizationSpace,
     ) -> FontInstance {
         let mut font = self.font.clone();
         font.size = font.size.scale_by(device_pixel_ratio);
-        match (font.render_mode, rasterization_kind) {
-            (FontRenderMode::Subpixel, RasterizationSpace::Screen) => {
-                if transform.has_perspective_component() || !transform.has_2d_inverse() {
-                    font.render_mode = FontRenderMode::Alpha;
-                } else {
-                    font.transform = FontTransform::from(transform).quantize();
-                }
+        if font.render_mode != FontRenderMode::Bitmap &&
+           rasterization_kind == RasterizationSpace::Screen {
+            if transform.has_perspective_component() || !transform.has_2d_inverse() {
+                font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
+            } else {
+                font.transform = FontTransform::from(transform).quantize();
             }
-            _ => {}
         }
         font
     }
 
     fn prepare_for_render(
         &mut self,
         resource_cache: &mut ResourceCache,
         device_pixel_ratio: f32,
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -277,17 +277,17 @@ impl Into<ShaderMode> for TextShaderMode
     fn into(self) -> i32 {
         self as i32
     }
 }
 
 impl From<GlyphFormat> for TextShaderMode {
     fn from(format: GlyphFormat) -> TextShaderMode {
         match format {
-            GlyphFormat::Alpha => TextShaderMode::Alpha,
+            GlyphFormat::Alpha | GlyphFormat::TransformedAlpha => TextShaderMode::Alpha,
             GlyphFormat::Subpixel | GlyphFormat::TransformedSubpixel => {
                 panic!("Subpixel glyph formats must be handled separately.");
             }
             GlyphFormat::ColorBitmap => TextShaderMode::ColorBitmap,
         }
     }
 }
 
@@ -595,22 +595,21 @@ impl SourceTextureResolver {
             device.delete_texture(texture);
         }
     }
 
     fn end_pass(
         &mut self,
         a8_texture: Option<Texture>,
         rgba8_texture: Option<Texture>,
-        a8_pool: &mut Vec<Texture>,
-        rgba8_pool: &mut Vec<Texture>,
+        pool: &mut Vec<Texture>,
     ) {
         // If we have cache textures from previous pass, return them to the pool.
-        rgba8_pool.extend(self.cache_rgba8_texture.take());
-        a8_pool.extend(self.cache_a8_texture.take());
+        pool.extend(self.cache_rgba8_texture.take());
+        pool.extend(self.cache_a8_texture.take());
 
         // We have another pass to process, make these textures available
         // as inputs to the next pass.
         self.cache_rgba8_texture = rgba8_texture;
         self.cache_a8_texture = a8_texture;
     }
 
     // Bind a source texture to the device.
@@ -1187,16 +1186,17 @@ impl TextShader {
                     TransformedRectKind::AxisAligned => {
                         self.simple.bind(device, projection, mode, renderer_errors)
                     }
                     TransformedRectKind::Complex => {
                         self.transform.bind(device, projection, mode, renderer_errors)
                     }
                 }
             }
+            GlyphFormat::TransformedAlpha |
             GlyphFormat::TransformedSubpixel => {
                 self.glyph_transform.bind(device, projection, mode, renderer_errors)
             }
         }
     }
 
     fn deinit(self, device: &mut Device) {
         self.simple.deinit(device);
@@ -1296,16 +1296,23 @@ pub enum ReadPixelsFormat {
     Bgra8,
 }
 
 struct FrameOutput {
     last_access: FrameId,
     fbo_id: FBOId,
 }
 
+#[derive(PartialEq)]
+struct TargetSelector {
+    size: DeviceUintSize,
+    num_layers: usize,
+    format: ImageFormat,
+}
+
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
@@ -1365,18 +1372,17 @@ pub struct Renderer {
     enable_clear_scissor: bool,
     debug: DebugRenderer,
     debug_flags: DebugFlags,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     profiler: Profiler,
     last_time: u64,
 
-    color_render_targets: Vec<Texture>,
-    alpha_render_targets: Vec<Texture>,
+    render_target_pool: Vec<Texture>,
 
     gpu_profile: GpuProfiler<GpuProfileTag>,
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
 
     node_data_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
@@ -2006,18 +2012,17 @@ impl Renderer {
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             max_texture_size: max_texture_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
-            color_render_targets: Vec::new(),
-            alpha_render_targets: Vec::new(),
+            render_target_pool: Vec::new(),
             gpu_profile,
             prim_vao,
             blur_vao,
             clip_vao,
             node_data_texture,
             render_task_texture,
             pipeline_epoch_map: FastHashMap::default(),
             dither_matrix_texture,
@@ -2383,16 +2388,22 @@ impl Renderer {
                 });
             // don't clear the framebuffer if one of the rendered documents will overwrite it
             if needs_clear {
                 let clear_color = self.clear_color.map(|color| color.to_array());
                 self.device.bind_draw_target(None, None);
                 self.device.clear_target(clear_color, None);
             }
 
+            // Re-use whatever targets possible from the pool, before
+            // they get changed/re-allocated by the rendered frames.
+            for doc_with_id in &mut active_documents {
+                self.prepare_tile_frame(&mut doc_with_id.1.frame);
+            }
+
             for &mut (_, RenderedDocument { ref mut frame, .. }) in &mut active_documents {
                 self.update_gpu_cache(frame);
 
                 self.draw_tile_frame(frame, framebuffer_size, cpu_frame_id);
 
                 if self.debug_flags.contains(DebugFlags::PROFILER_DBG) {
                     frame_profiles.push(frame.profile_counters.clone());
                 }
@@ -3524,54 +3535,91 @@ impl Renderer {
 
             for (ext_data, _) in self.texture_resolver.external_images.drain() {
                 handler.unlock(ext_data.0, ext_data.1);
             }
         }
     }
 
     fn prepare_target_list<T: RenderTarget>(
+        &mut self,
         list: &mut RenderTargetList<T>,
-        device: &mut Device,
-        target_pool: &mut Vec<Texture>,
-        format: ImageFormat,
+        perfect_only: bool,
     ) {
         debug_assert_ne!(list.max_size, DeviceUintSize::zero());
-        debug_assert!(list.texture.is_none());
         if list.targets.is_empty() {
             return;
         }
-        let mut texture = match target_pool.pop() {
-            Some(texture) => texture,
-            None => device.create_texture(TextureTarget::Array),
+        let mut texture = if perfect_only {
+            debug_assert!(list.texture.is_none());
+
+            let selector = TargetSelector {
+                size: list.max_size,
+                num_layers: list.targets.len() as _,
+                format: list.format,
+            };
+            let index = self.render_target_pool
+                .iter()
+                .position(|texture| {
+                    selector == TargetSelector {
+                        size: texture.get_dimensions(),
+                        num_layers: texture.get_render_target_layer_count(),
+                        format: texture.get_format(),
+                    }
+                });
+            match index {
+                Some(pos) => self.render_target_pool.swap_remove(pos),
+                None => return,
+            }
+        } else {
+            if list.texture.is_some() {
+                return
+            }
+            match self.render_target_pool.pop() {
+                Some(texture) => texture,
+                None => self.device.create_texture(TextureTarget::Array),
+            }
         };
-        device.init_texture(
+
+        self.device.init_texture(
             &mut texture,
             list.max_size.width,
             list.max_size.height,
-            format,
+            list.format,
             TextureFilter::Linear,
             Some(RenderTargetInfo {
                 has_depth: list.needs_depth(),
             }),
             list.targets.len() as _,
             None,
         );
         list.texture = Some(texture);
     }
 
-    fn prepare_frame(&mut self, frame: &mut Frame) {
+    fn prepare_tile_frame(&mut self, frame: &mut Frame) {
+        // Init textures and render targets to match this scene.
+        // First pass grabs all the perfectly matching targets from the pool.
+        for pass in &mut frame.passes {
+            if let RenderPassKind::OffScreen { ref mut alpha, ref mut color } = pass.kind {
+                self.prepare_target_list(alpha, true);
+                self.prepare_target_list(color, true);
+            }
+        }
+    }
+
+    fn bind_frame_data(&mut self, frame: &mut Frame) {
         let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_DATA);
         self.device.device_pixel_ratio = frame.device_pixel_ratio;
 
-        // Init textures and render targets to match this scene.
+        // Some of the textures are already assigned by `prepare_frame`.
+        // Now re-allocate the space for the rest of the target textures.
         for pass in &mut frame.passes {
             if let RenderPassKind::OffScreen { ref mut alpha, ref mut color } = pass.kind {
-                Self::prepare_target_list(alpha, &mut self.device, &mut self.alpha_render_targets, ImageFormat::A8);
-                Self::prepare_target_list(color, &mut self.device, &mut self.color_render_targets, ImageFormat::BGRA8);
+                self.prepare_target_list(alpha, false);
+                self.prepare_target_list(color, false);
             }
         }
 
         self.node_data_texture
             .update(&mut self.device, &mut frame.node_data);
         self.device
             .bind_texture(TextureSampler::ClipScrollNodes, &self.node_data_texture.texture);
 
@@ -3597,20 +3645,17 @@ impl Renderer {
         if frame.passes.is_empty() {
             return;
         }
 
         self.device.disable_depth_write();
         self.device.disable_stencil();
         self.device.set_blend(false);
 
-        self.prepare_frame(frame);
-
-        let base_color_target_count = self.color_render_targets.len();
-        let base_alpha_target_count = self.alpha_render_targets.len();
+        self.bind_frame_data(frame);
 
         for (pass_index, pass) in frame.passes.iter_mut().enumerate() {
             self.texture_resolver.bind(
                 &SourceTexture::CacheA8,
                 TextureSampler::CacheA8,
                 &mut self.device,
             );
             self.texture_resolver.bind(
@@ -3691,34 +3736,31 @@ impl Renderer {
 
                     (alpha.texture.take(), color.texture.take())
                 }
             };
 
             self.texture_resolver.end_pass(
                 cur_alpha,
                 cur_color,
-                &mut self.alpha_render_targets,
-                &mut self.color_render_targets,
+                &mut self.render_target_pool,
             );
 
             // After completing the first pass, make the A8 target available as an
             // input to any subsequent passes.
             if pass_index == 0 {
                 if let Some(shared_alpha_texture) =
                     self.texture_resolver.resolve(&SourceTexture::CacheA8)
                 {
                     self.device
                         .bind_texture(TextureSampler::SharedCacheA8, shared_alpha_texture);
                 }
             }
         }
 
-        self.color_render_targets[base_color_target_count..].reverse();
-        self.alpha_render_targets[base_alpha_target_count..].reverse();
         self.draw_render_target_debug(framebuffer_size);
         self.draw_texture_cache_debug(framebuffer_size);
 
         // Garbage collect any frame outputs that weren't used this frame.
         let device = &mut self.device;
         self.output_targets
             .retain(|_, target| if target.last_access != frame_id {
                 device.delete_fbo(target.fbo_id);
@@ -3774,33 +3816,29 @@ impl Renderer {
     fn draw_render_target_debug(&mut self, framebuffer_size: DeviceUintSize) {
         if !self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) {
             return;
         }
 
         let mut spacing = 16;
         let mut size = 512;
         let fb_width = framebuffer_size.width as i32;
-        let num_layers: i32 = self.color_render_targets
+        let num_layers: i32 = self.render_target_pool
             .iter()
-            .chain(self.alpha_render_targets.iter())
             .map(|texture| texture.get_render_target_layer_count() as i32)
             .sum();
 
         if num_layers * (size + spacing) > fb_width {
             let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32;
             size = (size as f32 * factor) as i32;
             spacing = (spacing as f32 * factor) as i32;
         }
 
         let mut target_index = 0;
-        for texture in self.color_render_targets
-            .iter()
-            .chain(self.alpha_render_targets.iter())
-        {
+        for texture in &self.render_target_pool {
             let dimensions = texture.get_dimensions();
             let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(), dimensions.to_i32());
 
             let layer_count = texture.get_render_target_layer_count();
             for layer_index in 0 .. layer_count {
                 self.device
                     .bind_read_target(Some((texture, layer_index as i32)));
                 let x = fb_width - (spacing + size) * (target_index + 1);
@@ -3901,20 +3939,17 @@ impl Renderer {
         //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
         self.device.begin_frame();
         self.gpu_cache_texture.deinit(&mut self.device);
         if let Some(dither_matrix_texture) = self.dither_matrix_texture {
             self.device.delete_texture(dither_matrix_texture);
         }
         self.node_data_texture.deinit(&mut self.device);
         self.render_task_texture.deinit(&mut self.device);
-        for texture in self.alpha_render_targets {
-            self.device.delete_texture(texture);
-        }
-        for texture in self.color_render_targets {
+        for texture in self.render_target_pool {
             self.device.delete_texture(texture);
         }
         self.device.delete_pbo(self.texture_cache_upload_pbo);
         self.texture_resolver.deinit(&mut self.device);
         self.device.delete_vao(self.prim_vao);
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
         self.debug.deinit(&mut self.device);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,15 +1,16 @@
 /* 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::{BorderRadiusKind, ClipId, ColorF, DeviceIntPoint, ImageKey};
 use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{DocumentLayer, ExternalImageType, FilterOp, FontRenderMode, ImageRendering};
+use api::{DocumentLayer, ExternalImageType, FilterOp, FontRenderMode};
+use api::{ImageFormat, ImageRendering};
 use api::{LayerRect, MixBlendMode, PipelineId};
 use api::{TileOffset, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide};
 use clip::{ClipSource, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId};
 use device::Texture;
 use euclid::{TypedTransform3D, vec3};
@@ -1253,27 +1254,30 @@ pub trait RenderTarget {
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum RenderTargetKind {
     Color, // RGBA32
     Alpha, // R8
 }
 
 pub struct RenderTargetList<T> {
     screen_size: DeviceIntSize,
+    pub format: ImageFormat,
     pub max_size: DeviceUintSize,
     pub targets: Vec<T>,
     pub texture: Option<Texture>,
 }
 
 impl<T: RenderTarget> RenderTargetList<T> {
     fn new(
         screen_size: DeviceIntSize,
+        format: ImageFormat,
     ) -> Self {
         RenderTargetList {
             screen_size,
+            format,
             max_size: DeviceUintSize::new(MIN_TARGET_SIZE, MIN_TARGET_SIZE),
             targets: Vec::new(),
             texture: None,
         }
     }
 
     fn build(
         &mut self,
@@ -1748,18 +1752,18 @@ impl RenderPass {
             tasks: vec![],
             dynamic_tasks: FastHashMap::default(),
         }
     }
 
     pub fn new_off_screen(screen_size: DeviceIntSize) -> Self {
         RenderPass {
             kind: RenderPassKind::OffScreen {
-                color: RenderTargetList::new(screen_size),
-                alpha: RenderTargetList::new(screen_size),
+                color: RenderTargetList::new(screen_size, ImageFormat::BGRA8),
+                alpha: RenderTargetList::new(screen_size, ImageFormat::A8),
             },
             tasks: vec![],
             dynamic_tasks: FastHashMap::default(),
         }
     }
 
     pub fn add_render_task(
         &mut self,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -205,17 +205,17 @@ pub fn get_normal(x: f32) -> Option<f32>
     if x.is_normal() {
         Some(x)
     } else {
         None
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
-#[repr(u8)]
+#[repr(u32)]
 pub enum TransformedRectKind {
     AxisAligned = 0,
     Complex = 1,
 }
 
 #[derive(Debug, Clone)]
 pub struct TransformedRect {
     pub local_rect: LayerRect,