Bug 1505871. Implement putting the required data in the gpu cache for component transfer. r=gw
☠☠ backed out by 3c030119c0dc ☠ ☠
authorTimothy Nikkel <tnikkel@gmail.com>
Mon, 25 Feb 2019 19:20:28 -0600
changeset 461106 0007feaf988dca3eee682aa1f288209ea8cb3095
parent 461105 3cb8fb01e77e49e12290dc3b92c404da7435a11e
child 461107 9be871042749e932076d769de91a01bfbd696796
push id35618
push usershindli@mozilla.com
push dateTue, 26 Feb 2019 16:54:44 +0000
treeherdermozilla-central@d326a9d5f77b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1505871
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1505871. Implement putting the required data in the gpu cache for component transfer. r=gw For table/discrete we just create a lookup table for all 256 possible input values. We should probably switch to just computing the value in the shader, unless the list of value is really long.
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/filterdata.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/picture.rs
gfx/wr/webrender/src/tiling.rs
gfx/wr/webrender_api/src/display_item.rs
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1383,16 +1383,17 @@ impl AlphaBatchBuilder {
                                             FilterOp::Saturate(..) => 5,
                                             FilterOp::Sepia(..) => 6,
                                             FilterOp::Brightness(..) => 7,
                                             FilterOp::Opacity(..) => 8,
                                             FilterOp::DropShadow(..) => 9,
                                             FilterOp::ColorMatrix(..) => 10,
                                             FilterOp::SrgbToLinear => 11,
                                             FilterOp::LinearToSrgb => 12,
+                                            FilterOp::ComponentTransfer => unreachable!(),
                                         };
 
                                         let user_data = match filter {
                                             FilterOp::Identity => 0x10000i32, // matches `Contrast(1)`
                                             FilterOp::Contrast(amount) |
                                             FilterOp::Grayscale(amount) |
                                             FilterOp::Invert(amount) |
                                             FilterOp::Saturate(amount) |
@@ -1408,16 +1409,17 @@ impl AlphaBatchBuilder {
                                             // Go through different paths
                                             FilterOp::Blur(..) |
                                             FilterOp::DropShadow(..) => {
                                                 unreachable!();
                                             }
                                             FilterOp::ColorMatrix(_) => {
                                                 picture.extra_gpu_data_handle.as_int(gpu_cache)
                                             }
+                                            FilterOp::ComponentTransfer => unreachable!(),
                                         };
 
                                         let (uv_rect_address, textures) = surface
                                             .resolve(
                                                 render_tasks,
                                                 ctx.resource_cache,
                                                 gpu_cache,
                                             );
@@ -1447,16 +1449,70 @@ impl AlphaBatchBuilder {
                                             key,
                                             bounding_rect,
                                             z_id,
                                             PrimitiveInstanceData::from(instance),
                                         );
                                     }
                                 }
                             }
