Bug 1528820 - Work around Adreno bug when blitting to texture array. r=kvark
authorJamie Nicol <jnicol@mozilla.com>
Thu, 07 Mar 2019 14:02:50 +0000
changeset 520757 cb745568d893c43e5ca7cb7dcda7d4c8464bd62b
parent 520756 912d0497234a65bafe04dfa5df6789beebb91d8e
child 520758 fa9146245803ec512e886835accb896c3d80f981
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1528820
milestone67.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 1528820 - Work around Adreno bug when blitting to texture array. r=kvark There is a bug on Adreno GPUs where glBlitFramebuffers always writes to the 0th layer of a texture array, regardless of which layer is actually attached to the draw framebuffer. With picture caching enabled on webrender, the cached pictures were all being drawn to the 0th layer of the picture cache texture array, leaving the other layers blank. This was resulting in the wrong content being drawn on one tile of the screen, and the rest being black. This works around this by blitting to an intermediate renderbuffer, then using glCopyTexSubImage3D to copy from the renderbuffer to the correct texture layer. Differential Revision: https://phabricator.services.mozilla.com/D22305
gfx/wr/webrender/src/device/gl.rs
--- a/gfx/wr/webrender/src/device/gl.rs
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -515,16 +515,19 @@ pub struct Texture {
     /// the depth buffer has never been requested.
     ///
     /// Note that we always fill fbos, and then lazily create fbos_with_depth
     /// when needed. We could make both lazy (i.e. render targets would have one
     /// or the other, but not both, unless they were actually used in both
     /// configurations). But that would complicate a lot of logic in this module,
     /// and FBOs are cheap enough to create.
     fbos_with_depth: Vec<FBOId>,
+    /// If we are unable to blit directly to a texture array then we need
+    /// an intermediate renderbuffer.
+    blit_workaround_buffer: Option<(RBOId, FBOId)>,
     last_frame_used: GpuFrameId,
 }
 
 impl Texture {
     pub fn get_dimensions(&self) -> DeviceIntSize {
         self.size
     }
 
@@ -871,16 +874,21 @@ pub struct UniformLocation(gl::GLint);
 impl UniformLocation {
     pub const INVALID: Self = UniformLocation(-1);
 }
 
 pub struct Capabilities {
     pub supports_multisampling: bool,
     /// Whether the function glCopyImageSubData is available.
     pub supports_copy_image_sub_data: bool,
+    /// Whether we are able to use glBlitFramebuffers with the draw fbo
+    /// bound to a non-0th layer of a texture array. This is buggy on
+    /// Adreno devices.
+    pub supports_blit_to_texture_array: bool,
+
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error message
     Link(String, String),        // name, error message
 }
 
@@ -1205,16 +1213,20 @@ impl Device {
                     TexStorageUsage::Never
                 },
             )
         };
 
         let supports_copy_image_sub_data = supports_extension(&extensions, "GL_EXT_copy_image") ||
             supports_extension(&extensions, "GL_ARB_copy_image");
 
