Bug 1395637. Update webrender to commit 0875e21c4e80e19a3faeabf46c445a7a1cd59212. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 01 Sep 2017 09:38:14 -0400
changeset 428499 c084a72939ccdcb66605ec39fe493d48bdbac705
parent 428498 807e9eadd5c1a63419ae951b95f8e043d573b458
child 428500 5ca9c4d3658cda11a748e7e8722f3fc51fc80b5d
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1395637
milestone57.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 1395637. Update webrender to commit 0875e21c4e80e19a3faeabf46c445a7a1cd59212. r=jrmuizel
gfx/webrender/examples/blob.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_box_shadow.fs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender_api/src/display_list.rs
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -1,14 +1,12 @@
 /* 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/. */
 
-extern crate app_units;
-extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate rayon;
 
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -196,14 +196,14 @@ pub fn main_wrapper(example: &mut Exampl
                         );
                         api.generate_frame(document_id, None);
                     }
                 }
             }
         }
 
         renderer.update();
-        renderer.render(DeviceUintSize::new(width, height));
+        renderer.render(DeviceUintSize::new(width, height)).unwrap();
         window.swap_buffers().ok();
     }
 
     renderer.deinit();
 }
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -1,14 +1,12 @@
 /* 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/. */
 
-extern crate app_units;
-extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::Example;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -12,16 +12,19 @@
 
 #define SUBPX_DIR_NONE        0
 #define SUBPX_DIR_HORIZONTAL  1
 #define SUBPX_DIR_VERTICAL    2
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
+// An A8 target for standalone tasks that is available to all passes.
+uniform sampler2DArray sSharedCacheA8;
+
 uniform sampler2D sGradients;
 
 struct RectWithSize {
     vec2 p0;
     vec2 size;
 };
 
 struct RectWithEndpoint {
@@ -762,17 +765,17 @@ BoxShadow fetch_boxshadow(int address) {
 }
 
 BoxShadow fetch_boxshadow_direct(ivec2 address) {
     vec4 data[4] = fetch_from_resource_cache_4_direct(address);
     return BoxShadow(data[0], data[1], data[2], data[3]);
 }
 
 void write_clip(vec2 global_pos, ClipArea area) {
-    vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
+    vec2 texture_size = vec2(textureSize(sSharedCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
     vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
     vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
@@ -801,17 +804,17 @@ vec2 init_transform_fs(vec3 local_pos, o
 
 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:
-        all(inside) ? textureLod(sCacheA8, vClipMaskUv, 0.0).r : 0.0;
+        all(inside) ? textureLod(sSharedCacheA8, vClipMaskUv, 0.0).r : 0.0;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/res/ps_box_shadow.fs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.fs.glsl
@@ -13,11 +13,11 @@ void main(void) {
     // shadow corner. This can happen, for example, when
     // drawing the outer parts of an inset box shadow.
     uv = clamp(uv, vec2(0.0), vec2(1.0));
 
     // Map the unit UV to the actual UV rect in the cache.
     uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
 
     // Modulate the box shadow by the color.
-    float mask = texture(sColor1, vec3(uv, vUv.z)).r;
+    float mask = texture(sSharedCacheA8, vec3(uv, vUv.z)).r;
     oFragColor = clip_scale * dither(vColor * vec4(1.0, 1.0, 1.0, mask));
 }
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -22,15 +22,15 @@ void main(void) {
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
 
     vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
     vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
-    vec2 texture_size = vec2(textureSize(sCacheA8, 0));
+    vec2 texture_size = vec2(textureSize(sSharedCacheA8, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
 
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -246,17 +246,17 @@ impl ClipScrollTree {
         };
         self.update_node_transform(root_reference_frame_id, &state);
     }
 
     fn update_node_transform(&mut self, layer_id: ClipId, state: &TransformUpdateState) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let (state, node_children) = {
-            let mut node = match self.nodes.get_mut(&layer_id) {
+            let node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
             };
             node.update_transform(&state);
 
             // The transformation we are passing is the transformation of the parent
             // reference frame and the offset is the accumulated offset of all the nodes
             // between us and the parent reference frame. If we are a reference frame,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -404,17 +404,17 @@ impl FontContext {
         }
 
         // We need to invert the pixels back since right now
         // transparent pixels are actually opaque white.
         for i in 0..metrics.rasterized_height {
             let current_height = (i * metrics.rasterized_width * 4) as usize;
             let end_row = current_height + (metrics.rasterized_width as usize * 4);
 
-            for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
+            for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
                 pixel[0] = 255 - pixel[0];
                 pixel[1] = 255 - pixel[1];
                 pixel[2] = 255 - pixel[2];
 
                 pixel[3] = match font.render_mode {
                     FontRenderMode::Subpixel => 255,
                     _ => {
                         assert_eq!(pixel[0], pixel[1]);
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontInstance, FontInstancePlatformOptions, FontKey, FontRenderMode};
-use api::{GlyphDimensions, GlyphKey, GlyphOptions, SubpixelDirection};
+use api::{GlyphDimensions, GlyphKey};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use internal_types::FastHashMap;
 
 use dwrote;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -171,17 +171,17 @@ impl RenderBackend {
             notifier,
             recorder,
 
             enable_render_on_scroll,
         }
     }
 
     fn process_document(&mut self, document_id: DocumentId, message: DocumentMsg,
-                        frame_counter: u32, mut profile_counters: &mut BackendProfileCounters)
+                        frame_counter: u32, profile_counters: &mut BackendProfileCounters)
                         -> DocumentOp
     {
         let doc = self.documents.get_mut(&document_id).expect("No document?");
 
         match message {
             DocumentMsg::SetPageZoom(factor) => {
                 doc.page_zoom_factor = factor.get();
                 DocumentOp::Nop
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -63,16 +63,26 @@ impl RenderTaskTree {
             RenderTaskLocation::Fixed => {
                 debug_assert!(pass_index == passes.len() - 1);
             }
             RenderTaskLocation::Dynamic(..) => {
                 debug_assert!(pass_index < passes.len() - 1);
             }
         }
 
+        // If this task can be shared between multiple
+        // passes, render it in the first pass so that
+        // it is available to all subsequent passes.
+        let pass_index = if task.is_shared() {
+            debug_assert!(task.children.is_empty());
+            0
+        } else {
+            pass_index
+        };
+
         let pass = &mut passes[pass_index];
         pass.add_render_task(id);
     }
 
     pub fn get(&self, id: RenderTaskId) -> &RenderTask {
         &self.tasks[id.0 as usize]
     }
 
@@ -509,16 +519,39 @@ impl RenderTask {
             RenderTaskKind::BoxShadow(..) => RenderTargetKind::Alpha,
 
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: target_kind() called on invalidated task");
             }
         }
     }
 
+    // Check if this task wants to be made available as an input
+    // to all passes (except the first) in the render task tree.
+    // To qualify for this, the task needs to have no children / dependencies.
+    // Currently, this is only supported for A8 targets, but it can be
+    // trivially extended to also support RGBA8 targets in the future
+    // if we decide that is useful.
+    pub fn is_shared(&self) -> bool {
+        match self.kind {
+            RenderTaskKind::Alpha(..) |
+            RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::VerticalBlur(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::HorizontalBlur(..) => false,
+
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::BoxShadow(..) => true,
+
+            RenderTaskKind::Alias(..) => {
+                panic!("BUG: is_shared() called on aliased task");
+            }
+        }
+    }
+
     pub fn set_alias(&mut self, id: RenderTaskId) {
         debug_assert!(self.cache_key.is_some());
         // TODO(gw): We can easily handle invalidation of tasks that
         //           contain children in the future. Since we don't
         //           have any cases of that yet, just assert to simplify
         //           the current implementation.
         debug_assert!(self.children.is_empty());
         self.kind = RenderTaskKind::Alias(id);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -136,16 +136,20 @@ enum TextureSampler {
     Color1,
     Color2,
     CacheA8,
     CacheRGBA8,
     ResourceCache,
     Layers,
     RenderTasks,
     Dither,
+    // A special sampler that is bound to the A8 output of
+    // the *first* pass. Items rendered in this target are
+    // available as inputs to tasks in any subsequent pass.
+    SharedCacheA8,
 }
 
 impl TextureSampler {
     fn color(n: usize) -> TextureSampler {
         match n {
             0 => TextureSampler::Color0,
             1 => TextureSampler::Color1,
             2 => TextureSampler::Color2,
@@ -163,16 +167,17 @@ impl Into<TextureSlot> for TextureSample
             TextureSampler::Color1 => TextureSlot(1),
             TextureSampler::Color2 => TextureSlot(2),
             TextureSampler::CacheA8 => TextureSlot(3),
             TextureSampler::CacheRGBA8 => TextureSlot(4),
             TextureSampler::ResourceCache => TextureSlot(5),
             TextureSampler::Layers => TextureSlot(6),
             TextureSampler::RenderTasks => TextureSlot(7),
             TextureSampler::Dither => TextureSlot(8),
+            TextureSampler::SharedCacheA8 => TextureSlot(9),
         }
     }
 }
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
@@ -389,24 +394,41 @@ impl SourceTextureResolver {
     fn deinit(self, device: &mut Device) {
         device.delete_texture(self.dummy_cache_texture);
 
         for texture in self.cache_texture_map {
             device.delete_texture(texture);
         }
     }
 
-    fn set_cache_textures(&mut self,
-                          a8_texture: Option<Texture>,
-                          rgba8_texture: Option<Texture>) {
-        // todo(gw): make the texture recycling cleaner...
-        debug_assert!(self.cache_a8_texture.is_none());
-        debug_assert!(self.cache_rgba8_texture.is_none());
-        self.cache_a8_texture = a8_texture;
-        self.cache_rgba8_texture = rgba8_texture;
+    fn end_pass(&mut self,
+                pass_index: usize,
+                pass_count: usize,
+                mut a8_texture: Option<Texture>,
+                mut rgba8_texture: Option<Texture>,
+                a8_pool: &mut Vec<Texture>,
+                rgba8_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());
+
+        if pass_index == pass_count-1 {
+            // On the last pass, return the textures from this pass to the pool.
+            if let Some(texture) = rgba8_texture.take() {
+                rgba8_pool.push(texture);
+            }
+            if let Some(texture) = a8_texture.take() {
+                a8_pool.push(texture);
+            }
+        } else {
+            // We have another pass to process, make these textures available
+            // as inputs to the next pass.
+            self.cache_rgba8_texture = rgba8_texture.take();
+            self.cache_a8_texture = a8_texture.take();
+        }
     }
 
     // Bind a source texture to the device.
     fn bind(&self,
             texture_id: &SourceTexture,
             sampler: TextureSampler,
             device: &mut Device) {
         match *texture_id {
@@ -711,19 +733,24 @@ impl LazilyCompiledShader {
 
         if precache {
             try!{ shader.get(device) };
         }
 
         Ok(shader)
     }
 
-    fn bind(&mut self, device: &mut Device, projection: &Transform3D<f32>) {
-        let program = self.get(device)
-                          .expect("Unable to get shader!");
+    fn bind(&mut self, device: &mut Device, projection: &Transform3D<f32>, renderer_errors: &mut Vec<RendererError>) {
+        let program = match self.get(device) {
+            Ok(program) => program,
+            Err(e) => {
+                renderer_errors.push(RendererError::from(e));
+                return;
+            }
+        };
         device.bind_program(program);
         device.set_uniforms(program, projection);
     }
 
     fn get(&mut self, device: &mut Device) -> Result<&Program, ShaderError> {
         if self.program.is_none() {
             let program = try!{
                 match self.kind {
@@ -803,20 +830,21 @@ impl PrimitiveShader {
             simple,
             transform,
         })
     }
 
     fn bind(&mut self,
             device: &mut Device,
             transform_kind: TransformedRectKind,
-            projection: &Transform3D<f32>) {
+            projection: &Transform3D<f32>,
+            renderer_errors: &mut Vec<RendererError>) {
         match transform_kind {
-            TransformedRectKind::AxisAligned => self.simple.bind(device, projection),
-            TransformedRectKind::Complex => self.transform.bind(device, projection),
+            TransformedRectKind::AxisAligned => self.simple.bind(device, projection, renderer_errors),
+            TransformedRectKind::Complex => self.transform.bind(device, projection, renderer_errors),
         }
     }
 
     fn deinit(self, device: &mut Device) {
         self.simple.deinit(device);
         self.transform.deinit(device);
     }
 }
@@ -849,16 +877,17 @@ fn create_prim_shader(name: &'static str
             ("sColor1", TextureSampler::Color1),
             ("sColor2", TextureSampler::Color2),
             ("sDither", TextureSampler::Dither),
             ("sCacheA8", TextureSampler::CacheA8),
             ("sCacheRGBA8", TextureSampler::CacheRGBA8),
             ("sLayers", TextureSampler::Layers),
             ("sRenderTasks", TextureSampler::RenderTasks),
             ("sResourceCache", TextureSampler::ResourceCache),
+            ("sSharedCacheA8", TextureSampler::SharedCacheA8),
         ]);
     }
 
     program
 }
 
 fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
     let prefix = format!("#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n
@@ -870,16 +899,17 @@ fn create_clip_shader(name: &'static str
     let program = device.create_program(name, &prefix, &DESC_CLIP);
 
     if let Ok(ref program) = program {
         device.bind_shader_samplers(program, &[
             ("sColor0", TextureSampler::Color0),
             ("sLayers", TextureSampler::Layers),
             ("sRenderTasks", TextureSampler::RenderTasks),
             ("sResourceCache", TextureSampler::ResourceCache),
+            ("sSharedCacheA8", TextureSampler::SharedCacheA8),
         ]);
     }
 
     program
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum ReadPixelsFormat {
@@ -978,35 +1008,37 @@ pub struct Renderer {
     texture_cache_upload_pbo: PBOId,
 
     dither_matrix_texture: Option<Texture>,
 
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
+    renderer_errors: Vec<RendererError>,
+
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 }
 
 #[derive(Debug)]
-pub enum InitError {
+pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
     MaxTextureSize,
 }
 
-impl From<ShaderError> for InitError {
-    fn from(err: ShaderError) -> Self { InitError::Shader(err) }
+impl From<ShaderError> for RendererError {
+    fn from(err: ShaderError) -> Self { RendererError::Shader(err) }
 }
 
-impl From<std::io::Error> for InitError {
-    fn from(err: std::io::Error) -> Self { InitError::Thread(err) }
+impl From<std::io::Error> for RendererError {
+    fn from(err: std::io::Error) -> Self { RendererError::Thread(err) }
 }
 
 impl Renderer {
     /// Initializes webrender and creates a `Renderer` and `RenderApiSender`.
     ///
     /// # Examples
     /// Initializes a `Renderer` with some reasonable values. For more information see
     /// [`RendererOptions`][rendereroptions].
@@ -1017,17 +1049,17 @@ impl Renderer {
     /// let opts = webrender::RendererOptions {
     ///    device_pixel_ratio: 1.0,
     ///    resource_override_path: None,
     ///    enable_aa: false,
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
-    pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), InitError> {
+    pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), RendererError> {
 
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let notifier = Arc::new(Mutex::new(None));
         let debug_server = DebugServer::new(api_tx.clone());
@@ -1045,17 +1077,17 @@ impl Renderer {
 
         let device_max_size = device.max_texture_size();
         // 512 is the minimum that the texture cache can work with.
         // Broken GL contexts can return a max texture size of zero (See #1260). Better to
         // gracefully fail now than panic as soon as a texture is allocated.
         let min_texture_size = 512;
         if device_max_size < min_texture_size {
             println!("Device reporting insufficient max texture size ({})", device_max_size);
-            return Err(InitError::MaxTextureSize);
+            return Err(RendererError::MaxTextureSize);
         }
         let max_device_size = cmp::max(
             cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size)),
             min_texture_size
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
@@ -1497,16 +1529,17 @@ impl Renderer {
             pipeline_epoch_map: FastHashMap::default(),
             dither_matrix_texture,
             external_image_handler: None,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             texture_cache_upload_pbo,
             texture_resolver,
+            renderer_errors: Vec::new(),
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
     pub fn get_max_texture_size(&self) -> u32 {
         self.max_texture_size
@@ -1706,17 +1739,17 @@ impl Renderer {
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     /// Renders the current frame.
     ///
     /// A Frame is supplied by calling [`generate_frame()`][genframe].
     /// [genframe]: ../../webrender_api/struct.DocumentApi.html#method.generate_frame
-    pub fn render(&mut self, framebuffer_size: DeviceUintSize) {
+    pub fn render(&mut self, framebuffer_size: DeviceUintSize) -> Result<(), Vec<RendererError>> {
         profile_scope!("render");
 
         if let Some(mut frame) = self.current_frame.take() {
             if let Some(ref mut frame) = frame.frame {
                 let mut profile_timers = RendererProfileTimers::new();
                 let mut profile_samplers = Vec::new();
 
                 {
@@ -1801,16 +1834,21 @@ impl Renderer {
                     self.device.end_frame();
                 }
                 self.last_time = current_time;
             }
 
             // Restore frame - avoid borrow checker!
             self.current_frame = Some(frame);
         }
+        if !self.renderer_errors.is_empty() {
+            let errors = mem::replace(&mut self.renderer_errors, Vec::new());
+            return Err(errors);
+        }
+        Ok(())
     }
 
     pub fn layers_are_bouncing_back(&self) -> bool {
         match self.current_frame {
             None => false,
             Some(ref current_frame) => !current_frame.layers_bouncing_back.is_empty(),
         }
     }
@@ -1948,99 +1986,99 @@ impl Renderer {
                           BlendMode::Alpha |
                           BlendMode::PremultipliedAlpha |
                           BlendMode::Subpixel(..) => true,
                           BlendMode::None => false,
                       });
 
         let marker = match key.kind {
             AlphaBatchKind::Composite { .. } => {
-                self.ps_composite.bind(&mut self.device, projection);
+                self.ps_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_COMPOSITE
             }
             AlphaBatchKind::HardwareComposite => {
-                self.ps_hw_composite.bind(&mut self.device, projection);
+                self.ps_hw_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_HW_COMPOSITE
             }
             AlphaBatchKind::SplitComposite => {
-                self.ps_split_composite.bind(&mut self.device, projection);
+                self.ps_split_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_SPLIT_COMPOSITE
             }
             AlphaBatchKind::Blend => {
-                self.ps_blend.bind(&mut self.device, projection);
+                self.ps_blend.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BLEND
             }
             AlphaBatchKind::Rectangle => {
                 if needs_clipping {
-                    self.ps_rectangle_clip.bind(&mut self.device, transform_kind, projection);
+                    self.ps_rectangle_clip.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 } else {
-                    self.ps_rectangle.bind(&mut self.device, transform_kind, projection);
+                    self.ps_rectangle.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 }
                 GPU_TAG_PRIM_RECT
             }
             AlphaBatchKind::Line => {
-                self.ps_line.bind(&mut self.device, transform_kind, projection);
+                self.ps_line.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_LINE
             }
             AlphaBatchKind::TextRun => {
                 match key.blend_mode {
                     BlendMode::Subpixel(..) => {
-                        self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection);
+                        self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                     }
                     BlendMode::Alpha |
                     BlendMode::PremultipliedAlpha |
                     BlendMode::None => {
-                        self.ps_text_run.bind(&mut self.device, transform_kind, projection);
+                        self.ps_text_run.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                     }
                 };
                 GPU_TAG_PRIM_TEXT_RUN
             }
             AlphaBatchKind::Image(image_buffer_kind) => {
                 self.ps_image[image_buffer_kind as usize]
                     .as_mut()
                     .expect("Unsupported image shader kind")
-                    .bind(&mut self.device, transform_kind, projection);
+                    .bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_IMAGE
             }
             AlphaBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
                 let shader_index = Renderer::get_yuv_shader_index(image_buffer_kind,
                                                                   format,
                                                                   color_space);
                 self.ps_yuv_image[shader_index]
                     .as_mut()
                     .expect("Unsupported YUV shader kind")
-                    .bind(&mut self.device, transform_kind, projection);
+                    .bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_YUV_IMAGE
             }
             AlphaBatchKind::BorderCorner => {
-                self.ps_border_corner.bind(&mut self.device, transform_kind, projection);
+                self.ps_border_corner.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BORDER_CORNER
             }
             AlphaBatchKind::BorderEdge => {
-                self.ps_border_edge.bind(&mut self.device, transform_kind, projection);
+                self.ps_border_edge.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BORDER_EDGE
             }
             AlphaBatchKind::AlignedGradient => {
-                self.ps_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_GRADIENT
             }
             AlphaBatchKind::AngleGradient => {
-                self.ps_angle_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_angle_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_ANGLE_GRADIENT
             }
             AlphaBatchKind::RadialGradient => {
-                self.ps_radial_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_radial_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_RADIAL_GRADIENT
             }
             AlphaBatchKind::BoxShadow => {
-                self.ps_box_shadow.bind(&mut self.device, transform_kind, projection);
+                self.ps_box_shadow.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BOX_SHADOW
             }
             AlphaBatchKind::CacheImage => {
-                self.ps_cache_image.bind(&mut self.device, transform_kind, projection);
+                self.ps_cache_image.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_CACHE_IMAGE
             }
         };
 
         // Handle special case readback for composites.
         match key.kind {
             AlphaBatchKind::Composite { task_id, source_id, backdrop_id } => {
                 // composites can't be grouped together because
@@ -2146,17 +2184,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_BLUR);
 
             self.device.set_blend(false);
-            self.cs_blur.bind(&mut self.device, projection);
+            self.cs_blur.bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(&target.vertical_blurs,
                                           VertexArrayKind::Blur,
                                           &BatchTextures::no_texture());
             }
 
             if !target.horizontal_blurs.is_empty() {
@@ -2172,29 +2210,29 @@ impl Renderer {
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_TEXT_RUN);
-            self.cs_text_run.bind(&mut self.device, projection);
+            self.cs_text_run.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.text_run_cache_prims,
                                       VertexArrayKind::Primitive,
                                       &target.text_run_textures);
         }
         if !target.line_cache_prims.is_empty() {
             // TODO(gw): Technically, we don't need blend for solid
             //           lines. We could check that here?
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
-            self.cs_line.bind(&mut self.device, projection);
+            self.cs_line.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.line_cache_prims,
                                       VertexArrayKind::Primitive,
                                       &BatchTextures::no_texture());
         }
 
         //TODO: record the pixel count for cached primitives
 
         if !target.alpha_batcher.is_empty() {
@@ -2288,76 +2326,76 @@ impl Renderer {
                                           None,
                                           target.used_rect());
         }
 
         // Draw any box-shadow caches for this target.
         if !target.box_shadow_cache_prims.is_empty() {
             self.device.set_blend(false);
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
-            self.cs_box_shadow.bind(&mut self.device, projection);
+            self.cs_box_shadow.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.box_shadow_cache_prims,
                                       VertexArrayKind::CacheBoxShadow,
                                       &BatchTextures::no_texture());
         }
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders [clear]");
                 self.device.set_blend(false);
-                self.cs_clip_border.bind(&mut self.device, projection);
+                self.cs_clip_border.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.border_clears,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // Draw any dots or dashes for border corners.
             if !target.clip_batcher.borders.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders");
                 // We are masking in parts of the corner (dots or dashes) here.
                 // Blend mode is set to max to allow drawing multiple dots.
                 // The individual dots and dashes in a border never overlap, so using
                 // a max blend mode here is fine.
                 self.device.set_blend(true);
                 self.device.set_blend_mode_max();
-                self.cs_clip_border.bind(&mut self.device, projection);
+                self.cs_clip_border.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.borders,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
-                self.cs_clip_rectangle.bind(&mut self.device, projection);
+                self.cs_clip_rectangle.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.rectangles,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
             // draw image masks
             for (mask_texture_id, items) in target.clip_batcher.images.iter() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         SourceTexture::Invalid,
                         SourceTexture::Invalid,
                     ]
                 };
-                self.cs_clip_image.bind(&mut self.device, projection);
+                self.cs_clip_image.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(items,
                                           VertexArrayKind::Clip,
                                           &textures);
             }
         }
 
         self.gpu_profile.done_sampler();
     }
@@ -2482,17 +2520,18 @@ impl Renderer {
         }
 
         self.layer_texture.update(&mut self.device, &mut frame.layer_texture_data);
         self.render_task_texture.update(&mut self.device, &mut frame.render_tasks.task_data);
 
         self.device.bind_texture(TextureSampler::Layers, &self.layer_texture.texture);
         self.device.bind_texture(TextureSampler::RenderTasks, &self.render_task_texture.texture);
 
-        self.texture_resolver.set_cache_textures(None, None);
+        debug_assert!(self.texture_resolver.cache_a8_texture.is_none());
+        debug_assert!(self.texture_resolver.cache_rgba8_texture.is_none());
     }
 
     fn draw_tile_frame(&mut self,
                        frame: &mut Frame,
                        framebuffer_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(self.device.rc_gl(), "tile frame draw");
 
         // Some tests use a restricted viewport smaller than the main screen size.
@@ -2504,18 +2543,19 @@ impl Renderer {
         self.device.disable_depth_write();
         self.device.disable_stencil();
         self.device.set_blend(false);
 
         if frame.passes.is_empty() {
             self.device.clear_target(Some(self.clear_color.to_array()), Some(1.0));
         } else {
             self.start_frame(frame);
-
-            for pass in &mut frame.passes {
+            let pass_count = frame.passes.len();
+
+            for (pass_index, pass) in frame.passes.iter_mut().enumerate() {
                 let size;
                 let clear_color;
                 let projection;
 
                 if pass.is_framebuffer {
                     clear_color = if self.clear_framebuffer || needs_clear {
                         Some(frame.background_color.map_or(self.clear_color.to_array(), |color| {
                             color.to_array()
@@ -2559,35 +2599,30 @@ impl Renderer {
                                            target,
                                            *size,
                                            clear_color,
                                            &frame.render_tasks,
                                            &projection);
 
                 }
 
-                // Return the texture IDs to the pool for next frame.
-                if let Some(texture) = self.texture_resolver.cache_rgba8_texture.take() {
-                    self.color_render_targets.push(texture);
-                }
-                if let Some(texture) = self.texture_resolver.cache_a8_texture.take() {
-                    self.alpha_render_targets.push(texture);
+                self.texture_resolver.end_pass(pass_index,
+                                               pass_count,
+                                               pass.alpha_texture.take(),
+                                               pass.color_texture.take(),
+                                               &mut self.alpha_render_targets,
+                                               &mut self.color_render_targets);
+
+                // 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.texture_resolver.set_cache_textures(pass.alpha_texture.take(),
-                                                         pass.color_texture.take());
-
-            }
-
-            // Return the texture IDs to the pool for next frame.
-            if let Some(texture) = self.texture_resolver.cache_rgba8_texture.take() {
-                self.color_render_targets.push(texture);
-            }
-            if let Some(texture) = self.texture_resolver.cache_a8_texture.take() {
-                self.alpha_render_targets.push(texture);
             }
 
             self.color_render_targets.reverse();
             self.alpha_render_targets.reverse();
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
         }
 
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -621,17 +621,17 @@ impl DisplayListBuilder {
             self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
         }
     }
 
     fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
                                                   font_key: FontInstanceKey,
                                                   color: ColorF,
                                                   glyphs: I) {
-        let mut font_glyphs = self.glyphs.entry((font_key, color))
+        let font_glyphs = self.glyphs.entry((font_key, color))
                                          .or_insert(FastHashSet::default());
 
         font_glyphs.extend(glyphs);
     }
 
     // Gradients can be defined with stops outside the range of [0, 1]
     // when this happens the gradient needs to be normalized by adjusting
     // the gradient stops and gradient line into an equivalent gradient