Bug 1607746 - Part 1: Move opacity to its own shader in WebRender r=nical
authorConnor Brewster <connorbrewster@yahoo.com>
Mon, 13 Jan 2020 16:05:57 +0000
changeset 509964 b0226237c55e4a08f4deb622fc15fa1e85f39afc
parent 509963 f2d90a6dd7c60b35bcbae8ab9e59c147631c02b6
child 509965 b2de33dc3f66665fa3fc53798ec24cfa2bb6d39a
push id37012
push userapavel@mozilla.com
push dateTue, 14 Jan 2020 03:45:02 +0000
treeherdermozilla-central@d1406439c461 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1607746
milestone74.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 1607746 - Part 1: Move opacity to its own shader in WebRender r=nical Opacity is a common effect that is used and the opacit filter path is also used when a stacking context has an opacity of < 1. The brush_blend shader is slow since it has support for a large portion of CSS filters; however, opacity is used much more often than the rest of the filters. This patch adds a simple shader for opacity effects which bypasses the extra overhead in the brush_blend shader. Differential Revision: https://phabricator.services.mozilla.com/D59610
gfx/wr/webrender/res/brush.glsl
gfx/wr/webrender/res/brush_multi.glsl
gfx/wr/webrender/res/brush_opacity.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/gpu_types.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/shade.rs
gfx/wr/webrender/tests/angle_shader_validation.rs
--- a/gfx/wr/webrender/res/brush.glsl
+++ b/gfx/wr/webrender/res/brush.glsl
@@ -85,16 +85,17 @@ void name(                              
 // Forward-declare all brush vertex entry points.
 FWD_DECLARE_VS_FUNCTION(image_brush_vs)
 FWD_DECLARE_VS_FUNCTION(solid_brush_vs)
 FWD_DECLARE_VS_FUNCTION(blend_brush_vs)
 FWD_DECLARE_VS_FUNCTION(mix_blend_brush_vs)
 FWD_DECLARE_VS_FUNCTION(linear_gradient_brush_vs)
 FWD_DECLARE_VS_FUNCTION(radial_gradient_brush_vs)
 FWD_DECLARE_VS_FUNCTION(yuv_brush_vs)
+FWD_DECLARE_VS_FUNCTION(opacity_brush_vs)
 
 void multi_brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     RectWithSize segment_rect,
     ivec4 prim_user_data,
     int specific_resource_address,
@@ -250,16 +251,17 @@ struct Fragment {
 // Foward-declare all brush entry-points.
 Fragment image_brush_fs();
 Fragment solid_brush_fs();
 Fragment blend_brush_fs();
 Fragment mix_blend_brush_fs();
 Fragment linear_gradient_brush_fs();
 Fragment radial_gradient_brush_fs();
 Fragment yuv_brush_fs();
+Fragment opacity_brush_fs();
 Fragment multi_brush_fs(int brush_kind);
 
 void main(void) {
 #ifdef WR_FEATURE_DEBUG_OVERDRAW
     oFragColor = WR_DEBUG_OVERDRAW_COLOR;
 #else
 
     // Run the specific brush FS code to output the color.
--- a/gfx/wr/webrender/res/brush_multi.glsl
+++ b/gfx/wr/webrender/res/brush_multi.glsl
@@ -16,16 +16,17 @@
 #define BRUSH_KIND_SOLID            1
 #define BRUSH_KIND_IMAGE            2
 #define BRUSH_KIND_TEXT             3
 #define BRUSH_KIND_LINEAR_GRADIENT  4
 #define BRUSH_KIND_RADIAL_GRADIENT  5
 #define BRUSH_KIND_BLEND            6
 #define BRUSH_KIND_MIX_BLEND        7
 #define BRUSH_KIND_YV               8
+#define BRUSH_KIND_OPACITY          9
 
 int vecs_per_brush(int brush_kind);
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_IMAGE_BRUSH
 #include brush_image
 #endif
@@ -65,16 +66,24 @@ int vecs_per_brush(int brush_kind);
 #undef VECS_PER_SPECIFIC_BRUSH
 #undef WR_BRUSH_VS_FUNCTION
 #undef WR_BRUSH_FS_FUNCTION
 
 #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
 #include brush_radial_gradient
 #endif
 
+#undef VECS_PER_SPECIFIC_BRUSH
+#undef WR_BRUSH_VS_FUNCTION
+#undef WR_BRUSH_FS_FUNCTION
+
+#ifdef WR_FEATURE_OPACITY_BRUSH
+#include brush_opacity
+#endif
+
 int vecs_per_brush(int brush_kind) {
     switch (brush_kind) {
         // The default arm should never be taken, we let it point to whichever shader
         // is enabled first to satisfy ANGLE validation.
         default:
 
         #ifdef WR_FEATURE_IMAGE_BRUSH
         case BRUSH_KIND_IMAGE: return VECS_PER_IMAGE_BRUSH;
@@ -94,16 +103,20 @@ int vecs_per_brush(int brush_kind) {
 
         #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH
         case BRUSH_KIND_LINEAR_GRADIENT: return VECS_PER_LINEAR_GRADIENT_BRUSH;
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT: return VECS_PER_RADIAL_GRADIENT_BRUSH;
         #endif
+
+        #ifdef WR_FEATURE_OPACITY_BRUSH
+        case BRUSH_KIND_OPACITY: return VECS_PER_OPACITY_BRUSH;
+        #endif
     }
 }
 
 #define BRUSH_VS_PARAMS vi, prim_address, local_rect, segment_rect, \
     prim_user_data, specific_resource_address, transform, pic_task, \
     brush_flags, texel_rect
 
 
@@ -154,16 +167,22 @@ void multi_brush_vs(
             break;
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT:
             radial_gradient_brush_vs(BRUSH_VS_PARAMS);
             break;
         #endif
+
+        #ifdef WR_FEATURE_OPACITY_BRUSH
+        case BRUSH_KIND_OPACITY:
+            opacity_brush_vs(BRUSH_VS_PARAMS);
+            break;
+        #endif
     }
 }
 
 #endif // WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
 Fragment multi_brush_fs(int brush_kind) {
@@ -188,12 +207,16 @@ Fragment multi_brush_fs(int brush_kind) 
 
         #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH
         case BRUSH_KIND_LINEAR_GRADIENT: return linear_gradient_brush_fs();
         #endif
 
         #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH
         case BRUSH_KIND_RADIAL_GRADIENT: return radial_gradient_brush_fs();
         #endif
+
+        #ifdef WR_FEATURE_OPACITY_BRUSH
+        case BRUSH_KIND_OPACITY: return opacity_brush_fs();
+        #endif
     }
 }
 
 #endif
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/brush_opacity.glsl
@@ -0,0 +1,91 @@
+/* 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/. */
+
+#define VECS_PER_OPACITY_BRUSH 3
+#define VECS_PER_SPECIFIC_BRUSH VECS_PER_OPACITY_BRUSH
+
+#define WR_BRUSH_VS_FUNCTION opacity_brush_vs
+#define WR_BRUSH_FS_FUNCTION opacity_brush_fs
+
+#include shared,prim_shared,brush
+
+// Interpolated UV coordinates to sample.
+#define V_UV                varying_vec4_0.zw
+#define V_LOCAL_POS         varying_vec4_0.xy
+
+// Normalized bounds of the source image in the texture.
+#define V_UV_BOUNDS         flat_varying_vec4_1
+
+// Layer index to sample.
+#define V_LAYER             flat_varying_vec4_2.x
+// Flag to allow perspective interpolation of UV.
+#define V_PERSPECTIVE       flat_varying_vec4_2.y
+
+#define V_OPACITY           flat_varying_vec4_2.z
+
+#ifdef WR_VERTEX_SHADER
+void opacity_brush_vs(
+    VertexInfo vi,
+    int prim_address,
+    RectWithSize local_rect,
+    RectWithSize segment_rect,
+    ivec4 prim_user_data,
+    int specific_resource_address,
+    mat4 transform,
+    PictureTask pic_task,
+    int brush_flags,
+    vec4 unused
+) {
+    ImageResource res = fetch_image_resource(prim_user_data.x);
+    vec2 uv0 = res.uv_rect.p0;
+    vec2 uv1 = res.uv_rect.p1;
+
+    vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+    vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
+    f = get_image_quad_uv(prim_user_data.x, f);
+    vec2 uv = mix(uv0, uv1, f);
+    float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;
+
+    V_UV = uv / texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate);
+    V_LAYER = res.layer;
+    V_PERSPECTIVE = perspective_interpolate;
+
+    // TODO: The image shader treats this differently: deflate the rect by half a pixel on each side and
+    // clamp the uv in the frame shader. Does it make sense to do the same here?
+    V_UV_BOUNDS = vec4(uv0, uv1) / texture_size.xyxy;
+    V_LOCAL_POS = vi.local_pos;
+
+    V_OPACITY = float(prim_user_data.y) / 65536.0;
+}
+#endif
+
+#ifdef WR_FRAGMENT_SHADER
+Fragment opacity_brush_fs() {
+    float perspective_divisor = mix(gl_FragCoord.w, 1.0, V_PERSPECTIVE);
+    vec2 uv = V_UV * perspective_divisor;
+    vec4 Cs = texture(sColor0, vec3(uv, V_LAYER));
+
+    // Un-premultiply the input.
+    float alpha = Cs.a;
+    vec3 color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb;
+
+    alpha *= V_OPACITY;
+
+    // Fail-safe to ensure that we don't sample outside the rendered
+    // portion of a blend source.
+    alpha *= min(point_inside_rect(uv, V_UV_BOUNDS.xy, V_UV_BOUNDS.zw),
+                 init_transform_fs(V_LOCAL_POS));
+
+    // Pre-multiply the alpha into the output value.
+    return Fragment(alpha * vec4(color, 1.0));
+}
+#endif
+
+// Undef macro names that could be re-defined by other shaders.
+#undef V_UV
+#undef V_LOCAL_POS
+#undef V_UV_BOUNDS
+#undef V_LAYER
+#undef V_PERSPECTIVE
+#undef V_OPACITY
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -53,16 +53,17 @@ pub enum BrushBatchKind {
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
     },
     YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange),
     RadialGradient,
     LinearGradient,
