Bug 1555483 - Part 3: Add SVG render task and shader r=gw
☠☠ backed out by 27679131d486 ☠ ☠
authorConnor Brewster <cbrewster@mozilla.com>
Wed, 10 Jul 2019 02:17:21 +0000
changeset 482263 418839320ab5b103817da82d7582016c3cf8ccbe
parent 482262 134a51a0e03463d045ab71b79d251a91c70a8cd9
child 482264 917f800e4e43a0dc4799dd5893d4fa4b96454857
push id89660
push usercsabou@mozilla.com
push dateWed, 10 Jul 2019 19:47:03 +0000
treeherderautoland@8149efc1f813 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1555483
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1555483 - Part 3: Add SVG render task and shader r=gw The SVG task supports the pre-existing SVG filters along with Blend. Differential Revision: https://phabricator.services.mozilla.com/D34091
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender/res/cs_svg_filter.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/filterdata.rs
gfx/wr/webrender/src/gpu_types.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/prim_store/picture.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/shade.rs
gfx/wr/webrender/src/tiling.rs
gfx/wr/webrender/tests/angle_shader_validation.rs
gfx/wr/webrender_api/src/display_item.rs
gfx/wr/wrench/src/yaml_frame_writer.rs
gfx/wr/wrench/src/yaml_helper.rs
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2184,16 +2184,17 @@ pub extern "C" fn wr_dp_push_stacking_co
          .push_stacking_context(bounds.origin,
                                 wr_spatial_id,
                                 params.is_backface_visible,
                                 wr_clip_id,
                                 params.transform_style,
                                 params.mix_blend_mode,
                                 &filters,
                                 &r_filter_datas,
+                                &[],
                                 glyph_raster_space,
                                 params.cache_tiles);
 
     result
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState,
new file mode 100644
--- /dev/null
+++ b/gfx/wr/webrender/res/cs_svg_filter.glsl
@@ -0,0 +1,534 @@
+/* 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/. */
+
+#include shared,prim_shared
+
+varying vec3 vInput1Uv;
+varying vec3 vInput2Uv;
+flat varying vec4 vInput1UvRect;
+flat varying vec4 vInput2UvRect;
+flat varying int vFilterInputCount;
+flat varying int vFilterKind;
+flat varying ivec4 vData;
+flat varying vec4 vFilterData0;
+flat varying vec4 vFilterData1;
+flat varying float vFloat0;
+flat varying mat3 vColorMat;
+flat varying int vFuncs[4];
+
+#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
+
+#ifdef WR_VERTEX_SHADER
+
+in int aFilterRenderTaskAddress;
+in int aFilterInput1TaskAddress;
+in int aFilterInput2TaskAddress;
+in int aFilterKind;
+in int aFilterInputCount;
+in int aFilterGenericInt;
+in ivec2 aFilterExtraDataAddress;
+
+struct FilterTask {
+    RenderTaskCommonData common_data;
+    vec3 user_data;
+};
+
+FilterTask fetch_filter_task(int address) {
+    RenderTaskData task_data = fetch_render_task_data(address);
+
+    FilterTask task = FilterTask(
+        task_data.common_data,
+        task_data.user_data.xyz
+    );
+
+    return task;
+}
+
+vec4 compute_uv_rect(RenderTaskCommonData task, vec2 texture_size) {
+    RectWithSize task_rect = task.task_rect;
+
+    vec4 uvRect = vec4(task_rect.p0 + vec2(0.5),
+                       task_rect.p0 + task_rect.size - vec2(0.5));
+    uvRect /= texture_size.xyxy;
+    return uvRect;
+}
+
+vec3 compute_uv(RenderTaskCommonData task, vec2 texture_size) {
+    RectWithSize task_rect = task.task_rect;
+    vec3 uv = vec3(0.0, 0.0, task.texture_layer_index);
+
+    vec2 uv0 = task_rect.p0 / texture_size;
+    vec2 uv1 = floor(task_rect.p0 + task_rect.size) / texture_size;
+    uv.xy = mix(uv0, uv1, aPosition.xy);
+
+    return uv;
+}
+
+void main(void) {
+    FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress);
+    RectWithSize target_rect = filter_task.common_data.task_rect;
+
+    vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy;
+
+    RenderTaskCommonData input_1_task;
+    if (aFilterInputCount > 0) {
+        vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+        input_1_task = fetch_render_task_common_data(aFilterInput1TaskAddress);
+        vInput1UvRect = compute_uv_rect(input_1_task, texture_size);
+        vInput1Uv = compute_uv(input_1_task, texture_size);
+    }
+
+    RenderTaskCommonData input_2_task;
+    if (aFilterInputCount > 1) {
+        vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
+        input_2_task = fetch_render_task_common_data(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[0] = (aFilterGenericInt >> 12) & 0xf; // R
+    vFuncs[1] = (aFilterGenericInt >> 8)  & 0xf; // G
+    vFuncs[2] = (aFilterGenericInt >> 4)  & 0xf; // B
+    vFuncs[3] = (aFilterGenericInt)       & 0xf; // A
+
+    switch (aFilterKind) {
+        case FILTER_BLEND:
+            vData = ivec4(aFilterGenericInt, 0, 0, 0);
+            break;
+        case FILTER_FLOOD:
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+            break;
+        case FILTER_OPACITY:
+            vFloat0 = filter_task.user_data.x;
+            break;
+        case FILTER_COLOR_MATRIX:
+            vec4 mat_data[3] = fetch_from_gpu_cache_3_direct(aFilterExtraDataAddress);
+            vColorMat = mat3(mat_data[0].xyz, mat_data[1].xyz, mat_data[2].xyz);
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress + ivec2(4, 0));
+            break;
+        case FILTER_DROP_SHADOW:
+            vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress);
+            break;
+        case FILTER_OFFSET:
+            vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
+            vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0));
+
+            RectWithSize task_rect = input_1_task.task_rect;
+            vec4 clipRect = vec4(task_rect.p0, task_rect.p0 + task_rect.size);
+            clipRect /= texture_size.xyxy;
+            vFilterData1 = clipRect;
+            break;
+        case FILTER_COMPONENT_TRANSFER:
+            vData = ivec4(aFilterExtraDataAddress, 0, 0);
+            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;
+
+    for (int i = 0; i < 4; i++) {
+        switch (vFuncs[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));
+                texel = fetch_from_gpu_cache_1_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_cache_1_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_cache_1_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;
+}
+
+vec4 sampleInUvRect(sampler2DArray sampler, vec3 uv, vec4 uvRect) {
+    vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw);
+    return texture(sampler, vec3(clamped, uv.z), 0.0);
+}
+
+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;
+            break;
+        case FILTER_COLOR_MATRIX:
+            result.rgb = vColorMat * Ca.rgb + vFilterData0.rgb;
+            result.a = Ca.a;
+            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.xy + vFilterData0.xy;
+            result = sampleInUvRect(sColor0, vec3(offsetUv, vInput1Uv.z), vInput1UvRect);
+            result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw);
+            needsPremul = false;
+            break;
+        case FILTER_COMPONENT_TRANSFER:
+            vec4 colora = Ca.a != 0.0 ? Ca / Ca.a : Ca;
+            result = ComponentTransfer(Ca);
+            break;
+        case FILTER_IDENTITY:
+            result = Ca;
+            break;
+        default:
+            break;
+    }
+
+    if (needsPremul) {
+        result.rgb *= result.a;
+    }
+
+    oFragColor = result;
+}
+#endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -95,16 +95,37 @@ impl BatchTextures {
         }
     }
 
     pub fn color(texture: TextureSource) -> Self {
         BatchTextures {
             colors: [texture, texture, TextureSource::Invalid],
         }
     }
+
+    pub fn is_compatible_with(&self, other: &BatchTextures) -> bool {
+        self.colors.iter().zip(other.colors.iter()).all(|(t1, t2)| textures_compatible(*t1, *t2))
+    }
+
+    pub fn combine_textures(&self, other: BatchTextures) -> Option<BatchTextures> {
+        if !self.is_compatible_with(&other) {
+            return None;
+        }
+
+        let mut new_textures = BatchTextures::no_texture();
+        for (i, (color, other_color)) in self.colors.iter().zip(other.colors.iter()).enumerate() {
+            // If these textures are compatible, for each source either both sources are invalid or only one is not invalid.
+            new_textures.colors[i] = if *color == TextureSource::Invalid {
+                *other_color
+            } else {
+                *color
+            };
+        }
+        Some(new_textures)
+    }
 }
 
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchKey {
     pub kind: BatchKind,
     pub blend_mode: BlendMode,
@@ -116,20 +137,17 @@ impl BatchKey {
         BatchKey {
             kind,
             blend_mode,
             textures,
         }
     }
 
     pub fn is_compatible_with(&self, other: &BatchKey) -> bool {
-        self.kind == other.kind && self.blend_mode == other.blend_mode &&
-            textures_compatible(self.textures.colors[0], other.textures.colors[0]) &&
-            textures_compatible(self.textures.colors[1], other.textures.colors[1]) &&
-            textures_compatible(self.textures.colors[2], other.textures.colors[2])
+        self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures)
     }
 }
 
 #[inline]
 fn textures_compatible(t1: TextureSource, t2: TextureSource) -> bool {
     t1 == TextureSource::Invalid || t2 == TextureSource::Invalid || t1 == t2
 }
 
