Bug 1536121 - cleanup display list code a little bit to prep for refactor. r=gw
authorAlexis Beingessner <a.beingessner@gmail.com>
Tue, 23 Apr 2019 17:29:38 +0000
changeset 470518 63ab1f26526681b3a14ba1e839a9f0ad13eb390d
parent 470517 7b326aa4930cad966e0a01d2d3d3d585d35002e2
child 470519 9550416c06abcb24b583cecc7cc09220e3208318
push id35906
push useraciure@mozilla.com
push dateTue, 23 Apr 2019 22:14:56 +0000
treeherdermozilla-central@0ce3633f8b80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1536121
milestone68.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 1536121 - cleanup display list code a little bit to prep for refactor. r=gw * make all enums repr(u8) (compiler bug blocking this long fixed) * add display list stats feature * remove cache markers (abandoned design) * don't always push empty SetFilters before PushStackingContext * remove dead pub methods Differential Revision: https://phabricator.services.mozilla.com/D25845
gfx/wr/webrender/Cargo.toml
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender_api/Cargo.toml
gfx/wr/webrender_api/src/display_item.rs
gfx/wr/webrender_api/src/display_list.rs
gfx/wr/webrender_api/src/font.rs
gfx/wr/webrender_api/src/image.rs
--- a/gfx/wr/webrender/Cargo.toml
+++ b/gfx/wr/webrender/Cargo.toml
@@ -9,16 +9,17 @@ build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
 debugger = ["ws", "serde_json", "serde", "image", "base64"]
 capture = ["webrender_api/serialize", "ron", "serde", "smallvec/serde"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
+display_list_stats = ["webrender_api/display_list_stats"]
 pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 serialize_program = ["serde", "webrender_build/serialize_program"]
 no_static_freetype = []
 
 [build-dependencies]
 webrender_build = { version = "0.0.1", path = "../webrender_build" }
 
 [dependencies]
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -607,34 +607,51 @@ impl<'a> DisplayListFlattener<'a> {
         loop {
             let subtraversal = {
                 let item = match traversal.next() {
                     Some(item) => item,
                     None => break,
                 };
 
                 match item.item() {
-                    SpecificDisplayItem::PopReferenceFrame |
-                    SpecificDisplayItem::PopStackingContext => return,
+                    DisplayItem::PopReferenceFrame |
+                    DisplayItem::PopStackingContext => return,
                     _ => (),
                 }
 
                 self.flatten_item(
                     item,
                     pipeline_id,
                     apply_pipeline_clip,
                 )
             };
 
             // If flatten_item created a sub-traversal, we need `traversal` to have the
             // same state as the completed subtraversal, so we reinitialize it here.
-            if let Some(subtraversal) = subtraversal {
+            if let Some(mut subtraversal) = subtraversal {
+                subtraversal.merge_debug_stats_from(traversal);
                 *traversal = subtraversal;
             }
         }
+
+        // TODO: factor this out to be part of capture
+        if cfg!(feature = "display_list_stats") {
+            let stats = traversal.debug_stats();
+            let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum();
+            println!("item, total count, total bytes, % of DL bytes, bytes per item");
+            for (label, stats) in stats {
+                println!("{}, {}, {}kb, {}%, {}",
+                    label,
+                    stats.total_count,
+                    stats.num_bytes / 1000,
+                    ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize,
+                    stats.num_bytes / stats.total_count.max(1));
+            }
+            println!("");
+        }
     }
 
     fn flatten_sticky_frame(
         &mut self,
         item: &DisplayItemRef,
         info: &StickyFrameDisplayItem,
         parent_node_index: SpatialNodeIndex,
     ) {
@@ -1051,17 +1068,17 @@ impl<'a> DisplayListFlattener<'a> {
                 );
                 self.add_clip_node(info.id, space_and_clip, clip_region);
             }
             SpecificDisplayItem::ClipChain(ref info) => {
                 // For a user defined clip-chain the parent (if specified) must
                 // refer to another user defined clip-chain. If none is specified,
                 // the parent is the root clip-chain for the given pipeline. This
                 // is used to provide a root clip chain for iframes.
-                let mut parent_clip_chain_id = match info.parent {
+                let parent_clip_chain_id = match info.parent {
                     Some(id) => {
                         self.id_to_index_mapper.get_clip_chain_id(ClipId::ClipChain(id))
                     }
                     None => {
                         self.pipeline_clip_chain_stack.last().cloned().unwrap()
                     }
                 };
 
@@ -1146,20 +1163,16 @@ impl<'a> DisplayListFlattener<'a> {
                 unreachable!("Should have returned in parent method.")
             }
             SpecificDisplayItem::PushShadow(shadow) => {
                 self.push_shadow(shadow, clip_and_scroll);
             }
             SpecificDisplayItem::PopAllShadows => {
                 self.pop_all_shadows();
             }
-            SpecificDisplayItem::PushCacheMarker(_marker) => {
-            }
-            SpecificDisplayItem::PopCacheMarker => {
-            }
         }
 
         None
     }
 
     // Given a list of clip sources, a positioning node and
     // a parent clip chain, return a new clip chain entry.
     // If the supplied list of clip sources is empty, then
--- a/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -53,17 +53,17 @@ impl GlyphRasterizer {
         );
         let mut new_glyphs = Vec::new();
 
         let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
 
         // select glyphs that have not been requested yet.
         for key in glyph_keys {
             match glyph_key_cache.entry(key.clone()) {
-                Entry::Occupied(mut entry) => {
+                Entry::Occupied(entry) => {
                     let value = entry.into_mut();
                     match *value {
                         GlyphCacheEntry::Cached(ref glyph) => {
                             // Skip the glyph if it is already has a valid texture cache handle.
                             if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) {
                                 continue;
                             }
                         }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1294,17 +1294,17 @@ impl RenderBackend {
                     None,
                     txn.frame_ops.take(),
                     txn.notifications.take(),
                     txn.render_frame,
                     txn.invalidate_rendered_frame,
                     frame_counter,
                     profile_counters,
                     false
-                );                
+                );
             }
 
             self.resource_cache.after_frames();
             return;
         }
 
         let tx = if use_high_priority {
             &self.scene_tx
@@ -1523,26 +1523,26 @@ impl RenderBackend {
                     Some(item) => item,
                     None => break,
                 };
 
                 match *item.item() {
                     display_item @ SpecificDisplayItem::PushStackingContext(..) => {
                         let mut subtraversal = item.sub_iter();
                         let mut child_node =
-                            debug_server::TreeNode::new(&display_item.debug_string());
+                            debug_server::TreeNode::new(&display_item.debug_name().to_string());
                         self.traverse_items(&mut subtraversal, &mut child_node);
                         node.add_child(child_node);
                         Some(subtraversal)
                     }
                     SpecificDisplayItem::PopStackingContext => {
                         return;
                     }
                     display_item => {
-                        node.add_item(&display_item.debug_string());
+                        node.add_item(&display_item.debug_name().to_string());
                         None
                     }
                 }
             };
 
             // If flatten_item created a sub-traversal, we need `traversal` to have the
             // same state as the completed subtraversal, so we reinitialize it here.
             if let Some(subtraversal) = subtraversal {
@@ -1628,56 +1628,16 @@ fn get_blob_image_updates(updates: &[Res
             }
             _ => {}
         }
     }
 
     requests
 }
 
-
-#[cfg(feature = "debugger")]
-trait ToDebugString {
-    fn debug_string(&self) -> String;
-}
-
-#[cfg(feature = "debugger")]
-impl ToDebugString for SpecificDisplayItem {
-    fn debug_string(&self) -> String {
-        match *self {
-            SpecificDisplayItem::Border(..) => String::from("border"),
-            SpecificDisplayItem::BoxShadow(..) => String::from("box_shadow"),
-            SpecificDisplayItem::ClearRectangle => String::from("clear_rectangle"),
-            SpecificDisplayItem::Clip(..) => String::from("clip"),
-            SpecificDisplayItem::ClipChain(..) => String::from("clip_chain"),
-            SpecificDisplayItem::Gradient(..) => String::from("gradient"),
-            SpecificDisplayItem::Iframe(..) => String::from("iframe"),
-            SpecificDisplayItem::Image(..) => String::from("image"),
-            SpecificDisplayItem::Line(..) => String::from("line"),
-            SpecificDisplayItem::PopAllShadows => String::from("pop_all_shadows"),
-            SpecificDisplayItem::PopReferenceFrame => String::from("pop_reference_frame"),
-            SpecificDisplayItem::PopStackingContext => String::from("pop_stacking_context"),
-            SpecificDisplayItem::PushShadow(..) => String::from("push_shadow"),
-            SpecificDisplayItem::PushReferenceFrame(..) => String::from("push_reference_frame"),
-            SpecificDisplayItem::PushStackingContext(..) => String::from("push_stacking_context"),
-            SpecificDisplayItem::SetFilterOps => String::from("set_filter_ops"),
-            SpecificDisplayItem::SetFilterData => String::from("set_filter_data"),
-            SpecificDisplayItem::RadialGradient(..) => String::from("radial_gradient"),
-            SpecificDisplayItem::Rectangle(..) => String::from("rectangle"),
-            SpecificDisplayItem::ScrollFrame(..) => String::from("scroll_frame"),
-            SpecificDisplayItem::SetGradientStops => String::from("set_gradient_stops"),
-            SpecificDisplayItem::StickyFrame(..) => String::from("sticky_frame"),
-            SpecificDisplayItem::Text(..) => String::from("text"),
-            SpecificDisplayItem::YuvImage(..) => String::from("yuv_image"),
-            SpecificDisplayItem::PushCacheMarker(..) => String::from("push_cache_marker"),
-            SpecificDisplayItem::PopCacheMarker => String::from("pop_cache_marker"),
-        }
-    }
-}
-
 impl RenderBackend {
     #[cfg(feature = "capture")]
     // Note: the mutable `self` is only needed here for resolving blob images
     fn save_capture(
         &mut self,
         root: PathBuf,
         bits: CaptureBits,
         profile_counters: &mut BackendProfileCounters,
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -2118,17 +2118,17 @@ impl Renderer {
                 38,
                 22,
                 41,
                 25,
                 37,
                 21,
             ];
 
-            let mut texture = device.create_texture(
+            let texture = device.create_texture(
                 TextureTarget::Default,
                 ImageFormat::R8,
                 8,
                 8,
                 TextureFilter::Nearest,
                 None,
                 1,
             );
@@ -2472,17 +2472,17 @@ impl Renderer {
                 ResultMsg::PublishPipelineInfo(mut pipeline_info) => {
                     for ((pipeline_id, document_id), epoch) in pipeline_info.epochs {
                         self.pipeline_info.epochs.insert((pipeline_id, document_id), epoch);
                     }
                     self.pipeline_info.removed_pipelines.extend(pipeline_info.removed_pipelines.drain(..));
                 }
                 ResultMsg::PublishDocument(
                     document_id,
-                    mut doc,
+                    doc,
                     texture_update_list,
                     profile_counters,
                 ) => {
                     if doc.is_new_scene {
                         self.new_scene_indicator.changed();
                     }
 
                     // Add a new document to the active set, expressed as a `Vec` in order
--- a/gfx/wr/webrender_api/Cargo.toml
+++ b/gfx/wr/webrender_api/Cargo.toml
@@ -6,16 +6,17 @@ license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 description = "Public API for WebRender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 serialize = []
 deserialize = []
+display_list_stats = []
 
 [dependencies]
 app_units = "0.7"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
 derive_more = "0.13"
 ipc-channel = {version = "0.11.0", optional = true}
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -95,17 +95,17 @@ impl SpaceAndClipInfo {
     pub fn root_scroll(pipeline_id: PipelineId) -> Self {
         SpaceAndClipInfo {
             spatial_id: SpatialId::root_scroll_node(pipeline_id),
             clip_id: ClipId::root(pipeline_id),
         }
     }
 }
 
-#[repr(u64)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     ScrollFrame(ScrollFrameDisplayItem),
     StickyFrame(StickyFrameDisplayItem),
     Rectangle(RectangleDisplayItem),
     ClearRectangle,
     Line(LineDisplayItem),
@@ -120,18 +120,16 @@ pub enum SpecificDisplayItem {
     Iframe(IframeDisplayItem),
     PushReferenceFrame(ReferenceFrameDisplayListItem),
     PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops,
     PushShadow(Shadow),
     PopAllShadows,
-    PushCacheMarker(CacheMarkerDisplayItem),
-    PopCacheMarker,
     SetFilterOps,
     SetFilterData,
 }
 
 /// This is a "complete" version of the DI specifics,
 /// containing the auxiliary data within the corresponding
 /// enumeration variants, to be used for debug serialization.
 #[cfg(any(feature = "serialize", feature = "deserialize"))]
@@ -155,18 +153,16 @@ pub enum CompletelySpecificDisplayItem {
     Iframe(IframeDisplayItem),
     PushReferenceFrame(ReferenceFrameDisplayListItem),
     PopReferenceFrame,
     PushStackingContext(PushStackingContextDisplayItem),
     PopStackingContext,
     SetGradientStops(Vec<GradientStop>),
     PushShadow(Shadow),
     PopAllShadows,
-    PushCacheMarker(CacheMarkerDisplayItem),
-    PopCacheMarker,
     SetFilterOps(Vec<FilterOp>),
     SetFilterData(FilterData),
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
     pub id: ClipId,
     pub image_mask: Option<ImageMask>,
@@ -332,17 +328,17 @@ impl NormalBorder {
 
         normalize_side(&mut self.left, widths.left);
         normalize_side(&mut self.right, widths.right);
         normalize_side(&mut self.top, widths.top);
         normalize_side(&mut self.bottom, widths.bottom);
     }
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum RepeatMode {
     Stretch,
     Repeat,
     Round,
     Space,
 }
 
@@ -442,17 +438,17 @@ pub enum BorderStyle {
 }
 
 impl BorderStyle {
     pub fn is_hidden(&self) -> bool {
         *self == BorderStyle::Hidden || *self == BorderStyle::None
     }
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
 pub enum BoxShadowClipMode {
     Outset = 0,
     Inset = 1,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BoxShadowDisplayItem {
@@ -524,22 +520,16 @@ pub struct RadialGradientDisplayItem {
     pub tile_spacing: LayoutSize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ReferenceFrameDisplayListItem {
     pub reference_frame: ReferenceFrame,
 }
 
-/// Provides a hint to WR that it should try to cache the items
-/// within a cache marker context in an off-screen surface.
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub struct CacheMarkerDisplayItem {
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum ReferenceFrameKind {
     Transform,
     Perspective {
         scrolling_relative_to: Option<ExternalScrollId>,
     }
 }
 
@@ -563,31 +553,31 @@ pub struct StackingContext {
     pub transform_style: TransformStyle,
     pub mix_blend_mode: MixBlendMode,
     pub clip_id: Option<ClipId>,
     pub raster_space: RasterSpace,
     /// True if picture caching should be used on this stacking context.
     pub cache_tiles: bool,
 } // IMPLICIT: filters: Vec<FilterOp>, filter_datas: Vec<FilterData>
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
     Flat = 0,
     Preserve3D = 1,
 }
 
 /// Configure whether the contents of a stacking context
 /// should be rasterized in local space or screen space.
 /// Local space rasterized pictures are typically used
 /// when we want to cache the output, and performance is
 /// important. Note that this is a performance hint only,
 /// which WR may choose to ignore.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-#[repr(u32)]
+#[repr(u8)]
 pub enum RasterSpace {
     // Rasterize in local-space, applying supplied scale to primitives.
     // Best performance, but lower quality.
     Local(f32),
 
     // Rasterize the picture in screen-space, including rotation / skew etc in
     // the rasterized element. Best quality, but slower performance. Note that
     // any stacking context with a perspective transform will be rasterized
@@ -599,17 +589,17 @@ impl RasterSpace {
     pub fn local_scale(&self) -> Option<f32> {
         match *self {
             RasterSpace::Local(scale) => Some(scale),
             RasterSpace::Screen => None,
         }
     }
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum MixBlendMode {
     Normal = 0,
     Multiply = 1,
     Screen = 2,
     Overlay = 3,
     Darken = 4,
     Lighten = 5,
@@ -773,17 +763,17 @@ pub struct ImageDisplayItem {
     pub image_key: ImageKey,
     pub stretch_size: LayoutSize,
     pub tile_spacing: LayoutSize,
     pub image_rendering: ImageRendering,
     pub alpha_type: AlphaType,
     pub color: ColorF,
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
 pub enum ImageRendering {
     Auto = 0,
     CrispEdges = 1,
     Pixelated = 2,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
@@ -795,17 +785,17 @@ pub enum AlphaType {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct YuvImageDisplayItem {
     pub yuv_data: YuvData,
     pub color_depth: ColorDepth,
     pub color_space: YuvColorSpace,
     pub image_rendering: ImageRendering,
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
 pub enum YuvColorSpace {
     Rec601 = 0,
     Rec709 = 1,
     Rec2020 = 2,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -1065,8 +1055,39 @@ impl ExternalScrollId {
     pub fn pipeline_id(&self) -> PipelineId {
         self.1
     }
 
     pub fn is_root(&self) -> bool {
         self.0 == 0
     }
 }
+
+impl SpecificDisplayItem {
+    pub fn debug_name(&self) -> &'static str {
+        match *self {
+            SpecificDisplayItem::Border(..) => "border",
+            SpecificDisplayItem::BoxShadow(..) => "box_shadow",
+            SpecificDisplayItem::ClearRectangle => "clear_rectangle",
+            SpecificDisplayItem::Clip(..) => "clip",
+            SpecificDisplayItem::ClipChain(..) => "clip_chain",
+            SpecificDisplayItem::Gradient(..) => "gradient",
+            SpecificDisplayItem::Iframe(..) => "iframe",
+            SpecificDisplayItem::Image(..) => "image",
+            SpecificDisplayItem::Line(..) => "line",
+            SpecificDisplayItem::PopAllShadows => "pop_all_shadows",
+            SpecificDisplayItem::PopReferenceFrame => "pop_reference_frame",
+            SpecificDisplayItem::PopStackingContext => "pop_stacking_context",
+            SpecificDisplayItem::PushShadow(..) => "push_shadow",
+            SpecificDisplayItem::PushReferenceFrame(..) => "push_reference_frame",
+            SpecificDisplayItem::PushStackingContext(..) => "push_stacking_context",
+            SpecificDisplayItem::SetFilterOps => "set_filter_ops",
+            SpecificDisplayItem::SetFilterData => "set_filter_data",
+            SpecificDisplayItem::RadialGradient(..) => "radial_gradient",
+            SpecificDisplayItem::Rectangle(..) => "rectangle",
+            SpecificDisplayItem::ScrollFrame(..) => "scroll_frame",
+            SpecificDisplayItem::SetGradientStops => "set_gradient_stops",
+            SpecificDisplayItem::StickyFrame(..) => "sticky_frame",
+            SpecificDisplayItem::Text(..) => "text",
+            SpecificDisplayItem::YuvImage(..) => "yuv_image",
+        }
+    }
+}
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -8,16 +8,17 @@ use euclid::SideOffsets2D;
 use serde::de::Deserializer;
 #[cfg(feature = "serialize")]
 use serde::ser::{Serializer, SerializeSeq};
 use serde::{Deserialize, Serialize};
 use std::io::{Read, stdout, Write};
 use std::marker::PhantomData;
 use std::ops::Range;
 use std::{io, mem, ptr, slice};
+use std::collections::HashMap;
 use time::precise_time_ns;
 // local imports
 use display_item as di;
 use api::{PipelineId, PropertyBinding};
 use gradient_builder::GradientBuilder;
 use color::ColorF;
 use font::{FontInstanceKey, GlyphInstance, GlyphOptions};
 use image::{ColorDepth, ImageKey};
@@ -63,16 +64,17 @@ impl<T> Default for ItemRange<T> {
 
 impl<T> ItemRange<T> {
     pub fn is_empty(&self) -> bool {
         // Nothing more than space for a length (0).
         self.length <= mem::size_of::<u64>()
     }
 }
 
+#[derive(Copy, Clone)]
 pub struct TempFilterData {
     pub func_types: ItemRange<di::ComponentTransferFuncType>,
     pub r_values: ItemRange<f32>,
     pub g_values: ItemRange<f32>,
     pub b_values: ItemRange<f32>,
     pub a_values: ItemRange<f32>,
 }
 
@@ -109,16 +111,34 @@ pub struct BuiltDisplayListIter<'a> {
     cur_item: di::DisplayItem,
     cur_stops: ItemRange<di::GradientStop>,
     cur_glyphs: ItemRange<GlyphInstance>,
     cur_filters: ItemRange<di::FilterOp>,
     cur_filter_data: Vec<TempFilterData>,
     cur_clip_chain_items: ItemRange<di::ClipId>,
     cur_complex_clip: (ItemRange<di::ComplexClipRegion>, usize),
     peeking: Peek,
+    /// Should just be initialized but never populated in release builds
+    debug_stats: DebugStats,
+}
+
+/// Internal info used for more detailed analysis of serialized display lists
+struct DebugStats {
+    /// Last address in the buffer we pointed to, for computing serialized sizes
+    last_addr: usize,
+    stats: HashMap<&'static str, ItemStats>,
+}
+
+/// Stats for an individual item
+#[derive(Copy, Clone, Debug, Default)]
+pub struct ItemStats {
+    /// How many instances of this kind of item we deserialized
+    pub total_count: usize,
+    /// How many bytes we processed for this kind of item
+    pub num_bytes: usize,
 }
 
 pub struct DisplayItemRef<'a: 'b, 'b> {
     iter: &'b BuiltDisplayListIter<'a>,
 }
 
 #[derive(PartialEq)]
 enum Peek {
@@ -230,16 +250,20 @@ impl<'a> BuiltDisplayListIter<'a> {
             },
             cur_stops: ItemRange::default(),
             cur_glyphs: ItemRange::default(),
             cur_filters: ItemRange::default(),
             cur_filter_data: Vec::new(),
             cur_clip_chain_items: ItemRange::default(),
             cur_complex_clip: (ItemRange::default(), 0),
             peeking: Peek::NotPeeking,
+            debug_stats: DebugStats {
+                last_addr: data.as_ptr() as usize,
+                stats: HashMap::default(),
+            }
         }
     }
 
     pub fn display_list(&self) -> &'a BuiltDisplayList {
         self.list
     }
 
     pub fn next<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
@@ -293,66 +317,80 @@ impl<'a> BuiltDisplayListIter<'a> {
         }
 
         {
             let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
             bincode::deserialize_in_place(reader, &mut self.cur_item)
                 .expect("MEH: malicious process?");
         }
 
+        self.log_item_stats();
+
         match self.cur_item.item {
             SetGradientStops => {
                 self.cur_stops = skip_slice::<di::GradientStop>(self.list, &mut self.data).0;
+                let temp = self.cur_stops;
+                self.log_slice_stats("set_gradient_stops.stops", temp);
             }
             SetFilterOps => {
                 self.cur_filters = skip_slice::<di::FilterOp>(self.list, &mut self.data).0;
+                let temp = self.cur_filters;
+                self.log_slice_stats("set_filter_ops.ops", temp);
             }
             SetFilterData => {
                 self.cur_filter_data.push(TempFilterData {
                     func_types: skip_slice::<di::ComponentTransferFuncType>(self.list, &mut self.data).0,
                     r_values: skip_slice::<f32>(self.list, &mut self.data).0,
                     g_values: skip_slice::<f32>(self.list, &mut self.data).0,
                     b_values: skip_slice::<f32>(self.list, &mut self.data).0,
                     a_values: skip_slice::<f32>(self.list, &mut self.data).0,
                 });
+
+                let data = *self.cur_filter_data.last().unwrap();
+                self.log_slice_stats("set_filter_data.func_types", data.func_types);
+                self.log_slice_stats("set_filter_data.r_values", data.r_values);
+                self.log_slice_stats("set_filter_data.g_values", data.g_values);
+                self.log_slice_stats("set_filter_data.b_values", data.b_values);
+                self.log_slice_stats("set_filter_data.a_values", data.a_values);
             }
             ClipChain(_) => {
                 self.cur_clip_chain_items = skip_slice::<di::ClipId>(self.list, &mut self.data).0;
+                let temp = self.cur_clip_chain_items;
+                self.log_slice_stats("clip_chain.clip_ids", temp);
             }
             Clip(_) | ScrollFrame(_) => {
-                self.cur_complex_clip = self.skip_slice::<di::ComplexClipRegion>()
+                self.cur_complex_clip = self.skip_slice::<di::ComplexClipRegion>();
+
+                let name = if let Clip(_) = self.cur_item.item {
+                    "clip.complex_clips"
+                } else {
+                    "scroll_frame.complex_clips"
+                };
+                let temp = self.cur_complex_clip.0;
+                self.log_slice_stats(name, temp);
             }
-            Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
+            Text(_) => {
+                self.cur_glyphs = self.skip_slice::<GlyphInstance>().0;
+                let temp = self.cur_glyphs;
+                self.log_slice_stats("text.glyphs", temp);
+            }
             _ => { /* do nothing */ }
         }
 
         Some(self.as_ref())
     }
 
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
         skip_slice::<T>(self.list, &mut self.data)
     }
 
     pub fn as_ref<'b>(&'b self) -> DisplayItemRef<'a, 'b> {
         DisplayItemRef { iter: self }
     }
 
-    pub fn starting_stacking_context(
-        &mut self,
-    ) -> Option<(di::StackingContext, LayoutRect, ItemRange<di::FilterOp>)> {
-        self.next().and_then(|item| match *item.item() {
-            di::SpecificDisplayItem::PushStackingContext(ref specific_item) => Some((
-                specific_item.stacking_context,
-                item.rect(),
-                item.filters(),
-            )),
-            _ => None,
-        })
-    }
-
     pub fn skip_current_stacking_context(&mut self) {
         let mut depth = 0;
         while let Some(item) = self.next() {
             match *item.item() {
                 di::SpecificDisplayItem::PushStackingContext(..) => depth += 1,
                 di::SpecificDisplayItem::PopStackingContext if depth == 0 => return,
                 di::SpecificDisplayItem::PopStackingContext => depth -= 1,
                 _ => {}
@@ -371,16 +409,78 @@ impl<'a> BuiltDisplayListIter<'a> {
     pub fn peek<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
         if self.peeking == Peek::NotPeeking {
             self.peeking = Peek::StartPeeking;
             self.next()
         } else {
             Some(self.as_ref())
         }
     }
+
+    /// Get the debug stats for what this iterator has deserialized.
+    /// Should always be empty in release builds.
+    pub fn debug_stats(&mut self) -> Vec<(&'static str, ItemStats)> {
+        let mut result = self.debug_stats.stats.drain().collect::<Vec<_>>();
+        result.sort_by_key(|stats| stats.0);
+        result
+    }
+
+    /// Adds the debug stats from another to our own, assuming we are a sub-iter of the other
+    /// (so we can ignore where they were in the traversal).
+    pub fn merge_debug_stats_from(&mut self, other: &mut Self) {
+        for (key, other_entry) in other.debug_stats.stats.iter() {
+            let entry = self.debug_stats.stats.entry(key).or_default();
+
+            entry.total_count += other_entry.total_count;
+            entry.num_bytes += other_entry.num_bytes;
+        }
+    }
+
+    /// Logs stats for the last deserialized display item
+    #[cfg(feature = "display_list_stats")]
+    fn log_item_stats(&mut self) {
+        let num_bytes = self.debug_num_bytes();
+
+        let item_name = self.cur_item.item.debug_name();
+        let entry = self.debug_stats.stats.entry(item_name).or_default();
+
+        entry.total_count += 1;
+        entry.num_bytes += num_bytes;
+    }
+
+    /// Logs the stats for the given serialized slice
+    #[cfg(feature = "display_list_stats")]
+    fn log_slice_stats<T: for<'de> Deserialize<'de>>(&mut self, slice_name: &'static str, range: ItemRange<T>) {
+        // Run this so log_item_stats is accurate, but ignore its result
+        // because log_slice_stats may be called after multiple slices have been
+        // processed, and the `range` has everything we need.
+        self.debug_num_bytes();
+
+        let entry = self.debug_stats.stats.entry(slice_name).or_default();
+
+        entry.total_count += self.list.get(range).size_hint().0;
+        entry.num_bytes += range.length;
+    }
+
+    /// Computes the number of bytes we've processed since we last called
+    /// this method, so we can compute the serialized size of a display item.
+    #[cfg(feature = "display_list_stats")]
+    fn debug_num_bytes(&mut self) -> usize {
+        let old_addr = self.debug_stats.last_addr;
+        let new_addr = self.data.as_ptr() as usize;
+        let delta = new_addr - old_addr;
+        self.debug_stats.last_addr = new_addr;
+
+        delta
+    }
+
+    #[cfg(not(feature = "display_list_stats"))]
+    fn log_item_stats(&mut self) { /* no-op */ }
+    #[cfg(not(feature = "display_list_stats"))]
+    fn log_slice_stats<T>(&mut self, _slice_name: &str, _range: ItemRange<T>) { /* no-op */ }
 }
 
 // Some of these might just become ItemRanges
 impl<'a, 'b> DisplayItemRef<'a, 'b> {
     pub fn display_item(&self) -> &di::DisplayItem {
         &self.iter.cur_item
     }
 
@@ -551,18 +651,16 @@ impl Serialize for BuiltDisplayList {
                             a_values: item.iter.list.get(temp_filter_data.a_values).collect(),
                         })
                     },
                     di::SpecificDisplayItem::SetGradientStops => SetGradientStops(
                         item.iter.list.get(item.iter.cur_stops).collect()
                     ),
                     di::SpecificDisplayItem::PushShadow(v) => PushShadow(v),
                     di::SpecificDisplayItem::PopAllShadows => PopAllShadows,
-                    di::SpecificDisplayItem::PushCacheMarker(m) => PushCacheMarker(m),
-                    di::SpecificDisplayItem::PopCacheMarker => PopCacheMarker,
                 },
                 layout: display_item.layout,
                 space_and_clip: display_item.space_and_clip,
             };
             seq.serialize_element(&serial_di)?
         }
         seq.end()
     }
@@ -655,18 +753,16 @@ impl<'de> Deserialize<'de> for BuiltDisp
                     },
                     PopStackingContext => di::SpecificDisplayItem::PopStackingContext,
                     SetGradientStops(stops) => {
                         DisplayListBuilder::push_iter_impl(&mut temp, stops);
                         di::SpecificDisplayItem::SetGradientStops
                     },
                     PushShadow(specific_item) => di::SpecificDisplayItem::PushShadow(specific_item),
                     PopAllShadows => di::SpecificDisplayItem::PopAllShadows,
-                    PushCacheMarker(marker) => di::SpecificDisplayItem::PushCacheMarker(marker),
-                    PopCacheMarker => di::SpecificDisplayItem::PopCacheMarker,
                 },
                 layout: complete.layout,
                 space_and_clip: complete.space_and_clip,
             };
             serialize_fast(&mut data, &item);
             // the aux data is serialized after the item, hence the temporary
             data.extend(temp.drain(..));
         }
@@ -1344,47 +1440,37 @@ impl DisplayListBuilder {
         let layout = di::LayoutPrimitiveInfo::new(*rect);
         self.push_item(&item, &layout, &di::SpaceAndClipInfo {
             spatial_id: parent,
             clip_id: di::ClipId::invalid(),
         });
         id
     }
 
-    pub fn push_cache_marker(&mut self) {
-        self.push_new_empty_item(&di::SpecificDisplayItem::PushCacheMarker(di::CacheMarkerDisplayItem {
-            // The display item itself is empty for now while we experiment with
-            // the API. In future it may contain extra information, such as details
-            // on whether the surface is known to be opaque and/or a background color
-            // hint that WR should clear the surface to.
-        }));
-    }
-
-    pub fn pop_cache_marker(&mut self) {
-        self.push_new_empty_item(&di::SpecificDisplayItem::PopCacheMarker);
-    }
-
     pub fn pop_reference_frame(&mut self) {
         self.push_new_empty_item(&di::SpecificDisplayItem::PopReferenceFrame);
     }
 
     pub fn push_stacking_context(
         &mut self,
         layout: &di::LayoutPrimitiveInfo,
         spatial_id: di::SpatialId,
         clip_id: Option<di::ClipId>,
         transform_style: di::TransformStyle,
         mix_blend_mode: di::MixBlendMode,
         filters: &[di::FilterOp],
         filter_datas: &[di::FilterData],
         raster_space: di::RasterSpace,
         cache_tiles: bool,
     ) {
-        self.push_new_empty_item(&di::SpecificDisplayItem::SetFilterOps);
-        self.push_iter(filters);
+
+        if filters.len() > 0 {
+            self.push_new_empty_item(&di::SpecificDisplayItem::SetFilterOps);
+            self.push_iter(filters);
+        }
 
         for filter_data in filter_datas {
             let func_types = [
                 filter_data.func_r_type, filter_data.func_g_type,
                 filter_data.func_b_type, filter_data.func_a_type];
             self.push_new_empty_item(&di::SpecificDisplayItem::SetFilterData);
             self.push_iter(&func_types);
             self.push_iter(&filter_data.r_values);
--- a/gfx/wr/webrender_api/src/font.rs
+++ b/gfx/wr/webrender_api/src/font.rs
@@ -91,17 +91,17 @@ impl FontKey {
 /// assigning size and various other options. The word 'template' here is
 /// intended to distinguish this data from instance-specific data.
 #[derive(Clone)]
 pub enum FontTemplate {
     Raw(Arc<Vec<u8>>, u32),
     Native(NativeFontHandle),
 }
 
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono = 0,
     Alpha,
     Subpixel,
 }
 
 impl FontRenderMode {
--- a/gfx/wr/webrender_api/src/image.rs
+++ b/gfx/wr/webrender_api/src/image.rs
@@ -69,17 +69,17 @@ pub enum TextureTarget {
     /// External texture. This maps to GL_TEXTURE_EXTERNAL_OES in OpenGL, which
     /// is an extension. This is used for image formats that OpenGL doesn't
     /// understand, particularly YUV. See
     /// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external.txt
     External = 3,
 }
 
 /// Storage format identifier for externally-managed images.
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
 pub enum ExternalImageType {
     /// The image is texture-backed.
     TextureHandle(TextureTarget),
     /// The image is heap-allocated by the embedding.
     Buffer,
 }
 
@@ -92,17 +92,17 @@ pub struct ExternalImageData {
     /// For multi-plane images (i.e. YUV), indicates the plane of the
     /// original image that this struct represents. 0 for single-plane images.
     pub channel_index: u8,
     /// Storage format identifier.
     pub image_type: ExternalImageType,
 }
 
 /// Specifies the format of a series of pixels, in driver terms.
-#[repr(u32)]
+#[repr(u8)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageFormat {
     /// One-channel, byte storage. The "red" doesn't map to the color
     /// red per se, and is just the way that OpenGL has historically referred
     /// to single-channel buffers.
     R8 = 1,
     /// One-channel, short storage
     R16 = 2,