+    Opacity,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BatchKind {
     SplitComposite,
     TextRun(GlyphFormat),
@@ -74,16 +75,17 @@ impl BatchKind {
         match self {
             BatchKind::Brush(BrushBatchKind::Solid) => BrushShaderKind::Solid,
             BatchKind::Brush(BrushBatchKind::Image(..)) => BrushShaderKind::Image,
             BatchKind::Brush(BrushBatchKind::LinearGradient) => BrushShaderKind::LinearGradient,
             BatchKind::Brush(BrushBatchKind::RadialGradient) => BrushShaderKind::RadialGradient,
             BatchKind::Brush(BrushBatchKind::Blend) => BrushShaderKind::Blend,
             BatchKind::Brush(BrushBatchKind::MixBlend { .. }) => BrushShaderKind::MixBlend,
             BatchKind::Brush(BrushBatchKind::YuvImage(..)) => BrushShaderKind::Yuv,
+            BatchKind::Brush(BrushBatchKind::Opacity) => BrushShaderKind::Opacity,
             BatchKind::TextRun(..) => BrushShaderKind::Text,
             _ => BrushShaderKind::None,
         }
     }
 }
 
 /// Optional textures that can be used as a source in the shaders.
 /// Textures that are not used by the batch are equal to TextureId::invalid().