@@ -1657,24 +1675,26 @@ impl BatchBuilder {
                                     ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                     RasterizationSpace::Screen as i32,
                                     get_shader_opacity(1.0),
                                     0,
                                 ]);
 
                                 self.add_brush_instance_to_batches(
                                     key,
+                                    batch_features,
                                     bounding_rect,
                                     z_id,
                                     INVALID_SEGMENT_INDEX,
                                     EdgeAaSegmentMask::empty(),
                                     clip_task_address,
                                     brush_flags,
                                     prim_header_index,
                                     uv_rect_address.as_int(),
+                                    prim_vis_mask,
                                 );
                             }
                         }
                     }
                     None => {
                         // If this picture is being drawn into an existing target (i.e. with
                         // no composition operation), recurse and add to the current batch list.
                         self.add_pic_to_batch(
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -1977,24 +1977,42 @@ impl<'a> DisplayListFlattener<'a> {
             }
 
             // Run the optimize pass on this picture, to see if we can
             // collapse opacity and avoid drawing to an off-screen surface.
             self.prim_store.optimize_picture_if_possible(current_pic_index);
         }
 
         if !stacking_context.composite_ops.filter_primitives.is_empty() {
-            let composite_mode = PictureCompositeMode::SvgFilter(stacking_context.composite_ops.filter_primitives.clone());
+            let filter_datas = stacking_context.composite_ops.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();
+
+            let composite_mode = PictureCompositeMode::SvgFilter(
+                stacking_context.composite_ops.filter_primitives,
+                filter_datas,
+            );
 
             let filter_pic_index = PictureIndex(self.prim_store.pictures
                 .alloc()
                 .init(PicturePrimitive::new_image(
                     Some(composite_mode.clone()),
                     Picture3DContext::Out,
-                    stacking_context.pipeline_id,
                     None,
                     true,
                     stacking_context.is_backface_visible,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.interners,
                     ),
@@ -3104,16 +3122,21 @@ impl FlattenedStackingContext {
             return false;
         }
 
         // If there are filters / mix-blend-mode
         if !self.composite_ops.filters.is_empty() {
             return false;
         }
 
+        // If there are svg filters
+        if !self.composite_ops.filter_primitives.is_empty() {
+            return false;
+        }
+
         // We can skip mix-blend modes if they are the first primitive in a stacking context,
         // see pop_stacking_context for a full explanation.
         if !self.composite_ops.mix_blend_mode.is_none() &&
             !parent.primitives.is_empty() {
             return false;
         }
 
         // If backface visibility is explicitly set.
--- a/gfx/wr/webrender/src/filterdata.rs
+++ b/gfx/wr/webrender/src/filterdata.rs
@@ -117,34 +117,44 @@ impl From<SFilterDataKey> for SFilterDat
     fn from(item: SFilterDataKey) -> Self {
         SFilterDataTemplate {
             data: item.data,
             gpu_cache_handle: GpuCacheHandle::new(),
         }
     }
 }
 
+impl SFilterData {
+    pub fn is_identity(&self) -> bool {
+        self.r_func == SFilterDataComponent::Identity
+            && self.g_func == SFilterDataComponent::Identity
+            && self.b_func == SFilterDataComponent::Identity
+            && self.a_func == SFilterDataComponent::Identity
+    }
+
+    pub fn update(&self, mut request: GpuDataRequest) {
+        push_component_transfer_data(&self.r_func, &mut request);
+        push_component_transfer_data(&self.g_func, &mut request);
+        push_component_transfer_data(&self.b_func, &mut request);
+        push_component_transfer_data(&self.a_func, &mut request);
+        assert!(!self.is_identity());
+    }
+}
+
 impl SFilterDataTemplate {
     /// Update the GPU cache for a given filter data template. This may be called multiple
     /// times per frame, by each primitive reference that refers to this interned
     /// template. The initial request call to the GPU cache ensures that work is only
     /// done if the cache entry is invalid (due to first use or eviction).
     pub fn update(
         &mut self,
         frame_state: &mut FrameBuildingState,
     ) {
-        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
-            push_component_transfer_data(&self.data.r_func, &mut request);
-            push_component_transfer_data(&self.data.g_func, &mut request);
-            push_component_transfer_data(&self.data.b_func, &mut request);
-            push_component_transfer_data(&self.data.a_func, &mut request);
-            assert!(self.data.r_func != SFilterDataComponent::Identity
-                 || self.data.g_func != SFilterDataComponent::Identity
-                 || self.data.b_func != SFilterDataComponent::Identity
-                 || self.data.a_func != SFilterDataComponent::Identity);
+        if let Some(request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) {
+            self.data.update(request);
         }
     }
 }
 
 impl intern::Internable for FilterDataIntern {
     type Key = SFilterDataKey;
     type StoreData = SFilterDataTemplate;
     type InternData = ();
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -104,16 +104,30 @@ pub struct BlurInstance {
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ScalingInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
 }
 
+#[derive(Debug)]
+#[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 extra_data_address: GpuCacheAddress,
+}
+
 #[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BorderSegment {
     TopLeft,
     TopRight,
     BottomRight,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,24 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
 use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
 use api::{DebugFlags, RasterSpace, ImageKey, ColorF};
 use api::units::*;
 use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore, ClipDataStore, ClipChainInstance};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
     ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace, CoordinateSystemId
 };
 use crate::debug_colors;
 use euclid::{vec3, TypedPoint2D, TypedScale, TypedSize2D, Vector2D, TypedRect};
 use euclid::approxeq::ApproxEq;
+use crate::filterdata::SFilterData;
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
 use plane_split::{Clipper, Polygon, Splitter};
 use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, PrimitiveTemplateKind};
@@ -1569,17 +1570,75 @@ pub enum PictureCompositeMode {
     ComponentTransferFilter(FilterDataHandle),
     /// Draw to intermediate surface, copy straight across. This
     /// is used for CSS isolation, and plane splitting.
     Blit(BlitReason),
     /// Used to cache a picture as a series of tiles.
     TileCache {
     },
     /// Apply an SVG filter
-    SvgFilter(Vec<FilterPrimitive>),
+    SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
+}
+
+impl PictureCompositeMode {
+    pub fn inflate_picture_rect(&self, picture_rect: PictureRect, inflation_factor: f32) -> PictureRect {
+        let mut result_rect = picture_rect;
+        match self {
+            PictureCompositeMode::Filter(filter) => match filter {
+                Filter::Blur(_) => {
+                    result_rect = picture_rect.inflate(inflation_factor, inflation_factor);
+                },
+                Filter::DropShadows(shadows) => {
+                    let mut max_inflation: f32 = 0.0;
+                    for shadow in shadows {
+                        let inflation_factor = shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+                        max_inflation = max_inflation.max(inflation_factor);
+                    }
+                    result_rect = picture_rect.inflate(max_inflation, max_inflation);
+                },
+                _ => {}
+            }
+            PictureCompositeMode::SvgFilter(primitives, _) => {
+                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(picture_rect);
+                            let inflation_factor = primitive.radius.round() * BLUR_SAMPLE_SCALE;
+                            input.inflate(inflation_factor, inflation_factor)
+                        }
+                        FilterPrimitiveKind::DropShadow(ref primitive) => {
+                            let inflation_factor = primitive.shadow.blur_radius.round() * BLUR_SAMPLE_SCALE;
+                            let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
+                            let shadow_rect = input.inflate(inflation_factor, inflation_factor);
+                            input.union(&shadow_rect.translate(&(primitive.shadow.offset * TypedScale::new(1.0))))
+                        }
+                        FilterPrimitiveKind::Blend(ref primitive) => {
+                            primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
+                                .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
+                        }
+                        FilterPrimitiveKind::Identity(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::Opacity(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::ColorMatrix(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+                        FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
+                            primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
+
+                        FilterPrimitiveKind::Flood(..) => picture_rect,
+                    };
+                    output_rects.push(output_rect);
+                    result_rect = result_rect.union(&output_rect);
+                }
+            }
+            _ => {},
+        }
+        result_rect
+    }
 }
 
 /// Enum value describing the place of a picture in a 3D context.
 #[derive(Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum Picture3DContext<C> {
     /// The picture is not a part of 3D context sub-hierarchy.
     Out,
