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:
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(..) => {}