+        // Due to a bug on Adreno devices, blitting to an fbo bound to
+        // a non-0th layer of a texture array is not supported.
+        let supports_blit_to_texture_array = !renderer_name.starts_with("Adreno");
+
         // On Adreno GPUs PBO texture upload is only performed asynchronously
         // if the stride of the data in the PBO is a multiple of 256 bytes.
         // Other platforms may have similar requirements and should be added
         // here.
         // The default value should be 4.
         let optimal_pbo_stride = if renderer_name.contains("Adreno") {
             NonZeroUsize::new(256).unwrap()
         } else {
@@ -1225,16 +1237,17 @@ impl Device {
             gl,
             resource_override_path,
             upload_method,
             inside_frame: false,
 
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
                 supports_copy_image_sub_data,
+                supports_blit_to_texture_array,
             },
 
             bgra_format_internal,
             bgra_format_external,
 
             depth_targets: FastHashMap::default(),
 
             bound_textures: [0; 16],
@@ -1692,16 +1705,17 @@ impl Device {
             id: self.gl.gen_textures(1)[0],
             target: get_gl_target(target),
             size: DeviceIntSize::new(width, height),
             layer_count,
             format,
             filter,
             fbos: vec![],
             fbos_with_depth: vec![],
+            blit_workaround_buffer: None,
             last_frame_used: self.frame_id,
             flags: TextureFlags::default(),
         };
         self.bind_texture(DEFAULT_TEXTURE, &texture);
         self.set_texture_parameters(texture.target, filter);
 
         // Allocate storage.
         let desc = self.gl_describe_format(texture.format);
@@ -1778,16 +1792,38 @@ impl Device {
         // Set up FBOs, if required.
         if let Some(rt_info) = render_target {
             self.init_fbos(&mut texture, false);
             if rt_info.has_depth {
                 self.init_fbos(&mut texture, true);
             }
         }
 
+        // Set up intermediate buffer for blitting to texture, if required.
+        if texture.layer_count > 1 && !self.capabilities.supports_blit_to_texture_array {
+            let rbo = RBOId(self.gl.gen_renderbuffers(1)[0]);
+            let fbo = FBOId(self.gl.gen_framebuffers(1)[0]);
+            self.gl.bind_renderbuffer(gl::RENDERBUFFER, rbo.0);
+            self.gl.renderbuffer_storage(
+                gl::RENDERBUFFER,
+                self.matching_renderbuffer_format(texture.format),
+                texture.size.width as _,
+                texture.size.height as _
+            );
+
+            self.bind_draw_target_impl(fbo);
+            self.gl.framebuffer_renderbuffer(
+                gl::DRAW_FRAMEBUFFER,
+                gl::COLOR_ATTACHMENT0,
+                gl::RENDERBUFFER,
+                rbo.0
+            );
+            texture.blit_workaround_buffer = Some((rbo, fbo));
+        }
+
         record_gpu_alloc(texture.size_in_bytes());
 
         texture
     }
 
     fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) {
         let mag_filter = match filter {
             TextureFilter::Nearest => gl::NEAREST,
@@ -2031,19 +2067,66 @@ impl Device {
         src_rect: FramebufferIntRect,
         dest_target: DrawTarget,
         dest_rect: FramebufferIntRect,
         filter: TextureFilter,
     ) {
         debug_assert!(self.inside_frame);
 
         self.bind_read_target(src_target);
-        self.bind_draw_target(dest_target);
-
-        self.blit_render_target_impl(src_rect, dest_rect, filter);
+
+        match dest_target {
+            DrawTarget::Texture { texture, layer, .. } if layer != 0 &&
+                !self.capabilities.supports_blit_to_texture_array =>
+            {
+                // This should have been initialized in create_texture().
+                let (_rbo, fbo) = texture.blit_workaround_buffer.expect("Blit workaround buffer has not been initialized.");
+
+                // Blit from read target to intermediate buffer.
+                self.bind_draw_target_impl(fbo);
+                self.blit_render_target_impl(
+                    src_rect,
+                    dest_rect,
+                    filter
+                );
+
+                // dest_rect may be inverted, so min_x/y() might actually be the
+                // bottom-right, max_x/y() might actually be the top-left,
+                // and width/height might be negative. See servo/euclid#321.
+                // Calculate the non-inverted rect here.
+                let dest_bounds = DeviceIntRect::new(
+                    DeviceIntPoint::new(
+                        dest_rect.min_x().min(dest_rect.max_x()),
+                        dest_rect.min_y().min(dest_rect.max_y()),
+                    ),
+                    DeviceIntSize::new(
+                        dest_rect.size.width.abs(),
+                        dest_rect.size.height.abs(),
+                    ),
+                ).intersection(&texture.size.into()).unwrap_or(DeviceIntRect::zero());
+
+                self.bind_read_target_impl(fbo);
+                self.bind_texture(DEFAULT_TEXTURE, texture);
+
+                // Copy from intermediate buffer to the texture layer.
+                self.gl.copy_tex_sub_image_3d(
+                    texture.target, 0,
+                    dest_bounds.origin.x, dest_bounds.origin.y,
+                    layer as _,
+                    dest_bounds.origin.x, dest_bounds.origin.y,
+                    dest_bounds.size.width, dest_bounds.size.height,
+                );
+
+            }
+            _ => {
+                self.bind_draw_target(dest_target);
+
+                self.blit_render_target_impl(src_rect, dest_rect, filter);
+            }
+        }
     }
 
     /// Performs a blit while flipping vertically. Useful for blitting textures
     /// (which use origin-bottom-left) to the main framebuffer (which uses
     /// origin-top-left).
     pub fn blit_render_target_invert_y(
         &mut self,
         src_target: ReadTarget,
@@ -2070,16 +2153,20 @@ impl Device {
         debug_assert!(self.inside_frame);
         record_gpu_free(texture.size_in_bytes());
         let had_depth = texture.supports_depth();
         self.deinit_fbos(&mut texture.fbos);
         self.deinit_fbos(&mut texture.fbos_with_depth);
         if had_depth {
             self.release_depth_target(texture.get_dimensions());
         }
+        if let Some((rbo, fbo)) = texture.blit_workaround_buffer {
+            self.gl.delete_framebuffers(&[fbo.0]);
+            self.gl.delete_renderbuffers(&[rbo.0]);
+        }
 
         self.gl.delete_textures(&[texture.id]);
 
         for bound_texture in &mut self.bound_textures {
             if *bound_texture == texture.id {
                 *bound_texture = 0
             }
         }
@@ -2903,16 +2990,30 @@ impl Device {
             ImageFormat::RG8 => FormatDesc {
                 internal: gl::RG8,
                 external: gl::RG,
                 pixel_type: gl::UNSIGNED_BYTE,
             },
         }
     }
 
+    /// Returns a GL format matching an ImageFormat suitable for a renderbuffer.
+    fn matching_renderbuffer_format(&self, format: ImageFormat) -> gl::GLenum {
+        match format {
+            ImageFormat::R8 => gl::R8,
+            ImageFormat::R16 => gl::R16UI,
+            // BGRA8 is not usable with renderbuffers so use RGBA8.
+            ImageFormat::BGRA8 => gl::RGBA8,
+            ImageFormat::RGBAF32 => gl::RGBA32F,
+            ImageFormat::RG8 => gl::RG8,
+            ImageFormat::RGBAI32 => gl::RGBA32I,
+            ImageFormat::RGBA8 => gl::RGBA8,
+        }
+    }
+
     /// Generates a memory report for the resources managed by the device layer.
     pub fn report_memory(&self) -> MemoryReport {
         let mut report = MemoryReport::default();
         for dim in self.depth_targets.keys() {
             report.depth_target_textures += depth_target_size_in_bytes(dim);
         }
         report
     }