@@ -1361,16 +1363,51 @@ impl BatchBuilder {
                                             EdgeAaSegmentMask::empty(),
                                             clip_task_address.unwrap(),
                                             brush_flags,
                                             content_prim_header_index,
                                             content_uv_rect_address,
                                             prim_vis_mask,
                                         );
                                     }
+                                    Filter::Opacity(_, amount) => {
+                                        let amount = (amount * 65536.0) as i32;
+
+                                        let (uv_rect_address, textures) = render_tasks.resolve_surface(
+                                            surface_task.expect("bug: surface must be allocated by now"),
+                                            gpu_cache,
+                                        );
+
+                                        let key = BatchKey::new(
+                                            BatchKind::Brush(BrushBatchKind::Opacity),
+                                            BlendMode::PremultipliedAlpha,
+                                            textures,
+                                        );
+
+                                        let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                            uv_rect_address.as_int(),
+                                            amount,
+                                            0,
+                                            0,
+                                        ]);
+
+                                        self.add_brush_instance_to_batches(
+                                            key,
+                                            batch_features,
+                                            bounding_rect,
+                                            z_id,
+                                            INVALID_SEGMENT_INDEX,
+                                            EdgeAaSegmentMask::empty(),
+                                            clip_task_address.unwrap(),
+                                            brush_flags,
+                                            prim_header_index,
+                                            0,
+                                            prim_vis_mask,
+                                        );
+                                    }
                                     _ => {
                                         let filter_mode = match filter {
                                             Filter::Identity => 1, // matches `Contrast(1)`
                                             Filter::Blur(..) => 0,
                                             Filter::Contrast(..) => 1,
                                             Filter::Grayscale(..) => 2,
                                             Filter::HueRotate(..) => 3,
                                             Filter::Invert(..) => 4,
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -76,16 +76,17 @@ pub enum BrushShaderKind {
     Solid           = 1,
     Image           = 2,
     Text            = 3,
     LinearGradient  = 4,
     RadialGradient  = 5,
     Blend           = 6,
     MixBlend        = 7,
     Yuv             = 8,
+    Opacity         = 9,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum RasterizationSpace {
     Local = 0,
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -136,16 +136,20 @@ pub const MAX_VERTEX_TEXTURE_WIDTH: usiz
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
 const GPU_CACHE_RESIZE_TEST: bool = false;
 
 /// Number of GPU blocks per UV rectangle provided for an image.
 pub const BLOCKS_PER_UV_RECT: usize = 2;
 
+const GPU_TAG_BRUSH_OPACITY: GpuProfileTag = GpuProfileTag {
+    label: "B_Opacity",
+    color: debug_colors::DARKMAGENTA,
+};
 const GPU_TAG_BRUSH_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "B_LinearGradient",
     color: debug_colors::POWDERBLUE,
 };
 const GPU_TAG_BRUSH_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
     label: "B_RadialGradient",
     color: debug_colors::LIGHTPINK,
 };
@@ -248,16 +252,17 @@ impl BatchKind {
                 match kind {
                     BrushBatchKind::Solid => "Brush (Solid)",
                     BrushBatchKind::Image(..) => "Brush (Image)",
                     BrushBatchKind::Blend => "Brush (Blend)",
                     BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
                     BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
                     BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
                     BrushBatchKind::LinearGradient => "Brush (LinearGradient)",
+                    BrushBatchKind::Opacity => "Brush (Opacity)",
                 }
             }
             BatchKind::TextRun(_) => "TextRun",
         }
     }
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
@@ -266,16 +271,17 @@ impl BatchKind {
                 match kind {
                     BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
                     BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
                     BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
                     BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
                     BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
                     BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
                     BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
+                    BrushBatchKind::Opacity => GPU_TAG_BRUSH_OPACITY,
                 }
             }
             BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN,
         }
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -533,16 +533,17 @@ pub struct Shaders {
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_fast_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
     brush_radial_gradient: BrushShader,
     brush_linear_gradient: BrushShader,
+    brush_opacity: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     pub cs_clip_rectangle_slow: LazilyCompiledShader,
     pub cs_clip_rectangle_fast: LazilyCompiledShader,
     pub cs_clip_box_shadow: LazilyCompiledShader,
     pub cs_clip_image: LazilyCompiledShader,
@@ -636,16 +637,26 @@ impl Shaders {
                &[]
             },
             options.precache_flags,
             false /* advanced blend */,
             false /* dual source */,
             use_pixel_local_storage,
         )?;
 