+                            PictureCompositeMode::ComponentTransferFilter(handle) => {
+                                // This is basically the same as the general filter case above
+                                // except we store a little more data in the filter mode and
+                                // a gpu cache handle in the user data.
+                                let surface = ctx.surfaces[raster_config.surface_index.0]
+                                    .surface
+                                    .as_ref()
+                                    .expect("bug: surface must be allocated by now");
+
+
+                                let filter_data = &ctx.data_stores.filterdata[handle];
+                                let filter_mode : i32 = 13 |
+                                    ((filter_data.data.r_func.to_int() << 28 |
+                                      filter_data.data.g_func.to_int() << 24 |
+                                      filter_data.data.b_func.to_int() << 20 |
+                                      filter_data.data.a_func.to_int() << 16) as i32);
+
+                                let user_data = filter_data.gpu_cache_handle.as_int(gpu_cache);
+
+                                let (uv_rect_address, textures) = surface
+                                    .resolve(
+                                        render_tasks,
+                                        ctx.resource_cache,
+                                        gpu_cache,
+                                    );
+
+                                let key = BatchKey::new(
+                                    BatchKind::Brush(BrushBatchKind::Blend),
+                                    BlendMode::PremultipliedAlpha,
+                                    textures,
+                                );
+
+                                let prim_header_index = prim_headers.push(&prim_header, z_id, [
+                                    uv_rect_address.as_int(),
+                                    filter_mode,
+                                    user_data,
+                                ]);
+
+                                let instance = BrushInstance {
+                                    prim_header_index,
+                                    clip_task_address,
+                                    segment_index: INVALID_SEGMENT_INDEX,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags,
+                                    user_data: 0,
+                                };
+
+                                self.current_batch_list().push_single_instance(
+                                    key,
+                                    bounding_rect,
+                                    z_id,
+                                    PrimitiveInstanceData::from(instance),
+                                );
+                            }
                             PictureCompositeMode::Puppet { master: Some(source) } if ctx.is_picture_surface_visible(source) => return,
                             PictureCompositeMode::MixBlend { mode, backdrop } if ctx.is_picture_surface_visible(backdrop) => {
                                 let backdrop_picture = &ctx.prim_store.pictures[backdrop.0];
 
                                 let source_id = ctx
                                     .surfaces[raster_config.surface_index.0]
                                     .surface
                                     .as_ref()
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -38,16 +38,17 @@ use resource_cache::{FontInstanceMap, Im
 use scene::{Scene, StackingContextHelpers};
 use scene_builder::{InternerMut, Interners};
 use spatial_node::{StickyFrameInfo, ScrollFrameKind, SpatialNodeType};
 use std::{f32, mem, usize};
 use std::collections::vec_deque::VecDeque;
 use std::sync::Arc;
 use tiling::{CompositeOps};
 use util::{MaxRect, VecHelper};
+use ::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
 
 impl ClipNode {
@@ -603,16 +604,17 @@ impl<'a> DisplayListFlattener<'a> {
             return;
         }
 
         let composition_operations = {
             // TODO(optimization?): self.traversal.display_list()
             let display_list = self.scene.get_display_list_for_pipeline(pipeline_id);
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, filters),
+                stacking_context.filter_datas_for_compositing(display_list, filter_datas),
                 stacking_context.mix_blend_mode_for_compositing(),
             )
         };
 
         let clip_chain_id = match stacking_context.clip_id {
             Some(clip_id) => self.id_to_index_mapper.get_clip_chain_id(clip_id),
             None => ClipChainId::NONE,
         };
@@ -1420,17 +1422,17 @@ impl<'a> DisplayListFlattener<'a> {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
             Picture3DContext::In { ancestor_index, .. } => {
-                assert_ne!(leaf_composite_mode, None);
+                assert!(!leaf_composite_mode.is_none());
                 Picture3DContext::In { root_data: None, ancestor_index }
             }
             Picture3DContext::Out => Picture3DContext::Out,
         };
 
         let leaf_prim_list = PrimitiveList::new(
             stacking_context.primitives,
             &self.interners,
@@ -1506,19 +1508,51 @@ impl<'a> DisplayListFlattener<'a> {
                 stacking_context.is_backface_visible,
                 ClipChainId::NONE,
                 stacking_context.spatial_node_index,
                 &mut self.interners,
             );
         }
 
         // For each filter, create a new image with that composite mode.
+        let mut current_filter_data_index = 0;
         for filter in &stacking_context.composite_ops.filters {
             let filter = filter.sanitize();
-            let composite_mode = Some(PictureCompositeMode::Filter(filter));
+
+            let composite_mode = Some(match filter {
+                FilterOp::ComponentTransfer => {
+                    let filter_data =
+                        &stacking_context.composite_ops.filter_datas[current_filter_data_index];
+                    let filter_data = filter_data.sanitize();
+                    current_filter_data_index = current_filter_data_index + 1;
+                    if filter_data.is_identity() {
+                        continue
+                    } else {
+                        let filter_data_key = SFilterDataKey {
+                            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),
+                                },
+                        };
+
+                        let handle = self.interners
+                            .filterdata
+                            .intern(&filter_data_key, || ());
+                        PictureCompositeMode::ComponentTransferFilter(handle)
+                    }
+                }
+                _ => PictureCompositeMode::Filter(filter),
+            });
 
             let filter_pic_index = PictureIndex(self.prim_store.pictures
                 .alloc()
                 .init(PicturePrimitive::new_image(
                     composite_mode,
                     Picture3DContext::Out,
                     stacking_context.pipeline_id,
                     None,
--- a/gfx/wr/webrender/src/filterdata.rs
+++ b/gfx/wr/webrender/src/filterdata.rs
@@ -55,16 +55,41 @@ impl hash::Hash for SFilterDataComponent
                 a.to_bits().hash(state);
                 b.to_bits().hash(state);
                 c.to_bits().hash(state);
             }
         }
     }
 }
 
