tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 188ef52248cb7e4c79ccd8f3176c4a42365577ba
parent 177ac4c58aa0da6ae9a2ff00ec1c3a7d5c1ba4f2
Author: Atila Butkovits <abutkovits@mozilla.com>
Date:   Thu, 20 Nov 2025 15:30:07 +0200

Revert "Bug 2000466 - Remove unused svg filter implementation. r=gfx-reviewers,lsalzman,ahale" for causing Wrench bustages.

This reverts commit aafa3d6a81142df8ce390c39804c8a0f358e7cfb.

Diffstat:
Mgfx/webrender_bindings/src/bindings.rs | 3++-
Agfx/wr/webrender/res/cs_svg_filter.glsl | 598+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/wr/webrender/src/batch.rs | 19+++++++++++++++++++
Mgfx/wr/webrender/src/gpu_types.rs | 15+++++++++++++++
Mgfx/wr/webrender/src/picture.rs | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mgfx/wr/webrender/src/prim_store/picture.rs | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mgfx/wr/webrender/src/render_target.rs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mgfx/wr/webrender/src/render_task.rs | 405++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mgfx/wr/webrender/src/renderer/mod.rs | 44+++++++++++++++++++++++++++++++++++++++++++-
Mgfx/wr/webrender/src/renderer/shade.rs | 11+++++++++++
Mgfx/wr/webrender/src/renderer/vertex.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/wr/webrender/src/scene_building.rs | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mgfx/wr/webrender_api/src/display_item.rs | 3+++
Mgfx/wr/webrender_api/src/display_list.rs | 34++++++++++++++++++++++++++++++++--
Mgfx/wr/webrender_build/src/shader_features.rs | 1+
Mgfx/wr/wrench/src/yaml_frame_reader.rs | 4++++
Mgfx/wr/wrench/src/yaml_helper.rs | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/wr/wrshell/src/wrench.rs | 1+
18 files changed, 1747 insertions(+), 14 deletions(-)

diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs @@ -3217,6 +3217,7 @@ pub extern "C" fn wr_dp_push_stacking_context( params.mix_blend_mode, &filters, &r_filter_datas, + &[], glyph_raster_space, params.flags, unsafe { params.snapshot.as_ref() }.cloned(), @@ -3551,7 +3552,7 @@ pub extern "C" fn wr_dp_push_backdrop_filter( state .frame_builder .dl_builder - .push_backdrop_filter(&prim_info, &filters, &filter_datas); + .push_backdrop_filter(&prim_info, &filters, &filter_datas, &[]); } #[no_mangle] diff --git a/gfx/wr/webrender/res/cs_svg_filter.glsl b/gfx/wr/webrender/res/cs_svg_filter.glsl @@ -0,0 +1,598 @@ +/* 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 WR_FEATURE_TEXTURE_2D + +#include shared,prim_shared,gpu_buffer + +varying highp vec2 vInput1Uv; +varying highp vec2 vInput2Uv; +flat varying highp vec4 vInput1UvRect; +flat varying highp vec4 vInput2UvRect; +flat varying mediump ivec4 vData; +flat varying mediump vec4 vFilterData0; +flat varying mediump vec4 vFilterData1; + +// x: Filter input count, y: Filter kind. +// Packed in to a vector to work around bug 1630356. +flat varying mediump ivec2 vFilterInputCountFilterKindVec; +#define vFilterInputCount vFilterInputCountFilterKindVec.x +#define vFilterKind vFilterInputCountFilterKindVec.y +// Packed in to a vector to work around bug 1630356. +flat varying mediump vec2 vFloat0; + +flat varying mediump mat4 vColorMat; +flat varying mediump ivec4 vFuncs; + +#define FILTER_BLEND 0 +#define FILTER_FLOOD 1 +#define FILTER_LINEAR_TO_SRGB 2 +#define FILTER_SRGB_TO_LINEAR 3 +#define FILTER_OPACITY 4 +#define FILTER_COLOR_MATRIX 5 +#define FILTER_DROP_SHADOW 6 +#define FILTER_OFFSET 7 +#define FILTER_COMPONENT_TRANSFER 8 +#define FILTER_IDENTITY 9 +#define FILTER_COMPOSITE 10 + +#define COMPOSITE_OVER 0 +#define COMPOSITE_IN 1 +#define COMPOSITE_OUT 2 +#define COMPOSITE_ATOP 3 +#define COMPOSITE_XOR 4 +#define COMPOSITE_LIGHTER 5 +#define COMPOSITE_ARITHMETIC 6 + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in int aFilterRenderTaskAddress; +PER_INSTANCE in int aFilterInput1TaskAddress; +PER_INSTANCE in int aFilterInput2TaskAddress; +PER_INSTANCE in int aFilterKind; +PER_INSTANCE in int aFilterInputCount; +PER_INSTANCE in int aFilterGenericInt; +PER_INSTANCE in int aFilterExtraDataAddress; + +struct FilterTask { + RectWithEndpoint task_rect; + vec3 user_data; +}; + +FilterTask fetch_filter_task(int address) { + RenderTaskData task_data = fetch_render_task_data(address); + + FilterTask task = FilterTask( + task_data.task_rect, + task_data.user_data.xyz + ); + + return task; +} + +vec4 compute_uv_rect(RectWithEndpoint task_rect, vec2 texture_size) { + vec4 uvRect = vec4(task_rect.p0 + vec2(0.5), + task_rect.p1 - vec2(0.5)); + uvRect /= texture_size.xyxy; + return uvRect; +} + +vec2 compute_uv(RectWithEndpoint task_rect, vec2 texture_size) { + vec2 uv0 = task_rect.p0 / texture_size; + vec2 uv1 = floor(task_rect.p1) / texture_size; + return mix(uv0, uv1, aPosition.xy); +} + +void main(void) { + FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress); + RectWithEndpoint target_rect = filter_task.task_rect; + + vec2 pos = mix(target_rect.p0, target_rect.p1, aPosition.xy); + + RectWithEndpoint input_1_task; + if (aFilterInputCount > 0) { + vec2 texture_size = vec2(TEX_SIZE(sColor0).xy); + input_1_task = fetch_render_task_rect(aFilterInput1TaskAddress); + vInput1UvRect = compute_uv_rect(input_1_task, texture_size); + vInput1Uv = compute_uv(input_1_task, texture_size); + } + + RectWithEndpoint input_2_task; + if (aFilterInputCount > 1) { + vec2 texture_size = vec2(TEX_SIZE(sColor1).xy); + input_2_task = fetch_render_task_rect(aFilterInput2TaskAddress); + vInput2UvRect = compute_uv_rect(input_2_task, texture_size); + vInput2Uv = compute_uv(input_2_task, texture_size); + } + + vFilterInputCount = aFilterInputCount; + vFilterKind = aFilterKind; + + // This assignment is only used for component transfer filters but this + // assignment has to be done here and not in the component transfer case + // below because it doesn't get executed on Windows because of a suspected + // miscompile of this shader on Windows. See + // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows + // default: just to satisfy angle_shader_validation.rs which needs one + // default: for every switch, even in comments. + vFuncs.r = (aFilterGenericInt >> 12) & 0xf; // R + vFuncs.g = (aFilterGenericInt >> 8) & 0xf; // G + vFuncs.b = (aFilterGenericInt >> 4) & 0xf; // B + vFuncs.a = (aFilterGenericInt) & 0xf; // A + + switch (aFilterKind) { + case FILTER_BLEND: + vData = ivec4(aFilterGenericInt, 0, 0, 0); + break; + case FILTER_FLOOD: + vFilterData0 = fetch_from_gpu_buffer_1f(aFilterExtraDataAddress); + break; + case FILTER_OPACITY: + vFloat0.x = filter_task.user_data.x; + break; + case FILTER_COLOR_MATRIX: { + ivec2 buffer_uv = get_gpu_buffer_uv(aFilterExtraDataAddress); + vec4 mat_data[4] = fetch_from_gpu_buffer_4f_direct(buffer_uv); + vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]); + vFilterData0 = fetch_from_gpu_buffer_1f_direct(buffer_uv + ivec2(4, 0)); + break; + } + case FILTER_DROP_SHADOW: + vFilterData0 = fetch_from_gpu_buffer_1f(aFilterExtraDataAddress); + break; + case FILTER_OFFSET: + vec2 texture_size = vec2(TEX_SIZE(sColor0).xy); + vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0)); + + RectWithEndpoint task_rect = input_1_task; + vec4 clipRect = vec4(task_rect.p0, task_rect.p1); + clipRect /= texture_size.xyxy; + vFilterData1 = clipRect; + break; + case FILTER_COMPONENT_TRANSFER: { + ivec2 buffer_uv = get_gpu_buffer_uv(aFilterExtraDataAddress); + vData = ivec4(buffer_uv, 0, 0); + break; + } + case FILTER_COMPOSITE: + vData = ivec4(aFilterGenericInt, 0, 0, 0); + if (aFilterGenericInt == COMPOSITE_ARITHMETIC) { + vFilterData0 = fetch_from_gpu_buffer_1f(aFilterExtraDataAddress); + } + break; + default: + break; + } + + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +#define COMPONENT_TRANSFER_IDENTITY 0 +#define COMPONENT_TRANSFER_TABLE 1 +#define COMPONENT_TRANSFER_DISCRETE 2 +#define COMPONENT_TRANSFER_LINEAR 3 +#define COMPONENT_TRANSFER_GAMMA 4 + +vec3 Multiply(vec3 Cb, vec3 Cs) { + return Cb * Cs; +} + +vec3 Screen(vec3 Cb, vec3 Cs) { + return Cb + Cs - (Cb * Cs); +} + +vec3 HardLight(vec3 Cb, vec3 Cs) { + vec3 m = Multiply(Cb, 2.0 * Cs); + vec3 s = Screen(Cb, 2.0 * Cs - 1.0); + vec3 edge = vec3(0.5, 0.5, 0.5); + return mix(m, s, step(edge, Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorDodge(float Cb, float Cs) { + if (Cb == 0.0) + return 0.0; + else if (Cs == 1.0) + return 1.0; + else + return min(1.0, Cb / (1.0 - Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorBurn(float Cb, float Cs) { + if (Cb == 1.0) + return 1.0; + else if (Cs == 0.0) + return 0.0; + else + return 1.0 - min(1.0, (1.0 - Cb) / Cs); +} + +float SoftLight(float Cb, float Cs) { + if (Cs <= 0.5) { + return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb); + } else { + float D; + + if (Cb <= 0.25) + D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb; + else + D = sqrt(Cb); + + return Cb + (2.0 * Cs - 1.0) * (D - Cb); + } +} + +vec3 Difference(vec3 Cb, vec3 Cs) { + return abs(Cb - Cs); +} + +vec3 Exclusion(vec3 Cb, vec3 Cs) { + return Cb + Cs - 2.0 * Cb * Cs; +} + +// These functions below are taken from the spec. +// There's probably a much quicker way to implement +// them in GLSL... +float Sat(vec3 c) { + return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b)); +} + +float Lum(vec3 c) { + vec3 f = vec3(0.3, 0.59, 0.11); + return dot(c, f); +} + +vec3 ClipColor(vec3 C) { + float L = Lum(C); + float n = min(C.r, min(C.g, C.b)); + float x = max(C.r, max(C.g, C.b)); + + if (n < 0.0) + C = L + (((C - L) * L) / (L - n)); + + if (x > 1.0) + C = L + (((C - L) * (1.0 - L)) / (x - L)); + + return C; +} + +vec3 SetLum(vec3 C, float l) { + float d = l - Lum(C); + return ClipColor(C + d); +} + +void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) { + if (Cmax > Cmin) { + Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin)); + Cmax = s; + } else { + Cmid = 0.0; + Cmax = 0.0; + } + Cmin = 0.0; +} + +vec3 SetSat(vec3 C, float s) { + if (C.r <= C.g) { + if (C.g <= C.b) { + SetSatInner(C.r, C.g, C.b, s); + } else { + if (C.r <= C.b) { + SetSatInner(C.r, C.b, C.g, s); + } else { + SetSatInner(C.b, C.r, C.g, s); + } + } + } else { + if (C.r <= C.b) { + SetSatInner(C.g, C.r, C.b, s); + } else { + if (C.g <= C.b) { + SetSatInner(C.g, C.b, C.r, s); + } else { + SetSatInner(C.b, C.g, C.r, s); + } + } + } + return C; +} + +vec3 Hue(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb)); +} + +vec3 Saturation(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb)); +} + +vec3 Color(vec3 Cb, vec3 Cs) { + return SetLum(Cs, Lum(Cb)); +} + +vec3 Luminosity(vec3 Cb, vec3 Cs) { + return SetLum(Cb, Lum(Cs)); +} + +const int BlendMode_Normal = 0; +const int BlendMode_Multiply = 1; +const int BlendMode_Screen = 2; +const int BlendMode_Overlay = 3; +const int BlendMode_Darken = 4; +const int BlendMode_Lighten = 5; +const int BlendMode_ColorDodge = 6; +const int BlendMode_ColorBurn = 7; +const int BlendMode_HardLight = 8; +const int BlendMode_SoftLight = 9; +const int BlendMode_Difference = 10; +const int BlendMode_Exclusion = 11; +const int BlendMode_Hue = 12; +const int BlendMode_Saturation = 13; +const int BlendMode_Color = 14; +const int BlendMode_Luminosity = 15; + +vec4 blend(vec4 Cs, vec4 Cb, int mode) { + vec4 result = vec4(1.0, 0.0, 0.0, 1.0); + + switch (mode) { + case BlendMode_Normal: + result.rgb = Cs.rgb; + break; + case BlendMode_Multiply: + result.rgb = Multiply(Cb.rgb, Cs.rgb); + break; + case BlendMode_Screen: + result.rgb = Screen(Cb.rgb, Cs.rgb); + break; + case BlendMode_Overlay: + // Overlay is inverse of Hardlight + result.rgb = HardLight(Cs.rgb, Cb.rgb); + break; + case BlendMode_Darken: + result.rgb = min(Cs.rgb, Cb.rgb); + break; + case BlendMode_Lighten: + result.rgb = max(Cs.rgb, Cb.rgb); + break; + case BlendMode_ColorDodge: + result.r = ColorDodge(Cb.r, Cs.r); + result.g = ColorDodge(Cb.g, Cs.g); + result.b = ColorDodge(Cb.b, Cs.b); + break; + case BlendMode_ColorBurn: + result.r = ColorBurn(Cb.r, Cs.r); + result.g = ColorBurn(Cb.g, Cs.g); + result.b = ColorBurn(Cb.b, Cs.b); + break; + case BlendMode_HardLight: + result.rgb = HardLight(Cb.rgb, Cs.rgb); + break; + case BlendMode_SoftLight: + result.r = SoftLight(Cb.r, Cs.r); + result.g = SoftLight(Cb.g, Cs.g); + result.b = SoftLight(Cb.b, Cs.b); + break; + case BlendMode_Difference: + result.rgb = Difference(Cb.rgb, Cs.rgb); + break; + case BlendMode_Exclusion: + result.rgb = Exclusion(Cb.rgb, Cs.rgb); + break; + case BlendMode_Hue: + result.rgb = Hue(Cb.rgb, Cs.rgb); + break; + case BlendMode_Saturation: + result.rgb = Saturation(Cb.rgb, Cs.rgb); + break; + case BlendMode_Color: + result.rgb = Color(Cb.rgb, Cs.rgb); + break; + case BlendMode_Luminosity: + result.rgb = Luminosity(Cb.rgb, Cs.rgb); + break; + default: break; + } + vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb; + result = mix(vec4(Cb.rgb * Cb.a, Cb.a), vec4(rgb, 1.0), Cs.a); + return result; +} + +// Based on the Gecko's implementation in +// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24 +// These could be made faster by sampling a lookup table stored in a float texture +// with linear interpolation. + +vec3 SrgbToLinear(vec3 color) { + vec3 c1 = color / 12.92; + vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4)); + return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2); +} + +vec3 LinearToSrgb(vec3 color) { + vec3 c1 = color * 12.92; + vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055); + return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2); +} + +// This function has to be factored out due to the following issue: +// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones +// (and now the words "default: default:" so angle_shader_validation.rs passes) +vec4 ComponentTransfer(vec4 colora) { + // We push a different amount of data to the gpu cache depending on the + // function type. + // Identity => 0 blocks + // Table/Discrete => 64 blocks (256 values) + // Linear => 1 block (2 values) + // Gamma => 1 block (3 values) + // We loop through the color components and increment the offset (for the + // next color component) into the gpu cache based on how many blocks that + // function type put into the gpu cache. + // Table/Discrete use a 256 entry look up table. + // Linear/Gamma are a simple calculation. + int offset = 0; + vec4 texel; + int k; + + // Dynamically indexing a vector is buggy on some devices, so use a temporary array. + int[4] funcs = int[4](vFuncs.r, vFuncs.g, vFuncs.b, vFuncs.a); + for (int i = 0; i < 4; i++) { + switch (funcs[i]) { + case COMPONENT_TRANSFER_IDENTITY: + break; + case COMPONENT_TRANSFER_TABLE: + case COMPONENT_TRANSFER_DISCRETE: + // fetch value from lookup table + k = int(floor(colora[i]*255.0 + 0.5)); + texel = fetch_from_gpu_buffer_1f_direct(vData.xy + ivec2(offset + k/4, 0)); + colora[i] = clamp(texel[k % 4], 0.0, 1.0); + // offset plus 256/4 blocks + offset = offset + 64; + break; + case COMPONENT_TRANSFER_LINEAR: + // fetch the two values for use in the linear equation + texel = fetch_from_gpu_buffer_1f_direct(vData.xy + ivec2(offset, 0)); + colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + case COMPONENT_TRANSFER_GAMMA: + // fetch the three values for use in the gamma equation + texel = fetch_from_gpu_buffer_1f_direct(vData.xy + ivec2(offset, 0)); + colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + default: + // shouldn't happen + break; + } + } + return colora; +} + +// Composite Filter + +vec4 composite(vec4 Cs, vec4 Cb, int mode) { + vec4 Cr = vec4(0.0, 1.0, 0.0, 1.0); + switch (mode) { + case COMPOSITE_OVER: + Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_IN: + Cr.rgb = Cs.a * Cs.rgb * Cb.a; + Cr.a = Cs.a * Cb.a; + break; + case COMPOSITE_OUT: + Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a); + Cr.a = Cs.a * (1.0 - Cb.a); + break; + case COMPOSITE_ATOP: + Cr.rgb = Cs.a * Cs.rgb * Cb.a + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a * Cb.a + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_XOR: + Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a) + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a * (1.0 - Cb.a) + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_LIGHTER: + Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb; + Cr.a = Cs.a + Cb.a; + Cr = clamp(Cr, vec4(0.0), vec4(1.0)); + break; + case COMPOSITE_ARITHMETIC: + Cr = vec4(vFilterData0.x) * Cs * Cb + vec4(vFilterData0.y) * Cs + vec4(vFilterData0.z) * Cb + vec4(vFilterData0.w); + Cr = clamp(Cr, vec4(0.0), vec4(1.0)); + break; + default: + break; + } + return Cr; +} + +vec4 sampleInUvRect(sampler2D sampler, vec2 uv, vec4 uvRect) { + vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw); + return texture(sampler, clamped); +} + +void main(void) { + vec4 Ca = vec4(0.0, 0.0, 0.0, 0.0); + vec4 Cb = vec4(0.0, 0.0, 0.0, 0.0); + if (vFilterInputCount > 0) { + Ca = sampleInUvRect(sColor0, vInput1Uv, vInput1UvRect); + if (Ca.a != 0.0) { + Ca.rgb /= Ca.a; + } + } + if (vFilterInputCount > 1) { + Cb = sampleInUvRect(sColor1, vInput2Uv, vInput2UvRect); + if (Cb.a != 0.0) { + Cb.rgb /= Cb.a; + } + } + + vec4 result = vec4(1.0, 0.0, 0.0, 1.0); + + bool needsPremul = true; + + switch (vFilterKind) { + case FILTER_BLEND: + result = blend(Ca, Cb, vData.x); + needsPremul = false; + break; + case FILTER_FLOOD: + result = vFilterData0; + needsPremul = false; + break; + case FILTER_LINEAR_TO_SRGB: + result.rgb = LinearToSrgb(Ca.rgb); + result.a = Ca.a; + break; + case FILTER_SRGB_TO_LINEAR: + result.rgb = SrgbToLinear(Ca.rgb); + result.a = Ca.a; + break; + case FILTER_OPACITY: + result.rgb = Ca.rgb; + result.a = Ca.a * vFloat0.x; + break; + case FILTER_COLOR_MATRIX: + result = vColorMat * Ca + vFilterData0; + result = clamp(result, vec4(0.0), vec4(1.0)); + break; + case FILTER_DROP_SHADOW: + vec4 shadow = vec4(vFilterData0.rgb, Cb.a * vFilterData0.a); + // Normal blend + source-over coposite + result = blend(Ca, shadow, BlendMode_Normal); + needsPremul = false; + break; + case FILTER_OFFSET: + vec2 offsetUv = vInput1Uv + vFilterData0.xy; + result = sampleInUvRect(sColor0, offsetUv, vInput1UvRect); + result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw); + needsPremul = false; + break; + case FILTER_COMPONENT_TRANSFER: + result = ComponentTransfer(Ca); + break; + case FILTER_IDENTITY: + result = Ca; + break; + case FILTER_COMPOSITE: + result = composite(Ca, Cb, vData.x); + needsPremul = false; + default: + break; + } + + if (needsPremul) { + result.rgb *= result.a; + } + + oFragColor = result; +} +#endif diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs @@ -1522,6 +1522,25 @@ impl BatchBuilder { } } } + PictureCompositeMode::SvgFilter(..) => { + let kind = BatchKind::Brush( + BrushBatchKind::Image(ImageBufferKind::Texture2D) + ); + let key = BatchKey::new( + kind, + blend_mode, + textures, + ); + + let prim_user_data = ImageBrushUserData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(); + + (key, prim_user_data, uv_rect_address.as_int()) + } PictureCompositeMode::SVGFEGraph(..) => { let kind = BatchKind::Brush( BrushBatchKind::Image(ImageBufferKind::Texture2D) diff --git a/gfx/wr/webrender/src/gpu_types.rs b/gfx/wr/webrender/src/gpu_types.rs @@ -167,6 +167,21 @@ impl ScalingInstance { #[repr(C)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SvgFilterInstance { + pub task_address: RenderTaskAddress, + pub input_1_task_address: RenderTaskAddress, + pub input_2_task_address: RenderTaskAddress, + pub kind: u16, + pub input_count: u16, + pub generic_int: u16, + pub padding: u16, + pub extra_data_address: i32, +} + +#[derive(Clone, Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct SVGFEFilterInstance { pub target_rect: DeviceRect, pub input_1_content_scale_and_offset: [f32; 4], diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs @@ -94,8 +94,8 @@ //! blend the overlay tile (this is not always optimal right now, but will be //! improved as a follow up). -use api::{BorderRadius, ClipMode, MixBlendMode, PremultipliedColorF, SVGFE_GRAPH_MAX}; -use api::{PropertyBinding, PropertyBindingId, FilterOpGraphPictureBufferId, RasterSpace}; +use api::{BorderRadius, ClipMode, FilterPrimitiveKind, MixBlendMode, PremultipliedColorF, SVGFE_GRAPH_MAX}; +use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FilterOpGraphPictureBufferId, RasterSpace}; use api::{DebugFlags, ImageKey, ColorF, ColorU, PrimitiveFlags, SnapshotInfo}; use api::{ImageRendering, ColorDepth, YuvRangedColorSpace, YuvFormat, AlphaType}; use api::units::*; @@ -111,6 +111,7 @@ use crate::composite::{CompositorTransformIndex, CompositorSurfaceKind}; use crate::debug_colors; use euclid::{vec3, Point2D, Scale, Vector2D, Box2D}; use euclid::approxeq::ApproxEq; +use crate::filterdata::SFilterData; use crate::intern::ItemUid; use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, FilterGraphOp, FilterGraphNode, Filter, FrameId}; use crate::internal_types::{PlaneSplitterIndex, PlaneSplitAnchor, TextureSource}; @@ -4407,6 +4408,8 @@ pub enum PictureCompositeMode { TileCache { slice_id: SliceId, }, + /// Apply an SVG filter + SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>), /// Apply an SVG filter graph SVGFEGraph(Vec<(FilterGraphNode, FilterGraphOp)>), /// A surface that is used as an input to another primitive @@ -4452,6 +4455,52 @@ impl PictureCompositeMode { surface_rect.inflate(blur_inflation_x, blur_inflation_y) } + PictureCompositeMode::SvgFilter(primitives, _) => { + let mut result_rect = surface_rect; + let mut output_rects = Vec::with_capacity(primitives.len()); + + for (cur_index, primitive) in primitives.iter().enumerate() { + let output_rect = match primitive.kind { + FilterPrimitiveKind::Blur(ref primitive) => { + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + let width_factor = primitive.width.round() * BLUR_SAMPLE_SCALE; + let height_factor = primitive.height.round() * BLUR_SAMPLE_SCALE; + input.inflate(width_factor, height_factor) + } + FilterPrimitiveKind::DropShadow(ref primitive) => { + let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE; + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + let shadow_rect = input.inflate(inflation_factor, inflation_factor); + input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0))) + } + FilterPrimitiveKind::Blend(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)) + } + FilterPrimitiveKind::Composite(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)) + } + FilterPrimitiveKind::Identity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::Opacity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::ColorMatrix(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::ComponentTransfer(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::Offset(ref primitive) => { + let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + input_rect.translate(primitive.offset * Scale::new(1.0)) + }, + + FilterPrimitiveKind::Flood(..) => surface_rect, + }; + output_rects.push(output_rect); + result_rect = result_rect.union(&output_rect); + } + result_rect + } PictureCompositeMode::SVGFEGraph(ref filters) => { // Return prim_subregion for use in get_local_prim_rect, which // is the polygon size. @@ -4506,6 +4555,53 @@ impl PictureCompositeMode { rect } + PictureCompositeMode::SvgFilter(primitives, _) => { + let mut result_rect = surface_rect; + let mut output_rects = Vec::with_capacity(primitives.len()); + + for (cur_index, primitive) in primitives.iter().enumerate() { + let output_rect = match primitive.kind { + FilterPrimitiveKind::Blur(ref primitive) => { + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + let width_factor = primitive.width.round() * BLUR_SAMPLE_SCALE; + let height_factor = primitive.height.round() * BLUR_SAMPLE_SCALE; + + input.inflate(width_factor, height_factor) + } + FilterPrimitiveKind::DropShadow(ref primitive) => { + let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE; + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + let shadow_rect = input.inflate(inflation_factor, inflation_factor); + input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0))) + } + FilterPrimitiveKind::Blend(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)) + } + FilterPrimitiveKind::Composite(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect)) + } + FilterPrimitiveKind::Identity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::Opacity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::ColorMatrix(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::ComponentTransfer(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect), + FilterPrimitiveKind::Offset(ref primitive) => { + let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(surface_rect); + input_rect.translate(primitive.offset * Scale::new(1.0)) + }, + + FilterPrimitiveKind::Flood(..) => surface_rect, + }; + output_rects.push(output_rect); + result_rect = result_rect.union(&output_rect); + } + result_rect + } PictureCompositeMode::SVGFEGraph(ref filters) => { // surface_rect may be for source or target, so invalidate based // on both interpretations @@ -4528,6 +4624,7 @@ impl PictureCompositeMode { PictureCompositeMode::IntermediateSurface => "IntermediateSurface", PictureCompositeMode::MixBlend(..) => "MixBlend", PictureCompositeMode::SVGFEGraph(..) => "SVGFEGraph", + PictureCompositeMode::SvgFilter(..) => "SvgFilter", PictureCompositeMode::TileCache{..} => "TileCache", PictureCompositeMode::Filter(Filter::Blur{..}) => "Filter::Blur", PictureCompositeMode::Filter(Filter::Brightness(..)) => "Filter::Brightness", @@ -6403,6 +6500,56 @@ impl PicturePrimitive { surface_rects.clipped_local, ); } + PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => { + let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer(); + + let picture_task_id = frame_state.rg_builder.add().init( + RenderTask::new_dynamic( + surface_rects.task_size, + RenderTaskKind::new_picture( + surface_rects.task_size, + surface_rects.needs_scissor_rect, + surface_rects.clipped.min, + surface_spatial_node_index, + raster_spatial_node_index, + device_pixel_scale, + None, + None, + None, + cmd_buffer_index, + can_use_shared_surface, + None, + ) + ).with_uv_rect_kind(surface_rects.uv_rect_kind) + ); + + let is_opaque = false; // TODO + let filter_task_id = request_render_task( + frame_state, + &self.snapshot, + &surface_rects, + is_opaque, + &mut|rg_builder, _| { + RenderTask::new_svg_filter( + primitives, + filter_datas, + rg_builder, + surface_rects.clipped.size().to_i32(), + surface_rects.uv_rect_kind, + picture_task_id, + device_pixel_scale, + ) + } + ); + + primary_render_task_id = filter_task_id; + + surface_descriptor = SurfaceDescriptor::new_chained( + picture_task_id, + filter_task_id, + surface_rects.clipped_local, + ); + } PictureCompositeMode::SVGFEGraph(ref filters) => { let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer(); @@ -6531,6 +6678,7 @@ impl PicturePrimitive { PictureCompositeMode::Filter(..) | PictureCompositeMode::MixBlend(..) | PictureCompositeMode::IntermediateSurface | + PictureCompositeMode::SvgFilter(..) | PictureCompositeMode::SVGFEGraph(..) => { // TODO(gw): We can take advantage of the same logic that // exists in the opaque rect detection for tile @@ -7168,7 +7316,8 @@ impl PicturePrimitive { } PictureCompositeMode::MixBlend(..) | PictureCompositeMode::Blit(_) | - PictureCompositeMode::IntermediateSurface => {} + PictureCompositeMode::IntermediateSurface | + PictureCompositeMode::SvgFilter(..) => {} PictureCompositeMode::SVGFEGraph(ref filters) => { // Update interned filter data for (_node, op) in filters { diff --git a/gfx/wr/webrender/src/prim_store/picture.rs b/gfx/wr/webrender/src/prim_store/picture.rs @@ -3,11 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{ - ColorU, MixBlendMode, PropertyBinding, PropertyBindingId, + ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, + ColorSpace, PropertyBinding, PropertyBindingId, CompositeOperator, RasterSpace, FilterOpGraphPictureBufferId, }; use api::units::Au; use crate::scene_building::IsVisible; +use crate::filterdata::SFilterData; use crate::gpu_types::BlurEdgeMode; use crate::intern::ItemUid; use crate::intern::{Internable, InternDebug, Handle as InternHandle}; @@ -21,6 +23,57 @@ use crate::prim_store::{ #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)] +pub enum CompositeOperatorKey { + Over, + In, + Out, + Atop, + Xor, + Lighter, + Arithmetic([Au; 4]), +} + +impl From<CompositeOperator> for CompositeOperatorKey { + fn from(operator: CompositeOperator) -> Self { + match operator { + CompositeOperator::Over => CompositeOperatorKey::Over, + CompositeOperator::In => CompositeOperatorKey::In, + CompositeOperator::Out => CompositeOperatorKey::Out, + CompositeOperator::Atop => CompositeOperatorKey::Atop, + CompositeOperator::Xor => CompositeOperatorKey::Xor, + CompositeOperator::Lighter => CompositeOperatorKey::Lighter, + CompositeOperator::Arithmetic(k_vals) => { + let k_vals = [ + Au::from_f32_px(k_vals[0]), + Au::from_f32_px(k_vals[1]), + Au::from_f32_px(k_vals[2]), + Au::from_f32_px(k_vals[3]), + ]; + CompositeOperatorKey::Arithmetic(k_vals) + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)] +pub enum FilterPrimitiveKey { + Identity(ColorSpace, FilterPrimitiveInput), + Flood(ColorSpace, ColorU), + Blend(ColorSpace, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveInput), + Blur(ColorSpace, Au, Au, FilterPrimitiveInput), + Opacity(ColorSpace, Au, FilterPrimitiveInput), + ColorMatrix(ColorSpace, [Au; 20], FilterPrimitiveInput), + DropShadow(ColorSpace, (VectorKey, Au, ColorU), FilterPrimitiveInput), + ComponentTransfer(ColorSpace, FilterPrimitiveInput, Vec<SFilterData>), + Offset(ColorSpace, FilterPrimitiveInput, VectorKey), + Composite(ColorSpace, FilterPrimitiveInput, FilterPrimitiveInput, CompositeOperatorKey), +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Copy, Default, MallocSizeOf, PartialEq, Hash, Eq)] pub enum FilterGraphPictureBufferIdKey { #[default] @@ -793,6 +846,7 @@ pub enum PictureCompositeKey { LinearToSrgb, ComponentTransfer(ItemUid), Flood(ColorU), + SvgFilter(Vec<FilterPrimitiveKey>), SVGFEGraph(Vec<(FilterGraphNodeKey, FilterGraphOpKey)>), // MixBlendMode @@ -890,6 +944,43 @@ impl From<Option<PictureCompositeMode>> for PictureCompositeKey { Some(PictureCompositeMode::ComponentTransferFilter(handle)) => { PictureCompositeKey::ComponentTransfer(handle.uid()) } + Some(PictureCompositeMode::SvgFilter(filter_primitives, filter_data)) => { + PictureCompositeKey::SvgFilter(filter_primitives.into_iter().map(|primitive| { + match primitive.kind { + FilterPrimitiveKind::Identity(identity) => FilterPrimitiveKey::Identity(primitive.color_space, identity.input), + FilterPrimitiveKind::Blend(blend) => FilterPrimitiveKey::Blend(primitive.color_space, blend.mode, blend.input1, blend.input2), + FilterPrimitiveKind::Flood(flood) => FilterPrimitiveKey::Flood(primitive.color_space, flood.color.into()), + FilterPrimitiveKind::Blur(blur) => + FilterPrimitiveKey::Blur(primitive.color_space, Au::from_f32_px(blur.width), Au::from_f32_px(blur.height), blur.input), + FilterPrimitiveKind::Opacity(opacity) => + FilterPrimitiveKey::Opacity(primitive.color_space, Au::from_f32_px(opacity.opacity), opacity.input), + FilterPrimitiveKind::ColorMatrix(color_matrix) => { + let mut quantized_values: [Au; 20] = [Au(0); 20]; + for (value, result) in color_matrix.matrix.iter().zip(quantized_values.iter_mut()) { + *result = Au::from_f32_px(*value); + } + FilterPrimitiveKey::ColorMatrix(primitive.color_space, quantized_values, color_matrix.input) + } + FilterPrimitiveKind::DropShadow(drop_shadow) => { + FilterPrimitiveKey::DropShadow( + primitive.color_space, + ( + drop_shadow.shadow.offset.into(), + Au::from_f32_px(drop_shadow.shadow.blur_radius), + drop_shadow.shadow.color.into(), + ), + drop_shadow.input, + ) + } + FilterPrimitiveKind::ComponentTransfer(component_transfer) => + FilterPrimitiveKey::ComponentTransfer(primitive.color_space, component_transfer.input, filter_data.clone()), + FilterPrimitiveKind::Offset(info) => + FilterPrimitiveKey::Offset(primitive.color_space, info.input, info.offset.into()), + FilterPrimitiveKind::Composite(info) => + FilterPrimitiveKey::Composite(primitive.color_space, info.input1, info.input2, info.operator.into()), + } + }).collect()) + } Some(PictureCompositeMode::SVGFEGraph(filter_nodes)) => { PictureCompositeKey::SVGFEGraph( filter_nodes.into_iter().map(|(node, op)| { diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs @@ -13,7 +13,7 @@ use crate::segment::EdgeAaSegmentMask; use crate::spatial_tree::SpatialTree; use crate::clip::{ClipStore, ClipItemKind}; use crate::frame_builder::FrameGlobalResources; -use crate::gpu_types::{BorderInstance, SVGFEFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance}; +use crate::gpu_types::{BorderInstance, SvgFilterInstance, SVGFEFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance}; use crate::gpu_types::{TransformPalette, ZBufferIdGenerator, MaskInstance, ClipSpace, BlurEdgeMode}; use crate::gpu_types::{ZBufferId, QuadSegment, PrimitiveInstanceData, TransformPaletteId}; use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphOp, FrameAllocator, FrameMemory, FrameVec, TextureSource}; @@ -27,7 +27,7 @@ use crate::prim_store::gradient::{ use crate::renderer::{GpuBufferAddress, GpuBufferBuilder}; use crate::render_backend::DataStores; use crate::render_task::{RenderTaskKind, RenderTaskAddress, SubPass}; -use crate::render_task::{RenderTask, ScalingTask, MaskSubPass, SVGFEFilterTask}; +use crate::render_task::{RenderTask, ScalingTask, SvgFilterInfo, MaskSubPass, SVGFEFilterTask}; use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; use crate::resource_cache::ResourceCache; use crate::spatial_tree::SpatialNodeIndex; @@ -159,6 +159,7 @@ pub struct RenderTarget { pub vertical_blurs: FastHashMap<TextureSource, FrameVec<BlurInstance>>, pub horizontal_blurs: FastHashMap<TextureSource, FrameVec<BlurInstance>>, pub scalings: FastHashMap<TextureSource, FrameVec<ScalingInstance>>, + pub svg_filters: FrameVec<(BatchTextures, FrameVec<SvgFilterInstance>)>, pub svg_nodes: FrameVec<(BatchTextures, FrameVec<SVGFEFilterInstance>)>, pub blits: FrameVec<BlitJob>, alpha_tasks: FrameVec<RenderTaskId>, @@ -227,6 +228,7 @@ impl RenderTarget { vertical_blurs: FastHashMap::default(), horizontal_blurs: FastHashMap::default(), scalings: FastHashMap::default(), + svg_filters: memory.new_vec(), svg_nodes: memory.new_vec(), blits: memory.new_vec(), alpha_tasks: memory.new_vec(), @@ -428,6 +430,18 @@ impl RenderTarget { } self.alpha_tasks.push(task_id); } + RenderTaskKind::SvgFilter(ref task_info) => { + add_svg_filter_instances( + &mut self.svg_filters, + render_tasks, + &task_info.info, + task_id, + task.children.get(0).cloned(), + task.children.get(1).cloned(), + task_info.extra_gpu_data, + &ctx.frame_memory, + ) + } RenderTaskKind::SVGFENode(ref task_info) => { add_svg_filter_node_instances( &mut self.svg_nodes, @@ -654,6 +668,102 @@ fn add_scaling_instances( )); } +fn add_svg_filter_instances( + instances: &mut FrameVec<(BatchTextures, FrameVec<SvgFilterInstance>)>, + render_tasks: &RenderTaskGraph, + filter: &SvgFilterInfo, + task_id: RenderTaskId, + input_1_task: Option<RenderTaskId>, + input_2_task: Option<RenderTaskId>, + extra_data_address: Option<GpuBufferAddress>, + memory: &FrameMemory, +) { + let mut textures = BatchTextures::empty(); + + if let Some(id) = input_1_task { + textures.input.colors[0] = render_tasks[id].get_texture_source(); + } + + if let Some(id) = input_2_task { + textures.input.colors[1] = render_tasks[id].get_texture_source(); + } + + let kind = match filter { + SvgFilterInfo::Blend(..) => 0, + SvgFilterInfo::Flood(..) => 1, + SvgFilterInfo::LinearToSrgb => 2, + SvgFilterInfo::SrgbToLinear => 3, + SvgFilterInfo::Opacity(..) => 4, + SvgFilterInfo::ColorMatrix(..) => 5, + SvgFilterInfo::DropShadow(..) => 6, + SvgFilterInfo::Offset(..) => 7, + SvgFilterInfo::ComponentTransfer(..) => 8, + SvgFilterInfo::Identity => 9, + SvgFilterInfo::Composite(..) => 10, + }; + + let input_count = match filter { + SvgFilterInfo::Flood(..) => 0, + + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::ComponentTransfer(..) | + SvgFilterInfo::Identity => 1, + + // Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content. + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Blend(..) | + SvgFilterInfo::Composite(..) => 2, + }; + + let generic_int = match filter { + SvgFilterInfo::Blend(mode) => *mode as u16, + SvgFilterInfo::ComponentTransfer(data) => + (data.r_func.to_int() << 12 | + data.g_func.to_int() << 8 | + data.b_func.to_int() << 4 | + data.a_func.to_int()) as u16, + SvgFilterInfo::Composite(operator) => + operator.as_int() as u16, + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Flood(..) | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::Identity => 0, + }; + + let instance = SvgFilterInstance { + task_address: task_id.into(), + input_1_task_address: input_1_task.map(|id| id.into()).unwrap_or(RenderTaskAddress(0)), + input_2_task_address: input_2_task.map(|id| id.into()).unwrap_or(RenderTaskAddress(0)), + kind, + input_count, + generic_int, + padding: 0, + extra_data_address: extra_data_address.unwrap_or(GpuBufferAddress::INVALID).as_int(), + }; + + for (ref mut batch_textures, ref mut batch) in instances.iter_mut() { + if let Some(combined_textures) = batch_textures.combine_textures(textures) { + batch.push(instance); + // Update the batch textures to the newly combined batch textures + *batch_textures = combined_textures; + return; + } + } + + let mut vec = memory.new_vec(); + vec.push(instance); + + instances.push((textures, vec)); +} + /// Generates SVGFEFilterInstances from a single SVGFEFilterTask, this is what /// prepares vertex data for the shader, and adds it to the appropriate batch. /// diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs @@ -2,8 +2,9 @@ * 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::{LineStyle, LineOrientation, ClipMode, ColorF, FilterOpGraphPictureBufferId}; -use api::{MAX_RENDER_TASK_SIZE, SVGFE_GRAPH_MAX}; +use api::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind, SVGFE_GRAPH_MAX}; +use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace, FilterOpGraphPictureBufferId}; +use api::MAX_RENDER_TASK_SIZE; use api::units::*; use std::time::Duration; use crate::box_shadow::BLUR_SAMPLE_SCALE; @@ -12,6 +13,7 @@ use crate::command_buffer::{CommandBufferIndex, QuadFlags}; use crate::pattern::{PatternKind, PatternShaderInput}; use crate::profiler::{add_text_marker}; use crate::spatial_tree::SpatialNodeIndex; +use crate::filterdata::SFilterData; use crate::frame_builder::FrameBuilderConfig; use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind, TransformPaletteId, BlurEdgeMode}; use crate::internal_types::{CacheTextureId, FastHashMap, FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT, TextureSource, Swizzle}; @@ -323,6 +325,32 @@ pub struct LineDecorationTask { #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum SvgFilterInfo { + Blend(MixBlendMode), + Flood(ColorF), + LinearToSrgb, + SrgbToLinear, + Opacity(f32), + ColorMatrix(Box<[f32; 20]>), + DropShadow(ColorF), + Offset(DeviceVector2D), + ComponentTransfer(SFilterData), + Composite(CompositeOperator), + // TODO: This is used as a hack to ensure that a blur task's input is always in the blur's previous pass. + Identity, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SvgFilterTask { + pub info: SvgFilterInfo, + pub extra_gpu_data: Option<GpuBufferAddress>, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct SVGFEFilterTask { pub node: FilterGraphNode, pub op: FilterGraphOp, @@ -366,6 +394,7 @@ pub enum RenderTaskKind { LinearGradient(LinearGradientTask), RadialGradient(RadialGradientTask), ConicGradient(ConicGradientTask), + SvgFilter(SvgFilterTask), SVGFENode(SVGFEFilterTask), TileComposite(TileCompositeTask), Prim(PrimTask), @@ -417,6 +446,7 @@ impl RenderTaskKind { RenderTaskKind::LinearGradient(..) => "LinearGradient", RenderTaskKind::RadialGradient(..) => "RadialGradient", RenderTaskKind::ConicGradient(..) => "ConicGradient", + RenderTaskKind::SvgFilter(..) => "SvgFilter", RenderTaskKind::SVGFENode(..) => "SVGFENode", RenderTaskKind::TileComposite(..) => "TileComposite", RenderTaskKind::Prim(..) => "Prim", @@ -439,7 +469,8 @@ impl RenderTaskKind { RenderTaskKind::Picture(..) | RenderTaskKind::Blit(..) | RenderTaskKind::TileComposite(..) | - RenderTaskKind::Prim(..) => { + RenderTaskKind::Prim(..) | + RenderTaskKind::SvgFilter(..) => { RenderTargetKind::Color } RenderTaskKind::SVGFENode(..) => { @@ -781,6 +812,13 @@ impl RenderTaskKind { [0.0; 4] } + RenderTaskKind::SvgFilter(ref task) => { + match task.info { + SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0, 0.0], + SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0, 0.0], + _ => [0.0; 4] + } + } RenderTaskKind::SVGFENode(_task) => { // we don't currently use this for SVGFE filters. // see SVGFEFilterInstance instead @@ -812,6 +850,34 @@ impl RenderTaskKind { gpu_buffer: &mut GpuBufferBuilder, ) { match self { + RenderTaskKind::SvgFilter(ref mut filter_task) => { + match filter_task.info { + SvgFilterInfo::ColorMatrix(ref matrix) => { + let mut writer = gpu_buffer.f32.write_blocks(5); + for i in 0..5 { + writer.push_one([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]); + } + filter_task.extra_gpu_data = Some(writer.finish()); + } + SvgFilterInfo::DropShadow(color) | + SvgFilterInfo::Flood(color) => { + let mut writer = gpu_buffer.f32.write_blocks(1); + writer.push_one(color.to_array()); + filter_task.extra_gpu_data = Some(writer.finish()); + } + SvgFilterInfo::ComponentTransfer(ref data) => { + filter_task.extra_gpu_data = Some(data.write_gpu_blocks(&mut gpu_buffer.f32)); + } + SvgFilterInfo::Composite(ref operator) => { + if let CompositeOperator::Arithmetic(k_vals) = operator { + let mut writer = gpu_buffer.f32.write_blocks(1); + writer.push_one(*k_vals); + filter_task.extra_gpu_data = Some(writer.finish()); + } + } + _ => {}, + } + } RenderTaskKind::SVGFENode(ref mut filter_task) => { match filter_task.op { FilterGraphOp::SVGFEBlendDarken => {} @@ -1233,6 +1299,339 @@ impl RenderTask { task_id } + pub fn new_svg_filter( + filter_primitives: &[FilterPrimitive], + filter_datas: &[SFilterData], + rg_builder: &mut RenderTaskGraphBuilder, + content_size: DeviceIntSize, + uv_rect_kind: UvRectKind, + original_task_id: RenderTaskId, + device_pixel_scale: DevicePixelScale, + ) -> RenderTaskId { + + if filter_primitives.is_empty() { + return original_task_id; + } + + // Resolves the input to a filter primitive + let get_task_input = | + input: &FilterPrimitiveInput, + filter_primitives: &[FilterPrimitive], + rg_builder: &mut RenderTaskGraphBuilder, + cur_index: usize, + outputs: &[RenderTaskId], + original: RenderTaskId, + color_space: ColorSpace, + | { + // TODO(cbrewster): Not sure we can assume that the original input is sRGB. + let (mut task_id, input_color_space) = match input.to_index(cur_index) { + Some(index) => (outputs[index], filter_primitives[index].color_space), + None => (original, ColorSpace::Srgb), + }; + + match (input_color_space, color_space) { + (ColorSpace::Srgb, ColorSpace::LinearRgb) => { + task_id = RenderTask::new_svg_filter_primitive( + smallvec![task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::SrgbToLinear, + rg_builder, + ); + }, + (ColorSpace::LinearRgb, ColorSpace::Srgb) => { + task_id = RenderTask::new_svg_filter_primitive( + smallvec![task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::LinearToSrgb, + rg_builder, + ); + }, + _ => {}, + } + + task_id + }; + + let mut outputs = vec![]; + let mut cur_filter_data = 0; + for (cur_index, primitive) in filter_primitives.iter().enumerate() { + let render_task_id = match primitive.kind { + FilterPrimitiveKind::Identity(ref identity) => { + // Identity does not create a task, it provides its input's render task + get_task_input( + &identity.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ) + } + FilterPrimitiveKind::Blend(ref blend) => { + let input_1_task_id = get_task_input( + &blend.input1, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + let input_2_task_id = get_task_input( + &blend.input2, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_svg_filter_primitive( + smallvec![input_1_task_id, input_2_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Blend(blend.mode), + rg_builder, + ) + }, + FilterPrimitiveKind::Flood(ref flood) => { + RenderTask::new_svg_filter_primitive( + smallvec![], + content_size, + uv_rect_kind, + SvgFilterInfo::Flood(flood.color), + rg_builder, + ) + } + FilterPrimitiveKind::Blur(ref blur) => { + let width_std_deviation = blur.width * device_pixel_scale.0; + let height_std_deviation = blur.height * device_pixel_scale.0; + let input_task_id = get_task_input( + &blur.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_blur( + DeviceSize::new(width_std_deviation, height_std_deviation), + // TODO: This is a hack to ensure that a blur task's input is always + // in the blur's previous pass. + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Identity, + rg_builder, + ), + rg_builder, + RenderTargetKind::Color, + None, + content_size, + BlurEdgeMode::Duplicate, + ) + } + FilterPrimitiveKind::Opacity(ref opacity) => { + let input_task_id = get_task_input( + &opacity.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Opacity(opacity.opacity), + rg_builder, + ) + } + FilterPrimitiveKind::ColorMatrix(ref color_matrix) => { + let input_task_id = get_task_input( + &color_matrix.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)), + rg_builder, + ) + } + FilterPrimitiveKind::DropShadow(ref drop_shadow) => { + let input_task_id = get_task_input( + &drop_shadow.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let blur_std_deviation = drop_shadow.shadow.blur_radius * device_pixel_scale.0; + let offset = drop_shadow.shadow.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale; + + let offset_task_id = RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Offset(offset), + rg_builder, + ); + + let blur_task_id = RenderTask::new_blur( + DeviceSize::new(blur_std_deviation, blur_std_deviation), + offset_task_id, + rg_builder, + RenderTargetKind::Color, + None, + content_size, + BlurEdgeMode::Duplicate, + ); + + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id, blur_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::DropShadow(drop_shadow.shadow.color), + rg_builder, + ) + } + FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => { + let input_task_id = get_task_input( + &component_transfer.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let filter_data = &filter_datas[cur_filter_data]; + cur_filter_data += 1; + if filter_data.is_identity() { + input_task_id + } else { + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::ComponentTransfer(filter_data.clone()), + rg_builder, + ) + } + } + FilterPrimitiveKind::Offset(ref info) => { + let input_task_id = get_task_input( + &info.input, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let offset = info.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale; + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Offset(offset), + rg_builder, + ) + } + FilterPrimitiveKind::Composite(info) => { + let input_1_task_id = get_task_input( + &info.input1, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + let input_2_task_id = get_task_input( + &info.input2, + filter_primitives, + rg_builder, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_svg_filter_primitive( + smallvec![input_1_task_id, input_2_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Composite(info.operator), + rg_builder, + ) + } + }; + outputs.push(render_task_id); + } + + // The output of a filter is the output of the last primitive in the chain. + let mut render_task_id = *outputs.last().unwrap(); + + // Convert to sRGB if needed + if filter_primitives.last().unwrap().color_space == ColorSpace::LinearRgb { + render_task_id = RenderTask::new_svg_filter_primitive( + smallvec![render_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::LinearToSrgb, + rg_builder, + ); + } + + render_task_id + } + + pub fn new_svg_filter_primitive( + tasks: TaskDependencies, + target_size: DeviceIntSize, + uv_rect_kind: UvRectKind, + info: SvgFilterInfo, + rg_builder: &mut RenderTaskGraphBuilder, + ) -> RenderTaskId { + let task_id = rg_builder.add().init(RenderTask::new_dynamic( + target_size, + RenderTaskKind::SvgFilter(SvgFilterTask { + extra_gpu_data: None, + info, + }), + ).with_uv_rect_kind(uv_rect_kind)); + + for child_id in tasks { + rg_builder.add_dependency(task_id, child_id); + } + + task_id + } + pub fn add_sub_pass( &mut self, sub_pass: SubPass, diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs @@ -70,7 +70,7 @@ use crate::device::FBOId; use crate::debug_item::DebugItem; use crate::frame_builder::Frame; use glyph_rasterizer::GlyphFormat; -use crate::gpu_types::{ScalingInstance, SVGFEFilterInstance, CopyInstance, PrimitiveInstanceData}; +use crate::gpu_types::{ScalingInstance, SvgFilterInstance, SVGFEFilterInstance, CopyInstance, PrimitiveInstanceData}; use crate::gpu_types::{BlurInstance, ClearInstance, CompositeInstance, ZBufferId}; use crate::internal_types::{TextureSource, TextureSourceExternal, FrameVec}; #[cfg(any(feature = "capture", feature = "replay"))] @@ -259,6 +259,10 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag { label: "Transparent pass", color: debug_colors::BLACK, }; +const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag { + label: "SvgFilter", + color: debug_colors::LEMONCHIFFON, +}; const GPU_TAG_SVG_FILTER_NODES: GpuProfileTag = GpuProfileTag { label: "SvgFilterNodes", color: debug_colors::LEMONCHIFFON, @@ -2699,6 +2703,35 @@ impl Renderer { } } + fn handle_svg_filters( + &mut self, + textures: &BatchTextures, + svg_filters: &[SvgFilterInstance], + projection: &default::Transform3D<f32>, + stats: &mut RendererStats, + ) { + if svg_filters.is_empty() { + return; + } + + let _timer = self.gpu_profiler.start_timer(GPU_TAG_SVG_FILTER); + + self.shaders.borrow_mut().cs_svg_filter().bind( + &mut self.device, + &projection, + None, + &mut self.renderer_errors, + &mut self.profile, + ); + + self.draw_instanced_batch( + &svg_filters, + VertexArrayKind::SvgFilter, + textures, + stats, + ); + } + fn handle_svg_nodes( &mut self, textures: &BatchTextures, @@ -4697,6 +4730,15 @@ impl Renderer { stats, ); + for (ref textures, ref filters) in &target.svg_filters { + self.handle_svg_filters( + textures, + filters, + &projection, + stats, + ); + } + for (ref textures, ref filters) in &target.svg_nodes { self.handle_svg_nodes(textures, filters, &projection, stats); } diff --git a/gfx/wr/webrender/src/renderer/shade.rs b/gfx/wr/webrender/src/renderer/shade.rs @@ -269,6 +269,7 @@ impl LazilyCompiledShader { VertexArrayKind::Border => &desc::BORDER, VertexArrayKind::Scale => &desc::SCALE, VertexArrayKind::Resolve => &desc::RESOLVE, + VertexArrayKind::SvgFilter => &desc::SVG_FILTER, VertexArrayKind::SvgFilterNode => &desc::SVG_FILTER_NODE, VertexArrayKind::Composite => &desc::COMPOSITE, VertexArrayKind::Clear => &desc::CLEAR, @@ -619,6 +620,7 @@ pub struct Shaders { cs_linear_gradient: ShaderHandle, cs_radial_gradient: ShaderHandle, cs_conic_gradient: ShaderHandle, + cs_svg_filter: ShaderHandle, cs_svg_filter_node: ShaderHandle, // Brush shaders @@ -758,6 +760,13 @@ impl Shaders { &shader_list, )?; + let cs_svg_filter = loader.create_shader( + ShaderKind::Cache(VertexArrayKind::SvgFilter), + "cs_svg_filter", + &[], + &shader_list, + )?; + let cs_svg_filter_node = loader.create_shader( ShaderKind::Cache(VertexArrayKind::SvgFilterNode), "cs_svg_filter_node", @@ -1088,6 +1097,7 @@ impl Shaders { cs_conic_gradient, cs_border_solid, cs_scale, + cs_svg_filter, cs_svg_filter_node, brush_solid, brush_image, @@ -1302,6 +1312,7 @@ impl Shaders { pub fn cs_linear_gradient(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_linear_gradient) } pub fn cs_radial_gradient(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_radial_gradient) } pub fn cs_conic_gradient(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_conic_gradient) } + pub fn cs_svg_filter(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_svg_filter) } pub fn cs_svg_filter_node(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_svg_filter_node) } pub fn cs_clip_rectangle_slow(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_clip_rectangle_slow) } pub fn cs_clip_rectangle_fast(&mut self) -> &mut LazilyCompiledShader { self.loader.get(self.cs_clip_rectangle_fast) } diff --git a/gfx/wr/webrender/src/renderer/vertex.rs b/gfx/wr/webrender/src/renderer/vertex.rs @@ -518,6 +518,56 @@ pub mod desc { }], }; + pub const SVG_FILTER: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::U8Norm, + }], + instance_attributes: &[ + VertexAttribute { + name: "aFilterRenderTaskAddress", + count: 1, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aFilterInput1TaskAddress", + count: 1, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aFilterInput2TaskAddress", + count: 1, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aFilterKind", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterInputCount", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterGenericInt", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aUnused", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterExtraDataAddress", + count: 1, + kind: VertexAttributeKind::I32, + }, + ], + }; + pub const SVG_FILTER_NODE: VertexDescriptor = VertexDescriptor { vertex_attributes: &[VertexAttribute { name: "aPosition", @@ -790,6 +840,7 @@ pub enum VertexArrayKind { RadialGradient, ConicGradient, Resolve, + SvgFilter, SvgFilterNode, Composite, Clear, @@ -1014,6 +1065,7 @@ pub struct RendererVAOs { radial_gradient_vao: VAO, conic_gradient_vao: VAO, resolve_vao: VAO, + svg_filter_vao: VAO, svg_filter_node_vao: VAO, composite_vao: VAO, clear_vao: VAO, @@ -1061,6 +1113,7 @@ impl RendererVAOs { radial_gradient_vao: device.create_vao_with_new_instances(&desc::RADIAL_GRADIENT, &prim_vao), conic_gradient_vao: device.create_vao_with_new_instances(&desc::CONIC_GRADIENT, &prim_vao), resolve_vao: device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao), + svg_filter_vao: device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao), svg_filter_node_vao: device.create_vao_with_new_instances(&desc::SVG_FILTER_NODE, &prim_vao), composite_vao: device.create_vao_with_new_instances(&desc::COMPOSITE, &prim_vao), clear_vao: device.create_vao_with_new_instances(&desc::CLEAR, &prim_vao), @@ -1083,6 +1136,7 @@ impl RendererVAOs { device.delete_vao(self.line_vao); device.delete_vao(self.border_vao); device.delete_vao(self.scale_vao); + device.delete_vao(self.svg_filter_vao); device.delete_vao(self.svg_filter_node_vao); device.delete_vao(self.composite_vao); device.delete_vao(self.clear_vao); @@ -1108,6 +1162,7 @@ impl ops::Index<VertexArrayKind> for RendererVAOs { VertexArrayKind::RadialGradient => &self.radial_gradient_vao, VertexArrayKind::ConicGradient => &self.conic_gradient_vao, VertexArrayKind::Resolve => &self.resolve_vao, + VertexArrayKind::SvgFilter => &self.svg_filter_vao, VertexArrayKind::SvgFilterNode => &self.svg_filter_node_vao, VertexArrayKind::Composite => &self.composite_vao, VertexArrayKind::Clear => &self.clear_vao, diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs @@ -38,7 +38,7 @@ use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, BuiltDisplayListIter, PrimitiveFlags, SnapshotInfo}; use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace}; use api::{DebugFlags, DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData}; -use api::{FilterOp, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop}; +use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop}; use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings}; use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags}; use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDescriptor}; @@ -152,6 +152,7 @@ pub struct CompositeOps { // Requires only a single texture as input (e.g. most filters) pub filters: Vec<Filter>, pub filter_datas: Vec<FilterData>, + pub filter_primitives: Vec<FilterPrimitive>, pub snapshot: Option<SnapshotInfo>, // Requires two source textures (e.g. mix-blend-mode) @@ -162,12 +163,14 @@ impl CompositeOps { pub fn new( filters: Vec<Filter>, filter_datas: Vec<FilterData>, + filter_primitives: Vec<FilterPrimitive>, mix_blend_mode: Option<MixBlendMode>, snapshot: Option<SnapshotInfo>, ) -> Self { CompositeOps { filters, filter_datas, + filter_primitives, mix_blend_mode, snapshot, } @@ -175,6 +178,7 @@ impl CompositeOps { pub fn is_empty(&self) -> bool { self.filters.is_empty() && + self.filter_primitives.is_empty() && self.mix_blend_mode.is_none() && self.snapshot.is_none() } @@ -208,6 +212,10 @@ impl CompositeOps { } } + if !self.filter_primitives.is_empty() { + return true; + } + false } } @@ -805,6 +813,7 @@ impl<'a> SceneBuilder<'a> { let has_blur = match &pictures[pic_index.0].composite_mode { Some(PictureCompositeMode::Filter(Filter::Blur { .. })) => true, Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) => true, + Some(PictureCompositeMode::SvgFilter( .. )) => true, Some(PictureCompositeMode::SVGFEGraph( .. )) => true, _ => false, }; @@ -997,6 +1006,7 @@ impl<'a> SceneBuilder<'a> { let composition_operations = CompositeOps::new( filter_ops_for_compositing(item.filters()), filter_datas_for_compositing(item.filter_datas()), + filter_primitives_for_compositing(item.filter_primitives()), info.stacking_context.mix_blend_mode_for_compositing(), snapshot, ); @@ -1921,6 +1931,7 @@ impl<'a> SceneBuilder<'a> { let filters = filter_ops_for_compositing(item.filters()); let filter_datas = filter_datas_for_compositing(item.filter_datas()); + let filter_primitives = filter_primitives_for_compositing(item.filter_primitives()); self.add_backdrop_filter( spatial_node_index, @@ -1928,6 +1939,7 @@ impl<'a> SceneBuilder<'a> { &layout, filters, filter_datas, + filter_primitives, ); } @@ -1935,6 +1947,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::SetGradientStops | DisplayItem::SetFilterOps | DisplayItem::SetFilterData | + DisplayItem::SetFilterPrimitives | DisplayItem::SetPoints => {} // Special items that are handled in the parent method @@ -2222,6 +2235,7 @@ impl<'a> SceneBuilder<'a> { CompositeOps { filters: Vec::new(), filter_datas: Vec::new(), + filter_primitives: Vec::new(), mix_blend_mode: None, snapshot, }, @@ -2690,6 +2704,7 @@ impl<'a> SceneBuilder<'a> { source, stacking_context.clip_node_id, stacking_context.composite_ops.filters, + stacking_context.composite_ops.filter_primitives, stacking_context.composite_ops.filter_datas, false, spatial_node_context_offset, @@ -3789,6 +3804,7 @@ impl<'a> SceneBuilder<'a> { info: &LayoutPrimitiveInfo, filters: Vec<Filter>, filter_datas: Vec<FilterData>, + filter_primitives: Vec<FilterPrimitive>, ) { // We don't know the spatial node for a backdrop filter, as it's whatever is the // backdrop root, but we can't know this if the root is a picture cache slice @@ -3842,6 +3858,7 @@ impl<'a> SceneBuilder<'a> { source, clip_node_id, filters, + filter_primitives, filter_datas, true, LayoutVector2D::zero(), @@ -3940,10 +3957,17 @@ impl<'a> SceneBuilder<'a> { mut source: PictureChainBuilder, clip_node_id: ClipNodeId, mut filter_ops: Vec<Filter>, + mut filter_primitives: Vec<FilterPrimitive>, filter_datas: Vec<FilterData>, is_backdrop_filter: bool, context_offset: LayoutVector2D, ) -> PictureChainBuilder { + // TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will + // happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives. + // At some point, these two should be combined and CSS filters should be expressed in terms of SVG filters. + assert!(filter_ops.is_empty() || filter_primitives.is_empty(), + "Filter ops and filter primitives are not allowed on the same stacking context."); + // For each filter, create a new image with that composite mode. let mut current_filter_data_index = 0; // Check if the filter chain is actually an SVGFE filter graph DAG @@ -4502,6 +4526,44 @@ impl<'a> SceneBuilder<'a> { ); } + if !filter_primitives.is_empty() { + let filter_datas = filter_datas.iter() + .map(|filter_data| filter_data.sanitize()) + .map(|filter_data| { + SFilterData { + r_func: SFilterDataComponent::from_functype_values( + filter_data.func_r_type, &filter_data.r_values), + g_func: SFilterDataComponent::from_functype_values( + filter_data.func_g_type, &filter_data.g_values), + b_func: SFilterDataComponent::from_functype_values( + filter_data.func_b_type, &filter_data.b_values), + a_func: SFilterDataComponent::from_functype_values( + filter_data.func_a_type, &filter_data.a_values), + } + }) + .collect(); + + // Sanitize filter inputs + for primitive in &mut filter_primitives { + primitive.sanitize(); + } + + let composite_mode = PictureCompositeMode::SvgFilter( + filter_primitives, + filter_datas, + ); + + source = source.add_picture( + composite_mode, + clip_node_id, + Picture3DContext::Out, + &mut self.interners, + &mut self.prim_store, + &mut self.prim_instances, + &mut self.clip_tree_builder, + ); + } + source } } @@ -4801,6 +4863,16 @@ fn filter_datas_for_compositing( filter_datas } +fn filter_primitives_for_compositing( + input_filter_primitives: ItemRange<FilterPrimitive>, +) -> Vec<FilterPrimitive> { + // Resolve these in the flattener? + // TODO(gw): Now that we resolve these later on, + // we could probably make it a bit + // more efficient than cloning these here. + input_filter_primitives.iter().map(|primitive| primitive).collect() +} + fn process_repeat_size( snapped_rect: &LayoutRect, unsnapped_rect: &LayoutRect, diff --git a/gfx/wr/webrender_api/src/display_item.rs b/gfx/wr/webrender_api/src/display_item.rs @@ -195,6 +195,7 @@ pub enum DisplayItem { SetGradientStops, SetFilterOps, SetFilterData, + SetFilterPrimitives, SetPoints, // These marker items terminate a scope introduced by a previous item. @@ -243,6 +244,7 @@ pub enum DebugDisplayItem { SetGradientStops(Vec<GradientStop>), SetFilterOps(Vec<FilterOp>), SetFilterData(FilterData), + SetFilterPrimitives(Vec<FilterPrimitive>), SetPoints(Vec<LayoutPoint>), PopReferenceFrame, @@ -2289,6 +2291,7 @@ impl DisplayItem { DisplayItem::PushStackingContext(..) => "push_stacking_context", DisplayItem::SetFilterOps => "set_filter_ops", DisplayItem::SetFilterData => "set_filter_data", + DisplayItem::SetFilterPrimitives => "set_filter_primitives", DisplayItem::SetPoints => "set_points", DisplayItem::RadialGradient(..) => "radial_gradient", DisplayItem::Rectangle(..) => "rectangle", diff --git a/gfx/wr/webrender_api/src/display_list.rs b/gfx/wr/webrender_api/src/display_list.rs @@ -349,6 +349,10 @@ impl<'de> Deserialize<'de> for DisplayListWithCache { DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values); Real::SetFilterData }, + Debug::SetFilterPrimitives(filter_primitives) => { + DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives); + Real::SetFilterPrimitives + } Debug::SetGradientStops(stops) => { DisplayListBuilder::push_iter_impl(&mut temp, stops); Real::SetGradientStops @@ -415,6 +419,7 @@ pub struct BuiltDisplayListIter<'a> { cur_glyphs: ItemRange<'a, GlyphInstance>, cur_filters: ItemRange<'a, di::FilterOp>, cur_filter_data: Vec<TempFilterData<'a>>, + cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>, cur_clip_chain_items: ItemRange<'a, di::ClipId>, cur_points: ItemRange<'a, LayoutPoint>, peeking: Peek, @@ -525,6 +530,10 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> { pub fn filter_datas(&self) -> &Vec<TempFilterData> { &self.iter.cur_filter_data } + + pub fn filter_primitives(&self) -> ItemRange<di::FilterPrimitive> { + self.iter.cur_filter_primitives + } } #[derive(PartialEq)] @@ -665,6 +674,9 @@ impl BuiltDisplayList { a_values: temp_filter_data.a_values.iter().collect(), }) }, + Real::SetFilterPrimitives => Debug::SetFilterPrimitives( + item.iter.cur_filter_primitives.iter().collect() + ), Real::SetGradientStops => Debug::SetGradientStops( item.iter.cur_stops.iter().collect() ), @@ -736,6 +748,7 @@ impl<'a> BuiltDisplayListIter<'a> { cur_glyphs: ItemRange::default(), cur_filters: ItemRange::default(), cur_filter_data: Vec::new(), + cur_filter_primitives: ItemRange::default(), cur_clip_chain_items: ItemRange::default(), cur_points: ItemRange::default(), peeking: Peek::NotPeeking, @@ -803,6 +816,7 @@ impl<'a> BuiltDisplayListIter<'a> { self.cur_clip_chain_items = ItemRange::default(); self.cur_points = ItemRange::default(); self.cur_filters = ItemRange::default(); + self.cur_filter_primitives = ItemRange::default(); self.cur_filter_data.clear(); loop { @@ -811,6 +825,7 @@ impl<'a> BuiltDisplayListIter<'a> { SetGradientStops | SetFilterOps | SetFilterData | + SetFilterPrimitives | SetPoints => { // These are marker items for populating other display items, don't yield them. continue; @@ -869,6 +884,10 @@ impl<'a> BuiltDisplayListIter<'a> { self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values); self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values); } + SetFilterPrimitives => { + self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data); + self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives); + } SetPoints => { self.cur_points = skip_slice::<LayoutPoint>(&mut self.data); self.debug_stats.log_slice("set_points.points", &self.cur_points); @@ -1771,12 +1790,13 @@ impl DisplayListBuilder { mix_blend_mode: di::MixBlendMode, filters: &[di::FilterOp], filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], raster_space: di::RasterSpace, flags: di::StackingContextFlags, snapshot: Option<di::SnapshotInfo> ) { let ref_frame_offset = self.rf_mapper.current_offset(); - self.push_filters(filters, filter_datas); + self.push_filters(filters, filter_datas, filter_primitives); let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem { origin, @@ -1810,6 +1830,7 @@ impl DisplayListBuilder { prim_flags, &[], &[], + &[], ); } @@ -1821,6 +1842,7 @@ impl DisplayListBuilder { prim_flags: di::PrimitiveFlags, filters: &[di::FilterOp], filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], ) { self.push_stacking_context( origin, @@ -1831,6 +1853,7 @@ impl DisplayListBuilder { di::MixBlendMode::Normal, filters, filter_datas, + filter_primitives, di::RasterSpace::Screen, di::StackingContextFlags::empty(), None, @@ -1855,13 +1878,14 @@ impl DisplayListBuilder { common: &di::CommonItemProperties, filters: &[di::FilterOp], filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], ) { let common = di::CommonItemProperties { clip_rect: self.remap_bounds(common.clip_rect), ..*common }; - self.push_filters(filters, filter_datas); + self.push_filters(filters, filter_datas, filter_primitives); let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem { common, @@ -1873,6 +1897,7 @@ impl DisplayListBuilder { &mut self, filters: &[di::FilterOp], filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], ) { if !filters.is_empty() { self.push_item(&di::DisplayItem::SetFilterOps); @@ -1890,6 +1915,11 @@ impl DisplayListBuilder { self.push_iter(&filter_data.b_values); self.push_iter(&filter_data.a_values); } + + if !filter_primitives.is_empty() { + self.push_item(&di::DisplayItem::SetFilterPrimitives); + self.push_iter(filter_primitives); + } } pub fn push_debug(&mut self, val: u32) { diff --git a/gfx/wr/webrender_build/src/shader_features.rs b/gfx/wr/webrender_build/src/shader_features.rs @@ -78,6 +78,7 @@ pub fn get_shader_features(flags: ShaderFeatureFlags) -> ShaderFeatures { "cs_fast_linear_gradient", "cs_border_segment", "cs_border_solid", + "cs_svg_filter", "cs_svg_filter_node", ] { shaders.insert(name, vec![String::new()]); diff --git a/gfx/wr/wrench/src/yaml_frame_reader.rs b/gfx/wr/wrench/src/yaml_frame_reader.rs @@ -2059,6 +2059,7 @@ impl YamlFrameReader { let filters = yaml["filters"].as_vec_filter_op().unwrap_or_default(); let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or_default(); + let filter_primitives = yaml["filter-primitives"].as_vec_filter_primitive().unwrap_or_default(); let snapshot = if !yaml["snapshot"].is_badvalue() { let yaml = &yaml["snapshot"]; @@ -2094,6 +2095,7 @@ impl YamlFrameReader { mix_blend_mode, &filters, &filter_datas, + &filter_primitives, raster_space, flags, snapshot, @@ -2124,11 +2126,13 @@ impl YamlFrameReader { let filters = item["filters"].as_vec_filter_op().unwrap_or_default(); let filter_datas = item["filter-datas"].as_vec_filter_data().unwrap_or_default(); + let filter_primitives = item["filter-primitives"].as_vec_filter_primitive().unwrap_or_default(); dl.push_backdrop_filter( info, &filters, &filter_datas, + &filter_primitives, ); } } diff --git a/gfx/wr/wrench/src/yaml_helper.rs b/gfx/wr/wrench/src/yaml_helper.rs @@ -39,6 +39,10 @@ pub trait YamlHelper { fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>; fn as_filter_data(&self) -> Option<FilterData>; fn as_vec_filter_data(&self) -> Option<Vec<FilterData>>; + fn as_filter_input(&self) -> Option<FilterPrimitiveInput>; + fn as_filter_primitive(&self) -> Option<FilterPrimitive>; + fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>>; + fn as_color_space(&self) -> Option<ColorSpace>; fn as_complex_clip_region(&self) -> ComplexClipRegion; fn as_sticky_offset_bounds(&self) -> StickyOffsetBounds; fn as_gradient(&self, dl: &mut DisplayListBuilder) -> Gradient; @@ -968,6 +972,24 @@ impl YamlHelper for Yaml { None } + fn as_filter_input(&self) -> Option<FilterPrimitiveInput> { + if let Some(input) = self.as_str() { + match input { + "original" => Some(FilterPrimitiveInput::Original), + "previous" => Some(FilterPrimitiveInput::Previous), + _ => None, + } + } else if let Some(index) = self.as_i64() { + if index >= 0 { + Some(FilterPrimitiveInput::OutputOfPrimitiveIndex(index as usize)) + } else { + panic!("Filter input index cannot be negative"); + } + } else { + panic!("Invalid filter input"); + } + } + fn as_vec_filter_data(&self) -> Option<Vec<FilterData>> { if let Some(v) = self.as_vec() { Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect()) @@ -976,6 +998,116 @@ impl YamlHelper for Yaml { } } + fn as_filter_primitive(&self) -> Option<FilterPrimitive> { + if let Some(filter_type) = self["type"].as_str() { + let kind = match filter_type { + "identity" => { + FilterPrimitiveKind::Identity(IdentityPrimitive { + input: self["in"].as_filter_input().unwrap(), + }) + } + "blend" => { + FilterPrimitiveKind::Blend(BlendPrimitive { + input1: self["in1"].as_filter_input().unwrap(), + input2: self["in2"].as_filter_input().unwrap(), + mode: self["blend-mode"].as_mix_blend_mode().unwrap(), + }) + } + "flood" => { + FilterPrimitiveKind::Flood(FloodPrimitive { + color: self["color"].as_colorf().unwrap(), + }) + } + "blur" => { + FilterPrimitiveKind::Blur(BlurPrimitive { + input: self["in"].as_filter_input().unwrap(), + width: self["width"].as_f32().unwrap(), + height: self["height"].as_f32().unwrap(), + }) + } + "opacity" => { + FilterPrimitiveKind::Opacity(OpacityPrimitive { + input: self["in"].as_filter_input().unwrap(), + opacity: self["opacity"].as_f32().unwrap(), + }) + } + "color-matrix" => { + let m: Vec<f32> = self["matrix"].as_vec_f32().unwrap(); + let mut matrix: [f32; 20] = [0.0; 20]; + matrix.clone_from_slice(&m); + + FilterPrimitiveKind::ColorMatrix(ColorMatrixPrimitive { + input: self["in"].as_filter_input().unwrap(), + matrix, + }) + } + "drop-shadow" => { + FilterPrimitiveKind::DropShadow(DropShadowPrimitive { + input: self["in"].as_filter_input().unwrap(), + shadow: Shadow { + offset: self["offset"].as_vector().unwrap(), + color: self["color"].as_colorf().unwrap(), + blur_radius: self["radius"].as_f32().unwrap(), + } + }) + } + "component-transfer" => { + FilterPrimitiveKind::ComponentTransfer(ComponentTransferPrimitive { + input: self["in"].as_filter_input().unwrap(), + }) + } + "offset" => { + FilterPrimitiveKind::Offset(OffsetPrimitive { + input: self["in"].as_filter_input().unwrap(), + offset: self["offset"].as_vector().unwrap(), + }) + } + "composite" => { + let operator = match self["operator"].as_str().unwrap() { + "over" => CompositeOperator::Over, + "in" => CompositeOperator::In, + "out" => CompositeOperator::Out, + "atop" => CompositeOperator::Atop, + "xor" => CompositeOperator::Xor, + "lighter" => CompositeOperator::Lighter, + "arithmetic" => { + let k_vals = self["k-values"].as_vec_f32().unwrap(); + assert!(k_vals.len() == 4, "Must be 4 k values for arithmetic composite operator"); + let k_vals = [k_vals[0], k_vals[1], k_vals[2], k_vals[3]]; + CompositeOperator::Arithmetic(k_vals) + } + _ => panic!("Invalid composite operator"), + }; + FilterPrimitiveKind::Composite(CompositePrimitive { + input1: self["in1"].as_filter_input().unwrap(), + input2: self["in2"].as_filter_input().unwrap(), + operator, + }) + } + _ => return None, + }; + + Some(FilterPrimitive { + kind, + color_space: self["color-space"].as_color_space().unwrap_or(ColorSpace::LinearRgb), + }) + } else { + None + } + } + + fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>> { + if let Some(v) = self.as_vec() { + Some(v.iter().map(|x| x.as_filter_primitive().unwrap()).collect()) + } else { + self.as_filter_primitive().map(|data| vec![data]) + } + } + + fn as_color_space(&self) -> Option<ColorSpace> { + self.as_str().and_then(StringEnum::from_str) + } + fn as_complex_clip_region(&self) -> ComplexClipRegion { let rect = self["rect"] .as_rect() diff --git a/gfx/wr/wrshell/src/wrench.rs b/gfx/wr/wrshell/src/wrench.rs @@ -615,6 +615,7 @@ impl YamlWriter { DisplayItem::SetGradientStops => {} DisplayItem::SetFilterOps => {} DisplayItem::SetFilterData => {} + DisplayItem::SetFilterPrimitives => {} DisplayItem::SetPoints => {} DisplayItem::PopAllShadows => {} DisplayItem::ReuseItems(..) => {}