@@ -2440,39 +2499,49 @@ impl PicturePrimitive {
                             device_pixel_scale,
                             PrimitiveVisibilityMask::all(),
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
 
                         Some((render_task_id, render_task_id))
                     }
-                    PictureCompositeMode::SvgFilter(..) => {
+                    PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &clipped,
                             device_pixel_scale,
                             true,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
                             pic_index,
                             clipped.origin,
                             uv_rect_kind,
-                            raster_spatial_node_index,
                             surface_spatial_node_index,
                             device_pixel_scale,
+                            PrimitiveVisibilityMask::all(),
+                        );
+
+                        let picture_task_id = frame_state.render_tasks.add(picture_task);
+
+                        let filter_task_id = RenderTask::new_svg_filter(
+                            primitives,
+                            filter_datas,
+                            &mut frame_state.render_tasks,
+                            clipped.size,
+                            uv_rect_kind,
+                            picture_task_id,
+                            device_pixel_scale,
                         );
 
-                        let render_task_id = frame_state.render_tasks.add(picture_task);
-
-                        (render_task_id, render_task_id)
+                        Some((filter_task_id, picture_task_id))
                     }
                 };
 
                 if let Some((root, port)) = dep_info {
                     frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks {
                         root,
                         port,
                     });
@@ -2517,17 +2586,18 @@ impl PicturePrimitive {
             Some(RasterConfig { ref composite_mode, .. }) => {
                 let subpixel_mode = match composite_mode {
                     PictureCompositeMode::TileCache { .. } => {
                         self.tile_cache.as_ref().unwrap().subpixel_mode
                     }
                     PictureCompositeMode::Blit(..) |
                     PictureCompositeMode::ComponentTransferFilter(..) |
                     PictureCompositeMode::Filter(..) |
-                    PictureCompositeMode::MixBlend(..) => {
+                    PictureCompositeMode::MixBlend(..) |
+                    PictureCompositeMode::SvgFilter(..) => {
                         // TODO(gw): We can take advantage of the same logic that
                         //           exists in the opaque rect detection for tile
                         //           caches, to allow subpixel text on other surfaces
                         //           that can be detected as opaque.
                         SubpixelMode::Deny
                     }
                 };
 
@@ -2763,24 +2833,40 @@ impl PicturePrimitive {
                     if self.options.inflate_if_required {
                         // The amount of extra space needed for primitives inside
                         // this picture to ensure the visibility check is correct.
                         BLUR_SAMPLE_SCALE * blur_radius
                     } else {
                         0.0
                     }
                 }
+                PictureCompositeMode::SvgFilter(ref primitives, _) if self.options.inflate_if_required => {
+                    let mut max = 0.0;
+                    for primitive in primitives {
+                        if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
+                            max = f32::max(max, blur.radius * BLUR_SAMPLE_SCALE);
+                        }
+                    }
+                    max
+                }
                 _ => {
                     0.0
                 }
             };
 
-            // Check if there is perspective, and thus whether a new
+            // Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root.
+            let has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode {
+                true
+            } else {
+                false
+            };
+
+            // Check if there is perspective or if an SVG filter is applied, and thus whether a new
             // rasterization root should be established.
-            let establishes_raster_root = frame_context.clip_scroll_tree
+            let establishes_raster_root = has_svg_filter || frame_context.clip_scroll_tree
                 .get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
                 .is_perspective();
 
             let surface = SurfaceInfo::new(
                 surface_spatial_node_index,
                 if establishes_raster_root {
                     surface_spatial_node_index
                 } else {
@@ -2857,36 +2943,22 @@ impl PicturePrimitive {
                 surface.rect = surface.rect.union(&cluster_rect);
             }
         }
 
         // If this picture establishes a surface, then map the surface bounding
         // rect into the parent surface coordinate space, and propagate that up
         // to the parent.
         if let Some(ref mut raster_config) = self.raster_config {
-            let mut surface_rect = {
-                let surface = state.current_surface_mut();
-                // Inflate the local bounding rect if required by the filter effect.
-                // This inflaction factor is to be applied to the surface itsefl.
-                // TODO: in prepare_for_render we round before multiplying with the
-                // blur sample scale. Should we do this here as well?
-                let inflation_size = match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                    PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
-                        let mut max = 0.0;
-                        for shadow in shadows {
-                            max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
-                        }
-                        max.ceil()
-                    }
-                    _ => 0.0,
-                };
-                surface.rect = surface.rect.inflate(inflation_size, inflation_size);
-                surface.rect * TypedScale::new(1.0)
-            };
+            let surface = state.current_surface_mut();
+            // Inflate the local bounding rect if required by the filter effect.
+            // This inflaction factor is to be applied to the surface itself.
+            surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.inflation_factor);
+
+            let mut surface_rect = surface.rect * TypedScale::new(1.0);
 
             // Pop this surface from the stack
             let surface_index = state.pop_surface();
             debug_assert_eq!(surface_index, raster_config.surface_index);
 
             // Snapping may change the local rect slightly, and as such should just be
             // considered an estimated size for determining if we need raster roots and
             // preparing the tile cache.
@@ -3013,17 +3085,17 @@ impl PicturePrimitive {
                 }
             }
             PictureCompositeMode::ComponentTransferFilter(handle) => {
                 let filter_data = &mut data_stores.filter_data[handle];
                 filter_data.update(frame_state);
             }
             PictureCompositeMode::MixBlend(..) |
             PictureCompositeMode::Blit(_) |
-            PictureCompositeMode::SvgFilter(_) => {}
+            PictureCompositeMode::SvgFilter(..) => {}
         }
 
         true
     }
 }
 
 // Calculate a single homogeneous screen-space UV for a picture.
 fn calculate_screen_uv(
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -5,17 +5,16 @@
 use api::{BorderRadius, ClipMode, ColorF};
 use api::{ImageRendering, RepeatMode};
 use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop};
 use api::{BoxShadowClipMode, LineStyle, LineOrientation, BorderStyle};
 use api::{PrimitiveKeyKind};
 use api::units::*;
 use crate::border::{get_max_scale_for_border, build_border_instances};
 use crate::border::BorderSegmentCacheKey;
-use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace};
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use crate::debug_colors;
 use crate::debug_render::DebugItem;
 use crate::display_list_flattener::{CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D, TypedPoint2D};
 use euclid::approxeq::ApproxEq;