+impl SFilterDataComponent {
+    pub fn to_int(&self) -> u32 {
+        match self {
+            SFilterDataComponent::Identity => 0,
+            SFilterDataComponent::Table(_) => 1,
+            SFilterDataComponent::Discrete(_) => 2,
+            SFilterDataComponent::Linear(_, _) => 3,
+            SFilterDataComponent::Gamma(_, _, _) => 4,
+        }
+    }
+
+    pub fn from_functype_values(
+        func_type: ComponentTransferFuncType,
+        values: &[f32],
+    ) -> SFilterDataComponent {
+        match func_type {
+            ComponentTransferFuncType::Identity => SFilterDataComponent::Identity,
+            ComponentTransferFuncType::Table => SFilterDataComponent::Table(values.to_vec()),
+            ComponentTransferFuncType::Discrete => SFilterDataComponent::Discrete(values.to_vec()),
+            ComponentTransferFuncType::Linear => SFilterDataComponent::Linear(values[0], values[1]),
+            ComponentTransferFuncType::Gamma => SFilterDataComponent::Gamma(values[0], values[1], values[2]),
+        }
+    }
+}
+
 #[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct SFilterData {
     pub r_func: SFilterDataComponent,
     pub g_func: SFilterDataComponent,
     pub b_func: SFilterDataComponent,
     pub a_func: SFilterDataComponent,
@@ -91,8 +116,83 @@ pub struct SFilterDataTemplate {
 impl From<SFilterDataKey> for SFilterDataTemplate {
     fn from(item: SFilterDataKey) -> Self {
         SFilterDataTemplate {
             data: item.data,
             gpu_cache_handle: GpuCacheHandle::new(),
         }
     }
 }
+
+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);
+        }
+    }
+}
+
+fn push_component_transfer_data(
+    func_comp: &SFilterDataComponent,
+    request: &mut GpuDataRequest,
+) {
+    match func_comp {
+        SFilterDataComponent::Identity => { return; }
+        SFilterDataComponent::Table(values) |
+        SFilterDataComponent::Discrete(values) => {
+            // Push a 256 entry lookup table.
+            assert!(values.len() > 0);
+            for i in 0 .. 64 {
+                let mut arr = [0.0 ; 4];
+                for j in 0 .. 4 {
+                    if (values.len() == 1) || (i == 63 && j == 3) {
+                        arr[j] = values[values.len()-1];
+                    } else {
+                        let c = ((4*i + j) as f32)/255.0;
+                        match func_comp {
+                            SFilterDataComponent::Table(_) => {
+                                let n = (values.len()-1) as f32;
+                                let k = (n * c).floor() as u32;
+                                let ku = k as usize;
+                                assert!(ku < values.len()-1);
+                                arr[j] = values[ku] + (c*n - (k as f32)) * (values[ku+1] - values[ku]);
+                            }
+                            SFilterDataComponent::Discrete(_) => {
+                                let n = values.len() as f32;
+                                let k = (n * c).floor() as usize;
+                                assert!(k < values.len());
+                                arr[j] = values[k];
+                            }
+                            SFilterDataComponent::Identity |
+                            SFilterDataComponent::Linear(_,_) |
+                            SFilterDataComponent::Gamma(_,_,_) => {
+                                unreachable!();
+                            }
+                        }
+
+                    }
+                }
+
+                request.push(arr);
+            }
+        }
+        SFilterDataComponent::Linear(a, b) => {
+            request.push([*a, *b, 0.0, 0.0]);
+        }
+        SFilterDataComponent::Gamma(a, b, c) => {
+            request.push([*a, *b, *c, 0.0]);
+        }
+    }
+}
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -32,16 +32,17 @@ use resource_cache::ResourceCache;
 use scene::{FilterOpHelpers, SceneProperties};
 use scene_builder::Interners;
 use smallvec::SmallVec;
 use std::{mem, u16};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use texture_cache::TextureCacheHandle;
 use tiling::RenderTargetKind;
 use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect};
+use ::filterdata::{FilterDataHandle};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
    picture into its parent.
  * A configuration describing how to draw the primitives on
@@ -1875,35 +1876,37 @@ bitflags! {
         /// Preserve-3D requires a surface for plane-splitting.
         const PRESERVE3D = 2;
     }
 }
 
 /// Specifies how this Picture should be composited
 /// onto the target it belongs to.
 #[allow(dead_code)]