+        let brush_opacity = BrushShader::new(
+            "brush_opacity",
+            device,
+            &[],
+            options.precache_flags,
+            false /* advanced blend */,
+            false /* dual source */,
+            use_pixel_local_storage,
+        )?;
+
         let cs_blur_a8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Blur),
             "cs_blur",
             &["ALPHA_TARGET"],
             device,
             options.precache_flags,
         )?;
 
@@ -890,16 +901,17 @@ impl Shaders {
             brush_solid,
             brush_image,
             brush_fast_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
+            brush_opacity,
             cs_clip_rectangle_slow,
             cs_clip_rectangle_fast,
             cs_clip_box_shadow,
             cs_clip_image,
             pls_init,
             pls_resolve,
             ps_text_run,
             ps_text_run_dual_source,
@@ -950,16 +962,19 @@ impl Shaders {
                     }
                     BrushBatchKind::YuvImage(image_buffer_kind, ..) => {
                         let shader_index =
                             Self::get_yuv_shader_index(image_buffer_kind);
                         self.brush_yuv_image[shader_index]
                             .as_mut()
                             .expect("Unsupported YUV shader kind")
                     }
+                    BrushBatchKind::Opacity => {
+                        &mut self.brush_opacity
+                    }
                 };
                 brush_shader.get(key.blend_mode, debug_flags)
             }
             BatchKind::TextRun(glyph_format) => {
                 let text_shader = match key.blend_mode {
                     BlendMode::SubpixelDualSource => &mut self.ps_text_run_dual_source,
                     _ => &mut self.ps_text_run,
                 };
@@ -973,16 +988,17 @@ impl Shaders {
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.cs_svg_filter.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
+        self.brush_opacity.deinit(device);
         self.cs_clip_rectangle_slow.deinit(device);
         self.cs_clip_rectangle_fast.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
         self.cs_clip_image.deinit(device);
         self.pls_init.deinit(device);
         self.pls_resolve.deinit(device);
         self.ps_text_run.deinit(device);
         self.ps_text_run_dual_source.deinit(device);
--- a/gfx/wr/webrender/tests/angle_shader_validation.rs
+++ b/gfx/wr/webrender/tests/angle_shader_validation.rs
@@ -108,16 +108,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "brush_radial_gradient",
         features: GRADIENT_FEATURES,
     },
     Shader {
         name: "brush_linear_gradient",
         features: GRADIENT_FEATURES,
     },
+    Shader {
+        name: "brush_opacity",
+        features: BRUSH_FEATURES,
+    },
 ];
 
 const VERSION_STRING: &str = "#version 300 es\n";
 
 #[test]
 fn validate_shaders() {
     mozangle::shaders::initialize().unwrap();