@@ -2248,28 +2247,17 @@ impl PrimitiveStore {
         // and stretch size. Drop shadow filters also depend on the local rect
         // size for the extra GPU cache data handle.
         // TODO(gw): In future, if we support specifying a flag which gets the
         //           stretch size from the segment rect in the shaders, we can
         //           remove this invalidation here completely.
         if let Some(ref raster_config) = pic.raster_config {
             // Inflate the local bounding rect if required by the filter effect.
             // This inflaction factor is to be applied to the surface itself.
-            let inflation_size = match raster_config.composite_mode {
-                PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
-                    let mut max = 0.0;
-                    for shadow in shadows {
-                        max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
-                    }
-                    max.ceil()
-                }
-                _ => 0.0,
-            };
-            surface_rect = surface_rect.inflate(inflation_size, inflation_size);
+            surface_rect = raster_config.composite_mode.inflate_picture_rect(surface_rect, surface.inflation_factor);
 
             // Layout space for the picture is picture space from the
             // perspective of its child primitives.
             let pic_local_rect = surface_rect * TypedScale::new(1.0);
             if pic.snapped_local_rect != pic_local_rect {
                 match raster_config.composite_mode {
                     PictureCompositeMode::Filter(Filter::DropShadows(..)) => {
                         for handle in &pic.extra_gpu_data_handles {
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -1,28 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{
-    ColorU, MixBlendMode, FilterPrimitive,
+    ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace,
     PropertyBinding, PropertyBindingId,
 };
 use api::units::{Au, LayoutSize, LayoutVector2D};
+use crate::display_list_flattener::IsVisible;
+use crate::filterdata::SFilterData;
 use crate::intern::ItemUid;
-use crate::display_list_flattener::IsVisible;
 use crate::intern::{Internable, InternDebug, Handle as InternHandle};
 use crate::internal_types::{LayoutPrimitiveInfo, Filter};
 use crate::picture::PictureCompositeMode;
 use crate::prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
     PrimitiveInstanceKind, PrimitiveSceneData, PrimitiveStore, VectorKey,
     InternablePrimitive,
 };
 
+#[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, FilterPrimitiveInput),
+    Opacity(ColorSpace, Au, FilterPrimitiveInput),
+    ColorMatrix(ColorSpace, [Au; 20], FilterPrimitiveInput),
+    DropShadow(ColorSpace, (VectorKey, Au, ColorU), FilterPrimitiveInput),
+    ComponentTransfer(ColorSpace, FilterPrimitiveInput, Vec<SFilterData>),
+}
+
 /// Represents a hashable description of how a picture primitive
 /// will be composited into its parent.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
 pub enum PictureCompositeKey {
     // No visual compositing effect
     Identity,
@@ -39,17 +54,17 @@ pub enum PictureCompositeKey {
     Saturate(Au),
     Sepia(Au),
     DropShadows(Vec<(VectorKey, Au, ColorU)>),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer(ItemUid),
     Flood(ColorU),
-    SvgFilter(Vec<FilterPrimitive>),
+    SvgFilter(Vec<FilterPrimitiveKey>),
 
     // MixBlendMode
     Multiply,
     Screen,
     Overlay,
     Darken,
     Lighten,
     ColorDodge,
@@ -126,18 +141,47 @@ impl From<Option<PictureCompositeMode>> 
                     }
                     Filter::ComponentTransfer => unreachable!(),
                     Filter::Flood(color) => PictureCompositeKey::Flood(color.into()),
                 }
             }
             Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
                 PictureCompositeKey::ComponentTransfer(handle.uid())
             }
-            Some(PictureCompositeMode::SvgFilter(filter_primitives)) => {
-                PictureCompositeKey::SvgFilter(filter_primitives)
+            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.radius), 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()),
+                    }
+                }).collect())
             }
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -1,24 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ImageDescriptor, ImageFormat};
-use api::{LineStyle, LineOrientation, ClipMode, DirtyRect};
+use api::{ImageDescriptor, ImageFormat, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
+use api::{LineStyle, LineOrientation, ClipMode, DirtyRect, MixBlendMode, ColorF, ColorSpace};
 use api::units::*;
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
 use crate::border::BorderSegmentCacheKey;
 use crate::box_shadow::{BoxShadowCacheKey};
 use crate::clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange, ClipNodeFlags};
 use crate::clip_scroll_tree::SpatialNodeIndex;
 use crate::device::TextureFilter;
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
+use crate::filterdata::SFilterData;
 use crate::frame_builder::FrameBuilderConfig;
 use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use crate::glyph_rasterizer::GpuGlyphCacheKey;
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind, SnapOffsets};
 use crate::internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex, TextureSource};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
@@ -349,25 +350,25 @@ impl RenderTaskGraph {
 
                     continue;
                 }
 
                 // Our dependency is an even number of passes behind, need
                 // to insert a blit to ensure we don't read and write from
                 // the same target.
 
-                let task_id = RenderTaskId {
+                let child_task_id = RenderTaskId {
                     index: child_task_index as u32,
                     #[cfg(debug_assertions)]
                     frame_id: self.frame_id,
                 };
 
                 let mut blit = RenderTask::new_blit(
-                    self.tasks[task_index].location.size(),
-                    BlitSource::RenderTask { task_id },
+                    self.tasks[child_task_index].location.size(),
+                    BlitSource::RenderTask { task_id: child_task_id },
                 );
 
                 // Mark for saving if the blit is more than pass appart from
                 // our task.
                 if child_pass_index < pass_index - 2 {
                     blit.mark_for_saving();
                 }
 
@@ -377,17 +378,17 @@ impl RenderTaskGraph {
                     frame_id: self.frame_id,
                 };
 
                 self.tasks.push(blit);
 
                 passes[child_pass_index as usize + 1].tasks.push(blit_id);
 
                 self.tasks[task_index].children[nth_child] = blit_id;
-                task_redirects[task_index] = Some(blit_id);
+                task_redirects[child_task_index] = Some(blit_id);
             }
         }
     }
 
     pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress {
         #[cfg(all(debug_assertions, not(feature = "replay")))]
         debug_assert_eq!(self.frame_id, id.frame_id);
         RenderTaskAddress(id.index as u16)
@@ -613,16 +614,43 @@ pub struct LineDecorationTask {
     pub style: LineStyle,
     pub orientation: LineOrientation,
     pub local_size: LayoutSize,
 }
 
 #[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),
+    // 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_cache_handle: Option<GpuCacheHandle>,
+    pub uv_rect_handle: GpuCacheHandle,
+    uv_rect_kind: UvRectKind,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskData {
     pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskKind {
     Picture(PictureTask),
@@ -633,16 +661,17 @@ pub enum RenderTaskKind {
     #[allow(dead_code)]
     Glyph(GlyphTask),
     Readback(DeviceIntRect),
     Scaling(ScalingTask),
     Blit(BlitTask),
     Border(BorderTask),
     LineDecoration(LineDecorationTask),
     Gradient(GradientTask),
+    SvgFilter(SvgFilterTask),
     #[cfg(test)]
     Test(RenderTargetKind),
 }
 
 impl RenderTaskKind {
     pub fn as_str(&self) -> &'static str {
         match *self {
             RenderTaskKind::Picture(..) => "Picture",
@@ -652,16 +681,17 @@ impl RenderTaskKind {
             RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
             RenderTaskKind::Glyph(..) => "Glyph",
             RenderTaskKind::Readback(..) => "Readback",
             RenderTaskKind::Scaling(..) => "Scaling",
             RenderTaskKind::Blit(..) => "Blit",
             RenderTaskKind::Border(..) => "Border",
             RenderTaskKind::LineDecoration(..) => "LineDecoration",
             RenderTaskKind::Gradient(..) => "Gradient",
+            RenderTaskKind::SvgFilter(..) => "SvgFilter",
             #[cfg(test)]
             RenderTaskKind::Test(..) => "Test",
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -1167,16 +1197,295 @@ impl RenderTask {
             RenderTaskKind::Scaling(ScalingTask {
                 target_kind,
                 uv_rect_kind,
             }),
             ClearMode::DontCare,
         )
     }
 
+    pub fn new_svg_filter(
+        filter_primitives: &[FilterPrimitive],
+        filter_datas: &[SFilterData],
+        render_tasks: &mut RenderTaskGraph,
+        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],
+            render_tasks: &mut RenderTaskGraph,
+            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) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::SrgbToLinear,
+                    );
+                    task_id = render_tasks.add(task);
+                },
+                (ColorSpace::LinearRgb, ColorSpace::Srgb) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::LinearToSrgb,
+                    );
+                    task_id = render_tasks.add(task);
+                },
+                _ => {},
+            }
+
+            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,
+                        render_tasks,
+                        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,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+                    let input_2_task_id = get_task_input(
+                        &blend.input2,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_1_task_id, input_2_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Blend(blend.mode),
+                    );
+                    render_tasks.add(task)
+                },
+                FilterPrimitiveKind::Flood(ref flood) => {
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Flood(flood.color),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::Blur(ref blur) => {
+                    let blur_std_deviation = blur.radius * device_pixel_scale.0;
+                    let input_task_id = get_task_input(
+                        &blur.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    // TODO: This is a hack to ensure that a blur task's input is always in the blur's previous pass.
+                    let svg_task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Identity,
+                    );
+
+                    RenderTask::new_blur(
+                        DeviceSize::new(blur_std_deviation, blur_std_deviation),
+                        render_tasks.add(svg_task),
+                        render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                        None,
+                    )
+                }
+                FilterPrimitiveKind::Opacity(ref opacity) => {
+                    let input_task_id = get_task_input(
+                        &opacity.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Opacity(opacity.opacity),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::ColorMatrix(ref color_matrix) => {
+                    let input_task_id = get_task_input(
+                        &color_matrix.input,
+                        filter_primitives,
+                        render_tasks,
+                        cur_index,
+                        &outputs,
+                        original_task_id,
+                        primitive.color_space
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::DropShadow(ref drop_shadow) => {
+                    let input_task_id = get_task_input(
+                        &drop_shadow.input,
+                        filter_primitives,
+                        render_tasks,
+                        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 = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::Offset(offset),
+                    );
+                    let offset_task_id = render_tasks.add(offset_task);
+
+                    let blur_task_id = RenderTask::new_blur(
+                        DeviceSize::new(blur_std_deviation, blur_std_deviation),
+                        offset_task_id,
+                        render_tasks,
+                        RenderTargetKind::Color,
+                        ClearMode::Transparent,
+                        None,
+                    );
+
+                    let task = RenderTask::new_svg_filter_primitive(
+                        vec![input_task_id, blur_task_id],
+                        content_size,
+                        uv_rect_kind,
+                        SvgFilterInfo::DropShadow(drop_shadow.shadow.color),
+                    );
+                    render_tasks.add(task)
+                }
+                FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => {
+                    let input_task_id = get_task_input(
+                        &component_transfer.input,
+                        filter_primitives,
+                        render_tasks,
+                        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 {
+                        let task = RenderTask::new_svg_filter_primitive(
+                            vec![input_task_id],
+                            content_size,
+                            uv_rect_kind,
+                            SvgFilterInfo::ComponentTransfer(filter_data.clone()),
+                        );
+                        render_tasks.add(task)
+                    }
+                }
+            };
+            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 {
+            let task = RenderTask::new_svg_filter_primitive(
+                vec![render_task_id],
+                content_size,
+                uv_rect_kind,
+                SvgFilterInfo::LinearToSrgb,
+            );
+            render_task_id = render_tasks.add(task);
+        }
+
+        render_task_id
+    }
+
+    pub fn new_svg_filter_primitive(
+        tasks: Vec<RenderTaskId>,
+        target_size: DeviceIntSize,
+        uv_rect_kind: UvRectKind,
+        info: SvgFilterInfo,
+    ) -> Self {
+        RenderTask::with_dynamic_location(
+            target_size,
+            tasks,
+            RenderTaskKind::SvgFilter(SvgFilterTask {
+                extra_gpu_cache_handle: None,
+                uv_rect_handle: GpuCacheHandle::new(),
+                uv_rect_kind,
+                info,
+            }),
+            ClearMode::Transparent,
+        )
+    }
+
     #[cfg(feature = "pathfinder")]
     pub fn new_glyph(
         location: RenderTaskLocation,
         mesh: Mesh,
         origin: &DeviceIntPoint,
         subpixel_offset: &TypedPoint2D<f32, DevicePixel>,
         render_mode: FontRenderMode,
         embolden_amount: &TypedVector2D<f32, DevicePixel>,
@@ -1211,16 +1520,20 @@ impl RenderTask {
             RenderTaskKind::HorizontalBlur(ref task) => {
                 task.uv_rect_kind
             }
 
             RenderTaskKind::Scaling(ref task) => {
                 task.uv_rect_kind
             }
 
+            RenderTaskKind::SvgFilter(ref task) => {
+                task.uv_rect_kind
+            }
+
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Glyph(_) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Blit(..) => {
                 UvRectKind::Rect
             }
@@ -1282,16 +1595,25 @@ impl RenderTask {
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
 
+
+            RenderTaskKind::SvgFilter(ref task) => {
+                match task.info {
+                    SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0],
+                    SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0],
+                    _ => [0.0; 3]
+                }
+            }
+
             #[cfg(test)]
             RenderTaskKind::Test(..) => {
                 unreachable!();
             }
         };
 
         let (mut target_rect, target_index) = self.get_target_rect();
         // The primitives inside a fixed-location render task
@@ -1319,16 +1641,19 @@ impl RenderTask {
         match self.kind {
             RenderTaskKind::Picture(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 gpu_cache.get_address(&info.uv_rect_handle)
             }
+            RenderTaskKind::SvgFilter(ref info) => {
+                gpu_cache.get_address(&info.uv_rect_handle)
+            }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
@@ -1388,48 +1713,41 @@ impl RenderTask {
                     RenderTargetIndex(layer as usize),
                 )
             }
         }
     }
 
     pub fn target_kind(&self) -> RenderTargetKind {
         match self.kind {
-            RenderTaskKind::Readback(..) => RenderTargetKind::Color,
-
-            RenderTaskKind::LineDecoration(..) => RenderTargetKind::Color,
+            RenderTaskKind::LineDecoration(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::Glyph(..) |
+            RenderTaskKind::Border(..) |
+            RenderTaskKind::Gradient(..) |
+            RenderTaskKind::Picture(..) |
+            RenderTaskKind::Blit(..) |
+            RenderTaskKind::SvgFilter(..) => {
+                RenderTargetKind::Color
+            }
 
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
 
-            RenderTaskKind::Glyph(..) => {
-                RenderTargetKind::Color
-            }
-
             RenderTaskKind::Scaling(ref task_info) => {
                 task_info.target_kind
             }
 
-            RenderTaskKind::Border(..) |
-            RenderTaskKind::Gradient(..) |
-            RenderTaskKind::Picture(..) => {
-                RenderTargetKind::Color
-            }
-
-            RenderTaskKind::Blit(..) => {
-                RenderTargetKind::Color
-            }
-
             #[cfg(test)]
             RenderTaskKind::Test(kind) => kind,
         }
     }
 
     pub fn write_gpu_blocks(
         &mut self,
         gpu_cache: &mut GpuCache,
@@ -1439,16 +1757,19 @@ impl RenderTask {
         let (cache_handle, uv_rect_kind) = match self.kind {
             RenderTaskKind::HorizontalBlur(ref mut info) |
             RenderTaskKind::VerticalBlur(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
             RenderTaskKind::Picture(ref mut info) => {
                 (&mut info.uv_rect_handle, info.uv_rect_kind)
             }
+            RenderTaskKind::SvgFilter(ref mut info) => {
+                (&mut info.uv_rect_handle, info.uv_rect_kind)
+            }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) |
@@ -1468,16 +1789,43 @@ impl RenderTask {
                 p0,
                 p1,
                 texture_layer: target_index.0 as f32,
                 user_data: [0.0; 3],
                 uv_rect_kind,
             };
             image_source.write_gpu_blocks(&mut request);
         }
+
+        if let RenderTaskKind::SvgFilter(ref mut filter_task) = self.kind {
+            match filter_task.info {
+                SvgFilterInfo::ColorMatrix(ref matrix) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(mut request) = gpu_cache.request(handle) {
+                        for i in 0..5 {
+                            request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+                        }
+                    }
+                }
+                SvgFilterInfo::DropShadow(color) |
+                SvgFilterInfo::Flood(color) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(mut request) = gpu_cache.request(handle) {
+                        request.push(color.to_array());
+                    }
+                }
+                SvgFilterInfo::ComponentTransfer(ref data) => {
+                    let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(|| GpuCacheHandle::new());
+                    if let Some(request) = gpu_cache.request(handle) {
+                        data.update(request);
+                    }
+                }
+                _ => {},
+            }
+        }
     }
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskGraph) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.pic_index));
             }
@@ -1515,23 +1863,28 @@ impl RenderTask {
                 pt.add_item(format!("source: {:?}", task.source));
             }
             RenderTaskKind::Glyph(..) => {
                 pt.new_level("Glyph".to_owned());
             }
             RenderTaskKind::Gradient(..) => {
                 pt.new_level("Gradient".to_owned());
             }
+            RenderTaskKind::SvgFilter(ref task) => {
+                pt.new_level("SvgFilter".to_owned());
+                pt.add_item(format!("primitive: {:?}", task.info));
+            }
             #[cfg(test)]
             RenderTaskKind::Test(..) => {
                 pt.new_level("Test".to_owned());
             }
         }
 
         pt.add_item(format!("clear to: {:?}", self.clear_mode));
+        pt.add_item(format!("dimensions: {:?}", self.location.size()));
 
         for &child_id in &self.children {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
         }
 
         pt.end_level();
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -58,17 +58,17 @@ use crate::device::query::GpuTimer;
 use euclid::{rect, Transform3D, TypedScale};
 use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
 #[cfg(feature = "pathfinder")]
 use crate::gpu_glyph_renderer::GpuGlyphRenderer;
-use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, TransformData, ResolveInstanceData};
+use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData, ResolveInstanceData};
 use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
 use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
 use crate::internal_types::{RenderTargetInfo, SavedTargetIndex};
 use malloc_size_of::MallocSizeOfOps;
 use crate::picture::{RecordedDirtyRegion, TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT};
 use crate::prim_store::DeferredResolve;
 use crate::profiler::{BackendProfileCounters, FrameProfileCounters, TimeProfileCounter,
@@ -215,16 +215,20 @@ const GPU_SAMPLER_TAG_ALPHA: GpuProfileT
 const GPU_SAMPLER_TAG_OPAQUE: GpuProfileTag = GpuProfileTag {
     label: "Opaque Pass",
     color: debug_colors::BLACK,
 };
 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,
+};
 
 /// The clear color used for the texture cache when the debug display is enabled.
 /// We use a shade of blue so that we can still identify completely blue items in
 /// the texture cache.
 const TEXTURE_CACHE_DBG_CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.8, 1.0];
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
@@ -657,16 +661,63 @@ pub(crate) mod desc {
             VertexAttribute {
                 name: "aRect",
                 count: 4,
                 kind: VertexAttributeKind::F32,
             },
         ],
     };
 
+    pub const SVG_FILTER: VertexDescriptor = VertexDescriptor {
+        vertex_attributes: &[
+            VertexAttribute {
+                name: "aPosition",
+                count: 2,
+                kind: VertexAttributeKind::F32,
+            },
+        ],
+        instance_attributes: &[
+            VertexAttribute {
+                name: "aFilterRenderTaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterInput1TaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            VertexAttribute {
+                name: "aFilterInput2TaskAddress",
+                count: 1,
+                kind: VertexAttributeKind::U16,
+            },
+            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: "aFilterExtraDataAddress",
+                count: 2,
+                kind: VertexAttributeKind::U16,
+            },
+        ],
+    };
+
     pub const VECTOR_STENCIL: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
         ],
@@ -754,16 +805,17 @@ pub(crate) enum VertexArrayKind {
     Clip,
     VectorStencil,
     VectorCover,
     Border,
     Scale,
     LineDecoration,
     Gradient,
     Resolve,
+    SvgFilter,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -1047,18 +1099,35 @@ impl TextureResolver {
                     .expect(&format!("BUG: External image should be resolved by now"));
                 device.bind_external_texture(sampler, texture);
             }
             TextureSource::TextureCache(index) => {
                 let texture = &self.texture_cache_map[&index];
                 device.bind_texture(sampler, texture);
             }
             TextureSource::RenderTaskCache(saved_index) => {
-                let texture = &self.saved_targets[saved_index.0];
-                device.bind_texture(sampler, texture)
+                if saved_index.0 < self.saved_targets.len() {
+                    let texture = &self.saved_targets[saved_index.0];
+                    device.bind_texture(sampler, texture)
+                } else {
+                    // Check if this saved index is referring to a the prev pass
+                    if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
+                        let texture = match self.prev_pass_color {
+                            Some(ref at) => &at.texture,
+                            None => &self.dummy_cache_texture,
+                        };
+                        device.bind_texture(sampler, texture);
+                    } else if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
+                        let texture = match self.prev_pass_alpha {
+                            Some(ref at) => &at.texture,
+                            None => &self.dummy_cache_texture,
+                        };
+                        device.bind_texture(sampler, texture);
+                    }
+                }
             }
         }
     }
 
     // Get the real (OpenGL) texture ID for a given source texture.
     // For a texture cache texture, the IDs are stored in a vector
     // map for fast access.
     fn resolve(&self, texture_id: &TextureSource) -> Option<&Texture> {
@@ -1587,16 +1656,17 @@ pub struct RendererVAOs {
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
     border_vao: VAO,
     line_vao: VAO,
     scale_vao: VAO,
     gradient_vao: VAO,
     resolve_vao: VAO,
+    svg_filter_vao: VAO,
 }
 
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 ///
 /// We have a separate `Renderer` instance for each instance of WebRender (generally
 /// one per OS window), and all instances share the same thread.
@@ -1934,16 +2004,17 @@ impl Renderer {
 
         let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao);
         let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao);
         let border_vao = device.create_vao_with_new_instances(&desc::BORDER, &prim_vao);
         let scale_vao = device.create_vao_with_new_instances(&desc::SCALE, &prim_vao);
         let line_vao = device.create_vao_with_new_instances(&desc::LINE, &prim_vao);
         let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao);
         let resolve_vao = device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao);
+        let svg_filter_vao = device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao);
         let texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = TextureResolver::new(&mut device);
 
         let prim_header_f_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let prim_header_i_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAI32);
         let transforms_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
         let render_task_texture = VertexDataTexture::new(&mut device, ImageFormat::RGBAF32);
@@ -2164,16 +2235,17 @@ impl Renderer {
                 prim_vao,
                 blur_vao,
                 clip_vao,
                 border_vao,
                 scale_vao,
                 gradient_vao,
                 resolve_vao,
                 line_vao,
+                svg_filter_vao,
             },
             transforms_texture,
             prim_header_i_texture,
             prim_header_f_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
@@ -2506,16 +2578,21 @@ impl Renderer {
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
+        debug_target.add(
+            debug_server::BatchKind::Cache,
+            "SVG Filters",
+            target.svg_filters.iter().map(|(_, batch)| batch.len()).sum(),
+        );
 
         for alpha_batch_container in &target.alpha_batch_containers {
             for batch in alpha_batch_container.opaque_batches.iter().rev() {
                 debug_target.add(
                     debug_server::BatchKind::Opaque,
                     batch.key.kind.debug_name(),
                     batch.instances.len(),
                 );
@@ -3370,16 +3447,43 @@ impl Renderer {
         self.draw_instanced_batch(
             &scalings,
             VertexArrayKind::Scale,
             &BatchTextures::color(source),
             stats,
         );
     }
 
+    fn handle_svg_filters(
+        &mut self,
+        textures: &BatchTextures,
+        svg_filters: &[SvgFilterInstance],
+        projection: &Transform3D<f32>,
+        stats: &mut RendererStats,
+    ) {
+        if svg_filters.is_empty() {
+            return;
+        }
+
+        let _timer = self.gpu_profile.start_timer(GPU_TAG_SVG_FILTER);
+
+        self.shaders.borrow_mut().cs_svg_filter.bind(
+            &mut self.device,
+            &projection,
+            &mut self.renderer_errors
+        );
+
+        self.draw_instanced_batch(
+            &svg_filters,
+            VertexArrayKind::SvgFilter,
+            textures,
+            stats,
+        );
+    }
+
     fn draw_picture_cache_target(
         &mut self,
         target: &PictureCacheTarget,
         draw_target: DrawTarget,
         content_origin: DeviceIntPoint,
         projection: &Transform3D<f32>,
         render_tasks: &RenderTaskGraph,
         stats: &mut RendererStats,
@@ -3738,16 +3842,25 @@ impl Renderer {
 
         self.handle_scaling(
             &target.scalings,
             TextureSource::PrevPassColor,
             projection,
             stats,
         );
 
+        for (ref textures, ref filters) in &target.svg_filters {
+            self.handle_svg_filters(
+                textures,
+                filters,
+                projection,
+                stats,
+            );
+        }
+
         for alpha_batch_container in &target.alpha_batch_containers {
             self.draw_alpha_batch_container(
                 alpha_batch_container,
                 draw_target,
                 content_origin,
                 framebuffer_kind,
                 projection,
                 render_tasks,
@@ -5109,16 +5222,17 @@ impl Renderer {
         self.device.delete_vao(self.vaos.prim_vao);
         self.device.delete_vao(self.vaos.resolve_vao);
         self.device.delete_vao(self.vaos.clip_vao);
         self.device.delete_vao(self.vaos.gradient_vao);
         self.device.delete_vao(self.vaos.blur_vao);
         self.device.delete_vao(self.vaos.line_vao);
         self.device.delete_vao(self.vaos.border_vao);
         self.device.delete_vao(self.vaos.scale_vao);
+        self.device.delete_vao(self.vaos.svg_filter_vao);
 
         self.debug.deinit(&mut self.device);
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
         if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
             shaders.into_inner().deinit(&mut self.device);
@@ -5932,16 +6046,17 @@ fn get_vao<'a>(vertex_array_kind: Vertex
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
         VertexArrayKind::Gradient => &vaos.gradient_vao,
         VertexArrayKind::Resolve => &vaos.resolve_vao,
+        VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
@@ -5950,16 +6065,17 @@ fn get_vao<'a>(vertex_array_kind: Vertex
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
         VertexArrayKind::Scale => &vaos.scale_vao,
         VertexArrayKind::LineDecoration => &vaos.line_vao,
         VertexArrayKind::Gradient => &vaos.gradient_vao,
         VertexArrayKind::Resolve => &vaos.resolve_vao,
+        VertexArrayKind::SvgFilter => &vaos.svg_filter_vao,
     }
 }
 #[derive(Clone, Copy, PartialEq)]
 enum FramebufferKind {
     Main,
     Other,
 }
 
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -187,16 +187,17 @@ impl LazilyCompiledShader {
                 VertexArrayKind::Gradient => &desc::GRADIENT,
                 VertexArrayKind::Blur => &desc::BLUR,
                 VertexArrayKind::Clip => &desc::CLIP,
                 VertexArrayKind::VectorStencil => &desc::VECTOR_STENCIL,
                 VertexArrayKind::VectorCover => &desc::VECTOR_COVER,
                 VertexArrayKind::Border => &desc::BORDER,
                 VertexArrayKind::Scale => &desc::SCALE,
                 VertexArrayKind::Resolve => &desc::RESOLVE,
+                VertexArrayKind::SvgFilter => &desc::SVG_FILTER,
             };
 
             device.link_program(program, vertex_descriptor)?;
             device.bind_program(program);
             match self.kind {
                 ShaderKind::ClipCache => {
                     device.bind_shader_samplers(
                         &program,
@@ -506,16 +507,17 @@ pub struct Shaders {
     // of these shaders are then used by the primitive shaders.
     pub cs_blur_a8: LazilyCompiledShader,
     pub cs_blur_rgba8: LazilyCompiledShader,
     pub cs_border_segment: LazilyCompiledShader,
     pub cs_border_solid: LazilyCompiledShader,
     pub cs_scale: LazilyCompiledShader,
     pub cs_line_decoration: LazilyCompiledShader,
     pub cs_gradient: LazilyCompiledShader,
+    pub cs_svg_filter: LazilyCompiledShader,
 
     // Brush shaders
     brush_solid: BrushShader,
     brush_image: Vec<Option<BrushShader>>,
     brush_fast_image: Vec<Option<BrushShader>>,
     brush_blend: BrushShader,
     brush_mix_blend: BrushShader,
     brush_yuv_image: Vec<Option<BrushShader>>,
@@ -628,16 +630,24 @@ impl Shaders {
         let cs_blur_rgba8 = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Blur),
             "cs_blur",
             &["COLOR_TARGET"],
             device,
             options.precache_flags,
         )?;
 
+        let cs_svg_filter = LazilyCompiledShader::new(
+            ShaderKind::Cache(VertexArrayKind::SvgFilter),
+            "cs_svg_filter",
+            &[],
+            device,
+            options.precache_flags,
+        )?;
+
         let cs_clip_rectangle_slow = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_rectangle",
             &[],
             device,
             options.precache_flags,
         )?;
 
@@ -841,16 +851,17 @@ impl Shaders {
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
             cs_line_decoration,
             cs_gradient,
             cs_border_solid,
             cs_scale,
+            cs_svg_filter,
             brush_solid,
             brush_image,
             brush_fast_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
@@ -925,16 +936,17 @@ impl Shaders {
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_scale.deinit(device);
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
+        self.cs_svg_filter.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.cs_clip_rectangle_slow.deinit(device);
         self.cs_clip_rectangle_fast.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,37 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, BorderStyle, MixBlendMode, PipelineId, PremultipliedColorF};
-use api::{DocumentLayer, FilterData, FilterPrimitive, ImageFormat, LineOrientation};
+use api::{ColorF, BorderStyle, FilterPrimitive, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{DocumentLayer, FilterData, ImageFormat, LineOrientation};
 use api::units::*;
 #[cfg(feature = "pathfinder")]
 use api::FontRenderMode;
-use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image, BatchBuilder};
+use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, ClipBatcher, resolve_image, BatchBuilder};
 use crate::clip::ClipStore;
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX};
 use crate::debug_render::DebugItem;
 use crate::device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use crate::frame_builder::FrameGlobalResources;
-use crate::gpu_cache::{GpuCache};
-use crate::gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
+use crate::gpu_cache::{GpuCache, GpuCacheAddress};
+use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use crate::gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
 use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex, TextureSource, Filter};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use crate::picture::{RecordedDirtyRegion, SurfaceInfo};
 use crate::prim_store::gradient::GRADIENT_FP_STOPS;
 use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask};
 use crate::profiler::FrameProfileCounters;
 use crate::render_backend::{DataStores, FrameId};
-use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
+use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind, SvgFilterTask, SvgFilterInfo};
 use crate::render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskGraph, ScalingTask};
 use crate::resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use crate::texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
 
 
 const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
 const STYLE_MASK: i32 = 0x00FF_FF00;
@@ -356,16 +356,17 @@ pub struct GlyphJob;
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
     pub alpha_batch_containers: Vec<AlphaBatchContainer>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInstance>,
+    pub svg_filters: Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
     pub blits: Vec<BlitJob>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     alpha_tasks: Vec<RenderTaskId>,
     screen_size: DeviceIntSize,
     // Track the used rect of the render target, so that
     // we can set a scissor rect and only clear to the
     // used portion of the target as an optimization.
@@ -378,16 +379,17 @@ impl RenderTarget for ColorRenderTarget 
         _: bool,
     ) -> Self {
         ColorRenderTarget {
             alpha_batch_containers: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             scalings: Vec::new(),
+            svg_filters: Vec::new(),
             blits: Vec::new(),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             screen_size,
             used_rect: DeviceIntRect::zero(),
         }
     }
 
@@ -528,16 +530,27 @@ impl RenderTarget for ColorRenderTarget 
                 // store the information necessary to do the copy.
                 if let Some(pipeline_id) = pic.frame_output_pipeline_id {
                     self.outputs.push(FrameOutput {
                         pipeline_id,
                         task_id,
                     });
                 }
             }
+            RenderTaskKind::SvgFilter(ref task_info) => {
+                task_info.add_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_cache_handle.map(|handle| gpu_cache.get_address(&handle)),
+                )
+            }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Gradient(..) |
             RenderTaskKind::LineDecoration(..) => {
                 panic!("Should not be added to color target!");
             }
             RenderTaskKind::Glyph(..) => {
@@ -681,17 +694,18 @@ impl RenderTarget for AlphaRenderTarget 
 
         match task.kind {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::Border(..) |
             RenderTaskKind::LineDecoration(..) |
             RenderTaskKind::Gradient(..) |
-            RenderTaskKind::Glyph(..) => {
+            RenderTaskKind::Glyph(..) |
+            RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
                     BlurDirection::Vertical,
                     render_tasks.get_task_address(task_id),
                     render_tasks.get_task_address(task.children[0]),
@@ -891,17 +905,18 @@ impl TextureCacheRenderTarget {
                     start_stop: [task_info.start_point, task_info.end_point],
                 });
             }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
-            RenderTaskKind::Scaling(..) => {
+            RenderTaskKind::Scaling(..) |
+            RenderTaskKind::SvgFilter(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
             #[cfg(test)]
             RenderTaskKind::Test(..) => {}
         }
     }
 
     #[cfg(feature = "pathfinder")]
@@ -1439,8 +1454,105 @@ impl ScalingTask {
         let instance = ScalingInstance {
             task_address,
             src_task_address,
         };
 
         instances.push(instance);
     }
 }
+
+impl SvgFilterTask {
+    fn add_instances(
+        &self,
+        instances: &mut Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
+        render_tasks: &RenderTaskGraph,
+        filter: &SvgFilterInfo,
+        task_id: RenderTaskId,
+        input_1_task: Option<RenderTaskId>,
+        input_2_task: Option<RenderTaskId>,
+        extra_data_address: Option<GpuCacheAddress>,
+    ) {
+        let mut textures = BatchTextures::no_texture();
+
+        if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) {
+            textures.colors[0] = match saved_index {
+                Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
+                None => TextureSource::PrevPassColor,
+            };
+        }
+
+        if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) {
+            textures.colors[1] = match saved_index {
+                Some(saved_index) => TextureSource::RenderTaskCache(*saved_index),
+                None => TextureSource::PrevPassColor,
+            };
+        }
+
+        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,
+        };
+
+        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(..) => 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::LinearToSrgb |
+            SvgFilterInfo::SrgbToLinear |
+            SvgFilterInfo::Flood(..) |
+            SvgFilterInfo::Opacity(..) |
+            SvgFilterInfo::ColorMatrix(..) |
+            SvgFilterInfo::DropShadow(..) |
+            SvgFilterInfo::Offset(..) |
+            SvgFilterInfo::Identity => 0,
+        };
+
+        let instance = SvgFilterInstance {
+            task_address: render_tasks.get_task_address(task_id),
+            input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
+            input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)),
+            kind,
+            input_count,
+            generic_int,
+            extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID),
+        };
+
+        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;
+            }
+        }
+
+        instances.push((textures, vec![instance]));
+    }
+}
--- a/gfx/wr/webrender/tests/angle_shader_validation.rs
+++ b/gfx/wr/webrender/tests/angle_shader_validation.rs
@@ -59,16 +59,20 @@ const SHADERS: &[Shader] = &[
     Shader {
         name: "cs_gradient",
         features: CACHE_FEATURES,
     },
     Shader {
         name: "cs_border_solid",
         features: CACHE_FEATURES,
     },
+    Shader {
+        name: "cs_svg_filter",
+        features: CACHE_FEATURES,
+    },
     // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
         features: &[ "", "GLYPH_TRANSFORM" ],
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -696,39 +696,126 @@ pub enum MixBlendMode {
     Hue = 12,
     Saturation = 13,
     Color = 14,
     Luminosity = 15,
 }
 
 /// An input to a SVG filter primitive.
 #[repr(C)]
-#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
+pub enum ColorSpace {
+    Srgb,
+    LinearRgb,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
 pub enum FilterPrimitiveInput {
     /// The input is the original graphic that the filter is being applied to.
     Original,
     /// The input is the output of the previous filter primitive in the filter primitive chain.
     Previous,
     /// The input is the output of the filter primitive at the given index in the filter primitive chain.
     OutputOfPrimitiveIndex(usize),
 }
 
+impl FilterPrimitiveInput {
+    /// Gets the index of the input.
+    /// Returns `None` if the source graphic is the input.
+    pub fn to_index(self, cur_index: usize) -> Option<usize> {
+        match self {
+            FilterPrimitiveInput::Previous if cur_index > 0 => Some(cur_index - 1),
+            FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => Some(index),
+            _ => None,
+        }
+    }
+}
+
 #[repr(C)]
-#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BlendPrimitive {
     pub input1: FilterPrimitiveInput,
     pub input2: FilterPrimitiveInput,
     pub mode: MixBlendMode,
 }
 
-/// SVG Filter Primitive.
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct FloodPrimitive {
+    pub color: ColorF,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BlurPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub radius: f32,
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct OpacityPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub opacity: f32,
+}
+
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ColorMatrixPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub matrix: [f32; 20],
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct DropShadowPrimitive {
+    pub input: FilterPrimitiveInput,
+    pub shadow: Shadow,
+}
+
 #[repr(C)]
-#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
-pub enum FilterPrimitive {
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct ComponentTransferPrimitive {
+    pub input: FilterPrimitiveInput,
+    // Component transfer data is stored in FilterData.
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct IdentityPrimitive {
+    pub input: FilterPrimitiveInput,
+}
+
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub enum FilterPrimitiveKind {
+    Identity(IdentityPrimitive),
     Blend(BlendPrimitive),
+    Flood(FloodPrimitive),
+    Blur(BlurPrimitive),
+    // TODO: Support animated opacity?
+    Opacity(OpacityPrimitive),
+    /// cbindgen:derive-eq=false
+    ColorMatrix(ColorMatrixPrimitive),
+    DropShadow(DropShadowPrimitive),
+    ComponentTransfer(ComponentTransferPrimitive),
+}
+
+/// SVG Filter Primitive.
+/// See: https://github.com/eqrion/cbindgen/issues/9
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
+pub struct FilterPrimitive {
+    pub kind: FilterPrimitiveKind,
+    pub color_space: ColorSpace,
 }
 
 /// CSS filter.
 #[repr(C)]
 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
 pub enum FilterOp {
     /// Filter that does no transformation of the colors, needed for
     /// debug purposes only.
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -135,16 +135,20 @@ fn f32_node(parent: &mut Table, key: &st
 fn bool_node(parent: &mut Table, key: &str, value: bool) {
     yaml_node(parent, key, Yaml::Boolean(value));
 }
 
 fn table_node(parent: &mut Table, key: &str, value: Table) {
     yaml_node(parent, key, Yaml::Hash(value));
 }
 
+fn filter_input_node(parent: &mut Table, key: &str, value: FilterPrimitiveInput) {
+    yaml_node(parent, key, Yaml::String(filter_input_to_string(value)));
+}
+
 fn string_vec_yaml(value: &[String], check_unique: bool) -> Yaml {
     if !value.is_empty() && check_unique && array_elements_are_same(value) {
         Yaml::String(value[0].clone())
     } else {
         Yaml::Array(value.iter().map(|v| Yaml::String(v.clone())).collect())
     }
 }
 
@@ -358,24 +362,59 @@ fn write_stacking_context(
     }
 
     yaml_node(parent, "filter-datas", Yaml::Array(filter_datas));
 
     // filter primitives
     let mut filter_primitives = vec![];
     for filter_primitive in filter_primitive_iter {
         let mut table = new_table();
-        match filter_primitive {
-            FilterPrimitive::Blend(blend_primitive) => {
+        match filter_primitive.kind {
+            FilterPrimitiveKind::Identity(identity_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("identity".into()));
+                filter_input_node(&mut table, "in", identity_primitive.input);
+            }
+            FilterPrimitiveKind::Blend(blend_primitive) => {
                 yaml_node(&mut table, "type", Yaml::String("blend".into()));
-                yaml_node(&mut table, "in1", Yaml::String(filter_input_to_string(blend_primitive.input1)));
-                yaml_node(&mut table, "in2", Yaml::String(filter_input_to_string(blend_primitive.input2)));
+                filter_input_node(&mut table, "in1", blend_primitive.input1);
+                filter_input_node(&mut table, "in2", blend_primitive.input2);
                 enum_node(&mut table, "mode", blend_primitive.mode);
             }
+            FilterPrimitiveKind::Flood(flood_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("flood".into()));
+                color_node(&mut table, "color", flood_primitive.color);
+            }
+            FilterPrimitiveKind::Blur(blur_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("blur".into()));
+                filter_input_node(&mut table, "in", blur_primitive.input);
+                f32_node(&mut table, "radius", blur_primitive.radius);
+            }
+            FilterPrimitiveKind::Opacity(opacity_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("opacity".into()));
+                filter_input_node(&mut table, "in", opacity_primitive.input);
+                f32_node(&mut table, "opacity", opacity_primitive.opacity);
+            }
+            FilterPrimitiveKind::ColorMatrix(color_matrix_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("color-matrix".into()));
+                filter_input_node(&mut table, "in", color_matrix_primitive.input);
+                f32_vec_node(&mut table, "matrix", &color_matrix_primitive.matrix);
+            }
+            FilterPrimitiveKind::DropShadow(drop_shadow_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("drop-shadow".into()));
+                filter_input_node(&mut table, "in", drop_shadow_primitive.input);
+                vector_node(&mut table, "offset", &drop_shadow_primitive.shadow.offset);
+                color_node(&mut table, "color", drop_shadow_primitive.shadow.color);
+                f32_node(&mut table, "radius", drop_shadow_primitive.shadow.blur_radius);
+            }
+            FilterPrimitiveKind::ComponentTransfer(component_transfer_primitive) => {
+                yaml_node(&mut table, "type", Yaml::String("component-transfer".into()));
+                filter_input_node(&mut table, "in", component_transfer_primitive.input);
+            }
         }
+        enum_node(&mut table, "color-space", filter_primitive.color_space);
         filter_primitives.push(Yaml::Hash(table));
     }
 
     yaml_node(parent, "filter-primitives", Yaml::Array(filter_primitives));
 }
 
 #[cfg(target_os = "macos")]
 fn native_font_handle_to_yaml(
--- a/gfx/wr/wrench/src/yaml_helper.rs
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -36,16 +36,17 @@ pub trait YamlHelper {
     fn as_mix_blend_mode(&self) -> Option<MixBlendMode>;
     fn as_filter_op(&self) -> Option<FilterOp>;
     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 string_to_color(color: &str) -> Option<ColorF> {
     match color {
         "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
         "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
         "blue" => Some(ColorF::new(0.0, 0.0, 1.0, 1.0)),
         "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
@@ -150,16 +151,24 @@ define_string_enum!(
         Identity = "Identity",
         Table = "Table",
         Discrete = "Discrete",
         Linear = "Linear",
         Gamma = "Gamma"
     ]
 );
 
+define_string_enum!(
+    ColorSpace,
+    [
+        Srgb = "srgb",
+        LinearRgb = "linear-rgb"
+    ]
+);
+
 // Rotate around `axis` by `degrees` angle
 fn make_rotation(
     origin: &LayoutPoint,
     degrees: f32,
     axis_x: f32,
     axis_y: f32,
     axis_z: f32,
 ) -> LayoutTransform {
@@ -700,31 +709,87 @@ impl YamlHelper for Yaml {
             Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect())
         } else {
             self.as_filter_data().map(|data| vec![data])
         }
     }
 
     fn as_filter_primitive(&self) -> Option<FilterPrimitive> {
         if let Some(filter_type) = self["type"].as_str() {
-            match filter_type {
+            let kind = match filter_type {
+                "identity" => {
+                    FilterPrimitiveKind::Identity(IdentityPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                    })
+                }
                 "blend" => {
-                    Some(FilterPrimitive::Blend(BlendPrimitive {
+                    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(),
+                        radius: self["radius"].as_f32().unwrap(),
+                    })
+                }
+                "opacity" => {
+                    FilterPrimitiveKind::Opacity(OpacityPrimitive {
+                        input: self["in"].as_filter_input().unwrap(),
+                        opacity: self["opacity"].as_f32().unwrap(),
+                    })
                 }
-                _ => None,
-            }
+                "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(),
+                    })
+                }
+                _ => 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(|x| StringEnum::from_str(x))
+    }
 }