-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub enum PictureCompositeMode {
     /// Don't composite this picture in a standard way,
     /// can be used for pictures that need to be isolated but used
     /// manually, e.g. for the backdrop of mix-blend pictures.
     Puppet {
         /// The master picture that actually handles compositing
         /// of this one. If that picture turns out to be invisible,
         /// the puppet mode becomes a regular blit.
         master: Option<PictureIndex>,
     },
     /// Apply CSS mix-blend-mode effect.
     MixBlend {
         mode: MixBlendMode,
         backdrop: PictureIndex,
     },
-    /// Apply a CSS filter.
+    /// Apply a CSS filter (except component transfer).
     Filter(FilterOp),
+    /// Apply a component transfer filter.
+    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 {
         clear_color: ColorF,
     },
 }
@@ -3097,16 +3100,43 @@ impl PicturePrimitive {
                     pic_context.raster_spatial_node_index,
                     device_pixel_scale,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
                 PictureSurface::RenderTask(render_task_id)
             }
+            PictureCompositeMode::ComponentTransferFilter(handle) => {
+                let filter_data = &mut data_stores.filterdata[handle];
+                filter_data.update(frame_state);
+
+                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,
+                    child_tasks,
+                    uv_rect_kind,
+                    pic_context.raster_spatial_node_index,
+                    device_pixel_scale,
+                );
+
+                let render_task_id = frame_state.render_tasks.add(picture_task);
+                frame_state.surfaces[surface_index.0].tasks.push(render_task_id);
+                PictureSurface::RenderTask(render_task_id)
+            }
             PictureCompositeMode::Puppet { .. } |
             PictureCompositeMode::MixBlend { .. } |
             PictureCompositeMode::Blit(_) => {
                 // The SplitComposite shader used for 3d contexts doesn't snap
                 // to pixels, so we shouldn't snap our uv coordinates either.
                 let supports_snapping = match self.context_3d {
                     Picture3DContext::In { .. } => false,
                     _ => true,
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -1,16 +1,17 @@
 /* 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, FilterOp, LayoutSize, LayoutPrimitiveInfo, MixBlendMode,
     PropertyBinding, PropertyBindingId, LayoutVector2D,
 };
+use intern::ItemUid;
 use app_units::Au;
 use display_list_flattener::{AsInstanceKind, IsVisible};
 use intern::{Internable, InternDebug};
 use intern_types;
 use picture::PictureCompositeMode;
 use prim_store::{
     PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData,
     PrimitiveInstanceKind, PrimitiveSceneData, PrimitiveStore, VectorKey,
@@ -35,16 +36,17 @@ pub enum PictureCompositeKey {
     Opacity(Au),
     OpacityBinding(PropertyBindingId, Au),
     Saturate(Au),
     Sepia(Au),
     DropShadow(VectorKey, Au, ColorU),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
+    ComponentTransfer(ItemUid),
 
     // MixBlendMode
     Multiply,
     Screen,
     Overlay,
     Darken,
     Lighten,
     ColorDodge,
@@ -110,18 +112,22 @@ impl From<Option<PictureCompositeMode>> 
                     }
                     FilterOp::ColorMatrix(values) => {
                         let mut quantized_values: [Au; 20] = [Au(0); 20];
                         for (value, result) in values.iter().zip(quantized_values.iter_mut()) {
                             *result = Au::from_f32_px(*value);
                         }
                         PictureCompositeKey::ColorMatrix(quantized_values)
                     }
+                    FilterOp::ComponentTransfer => unreachable!(),
                 }
             }
+            Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
+                PictureCompositeKey::ComponentTransfer(handle.uid())
+            }
             Some(PictureCompositeMode::Puppet { .. }) |
             Some(PictureCompositeMode::Blit(_)) |
             Some(PictureCompositeMode::TileCache { .. }) |
             None => {
                 PictureCompositeKey::Identity
             }
         }
     }
@@ -220,12 +226,12 @@ impl IsVisible for Picture {
 fn test_struct_sizes() {
     use std::mem;
     // The sizes of these structures are critical for performance on a number of
     // talos stress tests. If you get a failure here on CI, there's two possibilities:
     // (a) You made a structure smaller than it currently is. Great work! Update the
     //     test expectations and move on.
     // (b) You made a structure larger. This is not necessarily a problem, but should only
     //     be done with care, and after checking if talos performance regresses badly.
-    assert_eq!(mem::size_of::<Picture>(), 84, "Picture size changed");
+    assert_eq!(mem::size_of::<Picture>(), 88, "Picture size changed");
     assert_eq!(mem::size_of::<PictureTemplate>(), 20, "PictureTemplate size changed");
-    assert_eq!(mem::size_of::<PictureKey>(), 96, "PictureKey size changed");
+    assert_eq!(mem::size_of::<PictureKey>(), 104, "PictureKey size changed");
 }
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,14 +1,14 @@
 /* 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, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
-use api::{DocumentLayer, FilterOp, ImageFormat, DevicePoint};
+use api::{DocumentLayer, FilterOp, FilterData, ImageFormat, DevicePoint};
 use api::{MixBlendMode, PipelineId, DeviceRect, LayoutSize, WorldRect};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use debug_render::DebugItem;
 use device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
@@ -1100,31 +1100,35 @@ impl RenderPass {
         }
     }
 }
 
 #[derive(Debug, Clone, Default)]
 pub struct CompositeOps {
     // Requires only a single texture as input (e.g. most filters)
     pub filters: Vec<FilterOp>,
+    pub filter_datas: Vec<FilterData>,
 
     // Requires two source textures (e.g. mix-blend-mode)
     pub mix_blend_mode: Option<MixBlendMode>,
 }
 
 impl CompositeOps {
-    pub fn new(filters: Vec<FilterOp>, mix_blend_mode: Option<MixBlendMode>) -> Self {
+    pub fn new(filters: Vec<FilterOp>,
+               filter_datas: Vec<FilterData>,
+               mix_blend_mode: Option<MixBlendMode>) -> Self {
         CompositeOps {
             filters,
+            filter_datas,
             mix_blend_mode,
         }
     }
 
     pub fn is_empty(&self) -> bool {
-        self.filters.is_empty() && self.mix_blend_mode.is_none()
+        self.filters.is_empty() && self.filter_datas.is_empty() && self.mix_blend_mode.is_none()
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -682,16 +682,91 @@ pub struct FilterData {
     pub func_g_type: ComponentTransferFuncType,
     pub g_values: Vec<f32>,
     pub func_b_type: ComponentTransferFuncType,
     pub b_values: Vec<f32>,
     pub func_a_type: ComponentTransferFuncType,
     pub a_values: Vec<f32>,
 }
 
+fn sanitize_func_type(
+    func_type: ComponentTransferFuncType,
+    values: &[f32],
+) -> ComponentTransferFuncType {
+    if values.is_empty() {
+        return ComponentTransferFuncType::Identity;
+    }
+    if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+        return ComponentTransferFuncType::Identity;
+    }
+    if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+        return ComponentTransferFuncType::Identity;
+    }
+    func_type
+}
+
+fn sanitize_values(
+    func_type: ComponentTransferFuncType,
+    values: &[f32],
+) -> bool {
+    if values.len() < 2 && func_type == ComponentTransferFuncType::Linear {
+        return false;
+    }
+    if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma {
+        return false;
+    }
+    true
+}
+
+impl FilterData {
+    /// Ensure that the number of values matches up with the function type.
+    pub fn sanitize(&self) -> FilterData {
+        FilterData {
+            func_r_type: sanitize_func_type(self.func_r_type, &self.r_values),
+            r_values:
+                    if sanitize_values(self.func_r_type, &self.r_values) {
+                        self.r_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+            func_g_type: sanitize_func_type(self.func_g_type, &self.g_values),
+            g_values:
+                    if sanitize_values(self.func_g_type, &self.g_values) {
+                        self.g_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+            func_b_type: sanitize_func_type(self.func_b_type, &self.b_values),
+            b_values:
+                    if sanitize_values(self.func_b_type, &self.b_values) {
+                        self.b_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+            func_a_type: sanitize_func_type(self.func_a_type, &self.a_values),
+            a_values:
+                    if sanitize_values(self.func_a_type, &self.a_values) {
+                        self.a_values.clone()
+                    } else {
+                        Vec::new()
+                    },
+
+        }
+    }
+
+    pub fn is_identity(&self) -> bool {
+        self.func_r_type == ComponentTransferFuncType::Identity &&
+        self.func_g_type == ComponentTransferFuncType::Identity &&
+        self.func_b_type == ComponentTransferFuncType::Identity &&
+        self.func_a_type == ComponentTransferFuncType::Identity
+    }
+}
+
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
     pub pipeline_id: PipelineId,
     pub ignore_missing_pipeline: bool,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {