From cc79aa25c0c1578bb6f57315e7d92491aaece360 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Tue, 18 Sep 2018 16:59:43 +1000 Subject: [PATCH 1/3] Introduce structure interner, and use for clip interning. This patch introduces a new data structure, that allows generic structures to be interned. This works similarly to how normal string interning works, however it is specialized to the thread model for WR (explained at the top of intern.rs). The effect of this change is that clip nodes are de-duplicated and persisted between both frames and display lists. This has three primary benefits: * Since they are de-duplicated, the handle for an interned structure uniquely identifies it. This is very useful for future use where we want to be able to quickly and cheaply compare if the contents of a cached picture matches that of a new display list. * Since they are persisted between display lists, the GPU cache handles for the nodes remain valid. This means far fewer GPU cache update patches for types that are interned. * Since they are de-duplicated by content hash value, there are fewer clips overall used by the frame builder. The plan in the future is to extend this to other primitive types, as well as gradient stops, text runs etc. This will allow us to very quickly check if a cached picture remains valid, even in the presence of a completely new display list. This adds a small amount of overhead to the scene builder thread, (extra hashing) but reduces the CPU time in the render backend and compositor threads, which is also a good tradeoff. --- webrender/src/batch.rs | 10 +- webrender/src/border.rs | 38 ++- webrender/src/box_shadow.rs | 20 +- webrender/src/clip.rs | 330 ++++++++++++++---------- webrender/src/display_list_flattener.rs | 60 +++-- webrender/src/frame_builder.rs | 17 +- webrender/src/hit_test.rs | 85 +++--- webrender/src/intern.rs | 288 +++++++++++++++++++++ webrender/src/lib.rs | 1 + webrender/src/prim_store.rs | 11 +- webrender/src/render_backend.rs | 38 ++- webrender/src/render_task.rs | 6 +- webrender/src/scene_builder.rs | 43 ++- webrender/src/tiling.rs | 4 +- webrender_api/src/display_item.rs | 6 +- webrender_api/src/units.rs | 85 ++++++ 16 files changed, 817 insertions(+), 225 deletions(-) create mode 100644 webrender/src/intern.rs diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 4864d44042..8521cd4323 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -5,7 +5,7 @@ use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize}; use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering}; use api::{YuvColorSpace, YuvFormat, WorldPixel, WorldRect}; -use clip::{ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore}; +use clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItem, ClipStore}; use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; use euclid::vec3; use glyph_rasterizer::GlyphFormat; @@ -1775,12 +1775,14 @@ impl ClipBatcher { clip_store: &ClipStore, clip_scroll_tree: &ClipScrollTree, transforms: &mut TransformPalette, + clip_data_store: &ClipDataStore, ) { for i in 0 .. clip_node_range.count { - let (clip_node, flags, spatial_node_index) = clip_store.get_node_from_range(&clip_node_range, i); + let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); + let clip_node = clip_data_store.get(&clip_instance.handle); let clip_transform_id = transforms.get_id( - spatial_node_index, + clip_instance.spatial_node_index, ROOT_SPATIAL_NODE_INDEX, clip_scroll_tree, ); @@ -1852,7 +1854,7 @@ impl ClipBatcher { }); } ClipItem::Rectangle(_, mode) => { - if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) || + if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) || mode == ClipMode::ClipOut { self.rectangles.push(ClipMaskInstance { clip_data_address: gpu_address, diff --git a/webrender/src/border.rs b/webrender/src/border.rs index c47ad66105..dc282c1f3c 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -3,8 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize}; -use api::{LayoutSizeAu, LayoutSideOffsets, LayoutPrimitiveInfo, LayoutToDeviceScale}; +use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale}; use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder}; +use api::{AuSizeHelpers}; use app_units::Au; use ellipse::Ellipse; use display_list_flattener::DisplayListFlattener; @@ -26,19 +27,6 @@ pub const MAX_BORDER_RESOLUTION: u32 = 2048; /// a list of per-dot information in the first place. pub const MAX_DASH_COUNT: u32 = 2048; -trait AuSizeConverter { - fn to_au(&self) -> LayoutSizeAu; -} - -impl AuSizeConverter for LayoutSize { - fn to_au(&self) -> LayoutSizeAu { - LayoutSizeAu::new( - Au::from_f32_px(self.width), - Au::from_f32_px(self.height), - ) - } -} - // TODO(gw): Perhaps there is a better way to store // the border cache key than duplicating // all the border structs with hashable @@ -54,6 +42,17 @@ pub struct BorderRadiusAu { pub bottom_right: LayoutSizeAu, } +impl BorderRadiusAu { + pub fn zero() -> Self { + BorderRadiusAu { + top_left: LayoutSizeAu::zero(), + top_right: LayoutSizeAu::zero(), + bottom_left: LayoutSizeAu::zero(), + bottom_right: LayoutSizeAu::zero(), + } + } +} + impl From for BorderRadiusAu { fn from(radius: BorderRadius) -> BorderRadiusAu { BorderRadiusAu { @@ -65,6 +64,17 @@ impl From for BorderRadiusAu { } } +impl From for BorderRadius { + fn from(radius: BorderRadiusAu) -> Self { + BorderRadius { + top_left: LayoutSize::from_au(radius.top_left), + top_right: LayoutSize::from_au(radius.top_right), + bottom_right: LayoutSize::from_au(radius.bottom_right), + bottom_left: LayoutSize::from_au(radius.bottom_left), + } + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] diff --git a/webrender/src/box_shadow.rs b/webrender/src/box_shadow.rs index 1edcfd34da..1e5d40fc52 100644 --- a/webrender/src/box_shadow.rs +++ b/webrender/src/box_shadow.rs @@ -4,7 +4,7 @@ use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, DeviceIntSize, LayoutPrimitiveInfo}; use api::{LayoutRect, LayoutSize, LayoutVector2D}; -use clip::ClipItem; +use clip::ClipItemKey; use display_list_flattener::DisplayListFlattener; use gpu_cache::GpuCacheHandle; use gpu_types::BoxShadowStretchMode; @@ -13,7 +13,9 @@ use prim_store::ScrollNodeAndClipChain; use render_task::RenderTaskCacheEntryHandle; use util::RectHelpers; -#[derive(Debug)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct BoxShadowClipSource { // Parameters that define the shadow and are constant. pub shadow_radius: BorderRadius, @@ -122,7 +124,7 @@ impl<'a> DisplayListFlattener<'a> { } // TODO(gw): Add a fast path for ClipOut + zero border radius! - clips.push(ClipItem::new_rounded_rect( + clips.push(ClipItemKey::rounded_rect( prim_info.rect, border_radius, ClipMode::ClipOut @@ -132,7 +134,7 @@ impl<'a> DisplayListFlattener<'a> { } BoxShadowClipMode::Inset => { if shadow_rect.is_well_formed_and_nonempty() { - clips.push(ClipItem::new_rounded_rect( + clips.push(ClipItemKey::rounded_rect( shadow_rect, shadow_radius, ClipMode::ClipOut @@ -143,7 +145,11 @@ impl<'a> DisplayListFlattener<'a> { } }; - clips.push(ClipItem::new_rounded_rect(final_prim_rect, clip_radius, ClipMode::Clip)); + clips.push(ClipItemKey::rounded_rect( + final_prim_rect, + clip_radius, + ClipMode::Clip, + )); self.add_primitive( clip_and_scroll, @@ -163,7 +169,7 @@ impl<'a> DisplayListFlattener<'a> { // Add a normal clip mask to clip out the contents // of the surrounding primitive. - extra_clips.push(ClipItem::new_rounded_rect( + extra_clips.push(ClipItemKey::rounded_rect( prim_info.rect, border_radius, prim_clip_mode, @@ -181,7 +187,7 @@ impl<'a> DisplayListFlattener<'a> { ); // Create the box-shadow clip item. - let shadow_clip_source = ClipItem::new_box_shadow( + let shadow_clip_source = ClipItemKey::box_shadow( shadow_rect, shadow_radius, dest_rect, diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index 2d5c85e5a2..e1abe9a35a 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -6,13 +6,16 @@ use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelS use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip}; use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel}; use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform}; -use api::{VoidPtrToSizeFn}; -use border::{ensure_no_corner_overlap}; +use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey}; +use api::{AuRectHelpers, AuVectorHelpers}; +use app_units::Au; +use border::{ensure_no_corner_overlap, BorderRadiusAu}; use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey}; use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; use ellipse::Ellipse; use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks}; use gpu_types::{BoxShadowStretchMode}; +use intern; use internal_types::FastHashSet; use prim_store::{ClipData, ImageMaskData, SpaceMapper}; use render_task::to_cache_size; @@ -90,6 +93,17 @@ use util::{extract_inner_rect_safe, pack_as_float, project_rect, ScaleOffset}; */ +// Type definitions for interning clip nodes. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug)] +pub struct ClipDataMarker; + +pub type ClipDataStore = intern::DataStore; +pub type ClipDataHandle = intern::Handle; +pub type ClipDataUpdateList = intern::UpdateList; +pub type ClipDataInterner = intern::Interner; + // Result of comparing a clip node instance against a local rect. #[derive(Debug)] enum ClipResult { @@ -107,13 +121,67 @@ enum ClipResult { // that control where the GPU data for this clip source // can be found. #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ClipNode { pub item: ClipItem, pub gpu_cache_handle: GpuCacheHandle, } +// Convert from an interning key for a clip item +// to a clip node, which is cached in the document. +// TODO(gw): These enums are a bit messy - we should +// convert them to use named fields. +impl From for ClipNode { + fn from(item: ClipItemKey) -> Self { + let item = match item { + ClipItemKey::Rectangle(rect, mode) => { + ClipItem::Rectangle(LayoutRect::from_au(rect), mode) + } + ClipItemKey::RoundedRectangle(rect, radius, mode) => { + ClipItem::RoundedRectangle( + LayoutRect::from_au(rect), + radius.into(), + mode, + ) + } + ClipItemKey::LineDecoration(rect, style, orientation, wavy_line_thickness) => { + ClipItem::LineDecoration(LineDecorationClipSource { + rect: LayoutRect::from_au(rect), + style, + orientation, + wavy_line_thickness: wavy_line_thickness.to_f32_px(), + }) + } + ClipItemKey::ImageMask(rect, image, repeat) => { + ClipItem::Image(ImageMask { + image, + rect: LayoutRect::from_au(rect), + repeat, + }) + } + ClipItemKey::BoxShadow(shadow_rect, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => { + ClipItem::new_box_shadow( + LayoutRect::from_au(shadow_rect), + shadow_radius.into(), + LayoutRect::from_au(prim_shadow_rect), + blur_radius.to_f32_px(), + clip_mode, + ) + } + }; + + ClipNode { + item, + gpu_cache_handle: GpuCacheHandle::new(), + } + } +} + // Flags that are attached to instances of clip nodes. bitflags! { + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ClipNodeFlags: u8 { const SAME_SPATIAL_NODE = 0x1; const SAME_COORD_SYSTEM = 0x2; @@ -138,7 +206,7 @@ impl ClipChainId { // and a link to a parent clip chain node, or ClipChainId::NONE. #[derive(Clone)] pub struct ClipChainNode { - pub clip_node_index: ClipNodeIndex, + pub handle: ClipDataHandle, pub spatial_node_index: SpatialNodeIndex, pub parent_clip_chain_id: ClipChainId, } @@ -155,33 +223,13 @@ pub struct ClipNodeIndex(pub u32); // an index to the node data itself, as well as // some flags describing how this clip node instance // is positioned. -#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] +#[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ClipNodeInstance { - index_and_flags: u32, - spatial_node_index: u32, -} - -impl ClipNodeInstance { - fn new( - index: ClipNodeIndex, - flags: ClipNodeFlags, - spatial_node_index: SpatialNodeIndex, - ) -> ClipNodeInstance { - ClipNodeInstance { - index_and_flags: (index.0 & 0x00ffffff) | ((flags.bits() as u32) << 24), - spatial_node_index: spatial_node_index.0 as u32, - } - } - - fn flags(&self) -> ClipNodeFlags { - ClipNodeFlags::from_bits_truncate((self.index_and_flags >> 24) as u8) - } - - fn index(&self) -> usize { - (self.index_and_flags & 0x00ffffff) as usize - } + pub handle: ClipDataHandle, + pub flags: ClipNodeFlags, + pub spatial_node_index: SpatialNodeIndex, } // A range of clip node instances that were found by @@ -210,7 +258,7 @@ enum ClipSpaceConversion { // during building of a clip chain instance. struct ClipNodeInfo { conversion: ClipSpaceConversion, - node_index: ClipNodeIndex, + handle: ClipDataHandle, spatial_node_index: SpatialNodeIndex, has_non_root_coord_system: bool, } @@ -314,9 +362,8 @@ impl ClipNode { // The main clipping public interface that other modules access. pub struct ClipStore { - pub clip_nodes: Vec, pub clip_chain_nodes: Vec, - clip_node_indices: Vec, + clip_node_instances: Vec, clip_node_info: Vec, clip_node_collectors: Vec, } @@ -342,9 +389,8 @@ pub struct ClipChainInstance { impl ClipStore { pub fn new() -> Self { ClipStore { - clip_nodes: Vec::new(), clip_chain_nodes: Vec::new(), - clip_node_indices: Vec::new(), + clip_node_instances: Vec::new(), clip_node_info: Vec::new(), clip_node_collectors: Vec::new(), } @@ -354,60 +400,27 @@ impl ClipStore { &self.clip_chain_nodes[clip_chain_id.0 as usize] } - pub fn add_clip_chain_node_index( + pub fn add_clip_chain_node( &mut self, - clip_node_index: ClipNodeIndex, + handle: ClipDataHandle, spatial_node_index: SpatialNodeIndex, parent_clip_chain_id: ClipChainId, ) -> ClipChainId { let id = ClipChainId(self.clip_chain_nodes.len() as u32); self.clip_chain_nodes.push(ClipChainNode { - clip_node_index, + handle, spatial_node_index, parent_clip_chain_id, }); id } - pub fn add_clip_chain_node( - &mut self, - item: ClipItem, - spatial_node_index: SpatialNodeIndex, - parent_clip_chain_id: ClipChainId, - ) -> ClipChainId { - let clip_node_index = ClipNodeIndex(self.clip_nodes.len() as u32); - self.clip_nodes.push(ClipNode { - item, - gpu_cache_handle: GpuCacheHandle::new(), - }); - - self.add_clip_chain_node_index( - clip_node_index, - spatial_node_index, - parent_clip_chain_id, - ) - } - - pub fn get_node_from_range( + pub fn get_instance_from_range( &self, node_range: &ClipNodeRange, index: u32, - ) -> (&ClipNode, ClipNodeFlags, SpatialNodeIndex) { - let instance = self.clip_node_indices[(node_range.first + index) as usize]; - ( - &self.clip_nodes[instance.index()], - instance.flags(), - SpatialNodeIndex(instance.spatial_node_index as usize), - ) - } - - pub fn get_node_from_range_mut( - &mut self, - node_range: &ClipNodeRange, - index: u32, - ) -> (&mut ClipNode, ClipNodeFlags) { - let instance = self.clip_node_indices[(node_range.first + index) as usize]; - (&mut self.clip_nodes[instance.index()], instance.flags()) + ) -> &ClipNodeInstance { + &self.clip_node_instances[(node_range.first + index) as usize] } // Notify the clip store that a new rasterization root has been created. @@ -445,6 +458,7 @@ impl ClipStore { device_pixel_scale: DevicePixelScale, world_rect: &WorldRect, clip_node_collector: &Option, + clip_data_store: &mut ClipDataStore, ) -> Option { let mut local_clip_rect = local_prim_clip_rect; @@ -468,12 +482,12 @@ impl ClipStore { } None => { if !add_clip_node_to_current_chain( - clip_chain_node.clip_node_index, + clip_chain_node.handle, clip_chain_node.spatial_node_index, spatial_node_index, &mut local_clip_rect, &mut self.clip_node_info, - &self.clip_nodes, + clip_data_store, clip_scroll_tree, ) { return None; @@ -488,18 +502,18 @@ impl ClipStore { // handled as part of this rasterization root. if let Some(clip_node_collector) = clip_node_collector { for clip_chain_id in &clip_node_collector.clips { - let (clip_node_index, clip_spatial_node_index) = { + let (handle, clip_spatial_node_index) = { let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize]; - (clip_chain_node.clip_node_index, clip_chain_node.spatial_node_index) + (clip_chain_node.handle, clip_chain_node.spatial_node_index) }; if !add_clip_node_to_current_chain( - clip_node_index, + handle, clip_spatial_node_index, spatial_node_index, &mut local_clip_rect, &mut self.clip_node_info, - &self.clip_nodes, + clip_data_store, clip_scroll_tree, ) { return None; @@ -516,14 +530,14 @@ impl ClipStore { // Run through the clip nodes, and see which ones affect this prim region. - let first_clip_node_index = self.clip_node_indices.len() as u32; + let first_clip_node_index = self.clip_node_instances.len() as u32; let mut has_non_root_coord_system = false; let mut has_non_local_clips = false; let mut needs_mask = false; // For each potential clip node for node_info in self.clip_node_info.drain(..) { - let node = &mut self.clip_nodes[node_info.node_index.0 as usize]; + let node = clip_data_store.get_mut(&node_info.handle); // See how this clip affects the prim region. let clip_result = match node_info.conversion { @@ -597,12 +611,12 @@ impl ClipStore { }; // Store this in the index buffer for this clip chain instance. - let instance = ClipNodeInstance::new( - node_info.node_index, + let instance = ClipNodeInstance { + handle: node_info.handle, flags, - node_info.spatial_node_index, - ); - self.clip_node_indices.push(instance); + spatial_node_index: node_info.spatial_node_index, + }; + self.clip_node_instances.push(instance); has_non_root_coord_system |= node_info.has_non_root_coord_system; } @@ -612,7 +626,7 @@ impl ClipStore { // Get the range identifying the clip nodes in the index buffer. let clips_range = ClipNodeRange { first: first_clip_node_index, - count: self.clip_node_indices.len() as u32 - first_clip_node_index, + count: self.clip_node_instances.len() as u32 - first_clip_node_index, }; // Return a valid clip chain instance @@ -630,16 +644,17 @@ impl ClipStore { pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize { let mut size = 0; unsafe { - size += op(self.clip_nodes.as_ptr() as *const c_void); size += op(self.clip_chain_nodes.as_ptr() as *const c_void); - size += op(self.clip_node_indices.as_ptr() as *const c_void); + size += op(self.clip_node_instances.as_ptr() as *const c_void); size += op(self.clip_node_info.as_ptr() as *const c_void); } size } } -#[derive(Debug)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct LineDecorationClipSource { rect: LayoutRect, style: LineStyle, @@ -721,49 +736,112 @@ impl ClipRegion> { } } -#[derive(Debug)] -pub enum ClipItem { - Rectangle(LayoutRect, ClipMode), - RoundedRectangle(LayoutRect, BorderRadius, ClipMode), - Image(ImageMask), - BoxShadow(BoxShadowClipSource), - LineDecoration(LineDecorationClipSource), +// The ClipItemKey is a hashable representation of the contents +// of a clip item. It is used during interning to de-duplicate +// clip nodes between frames and display lists. This allows quick +// comparison of clip node equality by handle, and also allows +// the uploaded GPU cache handle to be retained between display lists. +// TODO(gw): Maybe we should consider constructing these directly +// in the DL builder? +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ClipItemKey { + Rectangle(LayoutRectAu, ClipMode), + RoundedRectangle(LayoutRectAu, BorderRadiusAu, ClipMode), + ImageMask(LayoutRectAu, ImageKey, bool), + BoxShadow(LayoutRectAu, BorderRadiusAu, LayoutRectAu, Au, BoxShadowClipMode), + LineDecoration(LayoutRectAu, LineStyle, LineOrientation, Au), } -impl ClipItem { - pub fn new_rounded_rect( - rect: LayoutRect, - mut radii: BorderRadius, - clip_mode: ClipMode - ) -> Self { +impl ClipItemKey { + pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self { + ClipItemKey::Rectangle(rect.to_au(), mode) + } + + pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self { if radii.is_zero() { - ClipItem::Rectangle(rect, clip_mode) + ClipItemKey::rectangle(rect, mode) } else { ensure_no_corner_overlap(&mut radii, &rect); - ClipItem::RoundedRectangle( - rect, - radii, - clip_mode, + ClipItemKey::RoundedRectangle( + rect.to_au(), + radii.into(), + mode, ) } } - pub fn new_line_decoration( + pub fn image_mask(image_mask: &ImageMask) -> Self { + ClipItemKey::ImageMask( + image_mask.rect.to_au(), + image_mask.image, + image_mask.repeat, + ) + } + + pub fn line_decoration( rect: LayoutRect, style: LineStyle, orientation: LineOrientation, wavy_line_thickness: f32, ) -> Self { - ClipItem::LineDecoration( - LineDecorationClipSource { - rect, - style, - orientation, - wavy_line_thickness, - } + ClipItemKey::LineDecoration( + rect.to_au(), + style, + orientation, + Au::from_f32_px(wavy_line_thickness), ) } + pub fn box_shadow( + shadow_rect: LayoutRect, + shadow_radius: BorderRadius, + prim_shadow_rect: LayoutRect, + blur_radius: f32, + clip_mode: BoxShadowClipMode, + ) -> Self { + ClipItemKey::BoxShadow( + shadow_rect.to_au(), + shadow_radius.into(), + prim_shadow_rect.to_au(), + Au::from_f32_px(blur_radius), + clip_mode, + ) + } + + // Return a modified clip source that is the same as self + // but offset in local-space by a specified amount. + pub fn offset(&self, offset: &LayoutVector2D) -> Self { + let offset = offset.to_au(); + match *self { + ClipItemKey::LineDecoration(rect, style, orientation, wavy_line_thickness) => { + ClipItemKey::LineDecoration( + rect.translate(&offset), + style, + orientation, + wavy_line_thickness, + ) + } + _ => { + panic!("bug: other clip sources not expected here yet"); + } + } + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ClipItem { + Rectangle(LayoutRect, ClipMode), + RoundedRectangle(LayoutRect, BorderRadius, ClipMode), + Image(ImageMask), + BoxShadow(BoxShadowClipSource), + LineDecoration(LineDecorationClipSource), +} + +impl ClipItem { pub fn new_box_shadow( shadow_rect: LayoutRect, mut shadow_radius: BorderRadius, @@ -862,22 +940,6 @@ impl ClipItem { }) } - // Return a modified clip source that is the same as self - // but offset in local-space by a specified amount. - pub fn offset(&self, offset: &LayoutVector2D) -> Self { - match *self { - ClipItem::LineDecoration(ref info) => { - ClipItem::LineDecoration(LineDecorationClipSource { - rect: info.rect.translate(offset), - ..*info - }) - } - _ => { - panic!("bug: other clip sources not expected here yet"); - } - } - } - // Get an optional clip rect that a clip source can provide to // reduce the size of a primitive region. This is typically // used to eliminate redundant clips, and reduce the size of @@ -1151,15 +1213,15 @@ impl ClipNodeCollector { // for the current clip chain. Returns false if the clip // results in the entire primitive being culled out. fn add_clip_node_to_current_chain( - clip_node_index: ClipNodeIndex, + handle: ClipDataHandle, clip_spatial_node_index: SpatialNodeIndex, spatial_node_index: SpatialNodeIndex, local_clip_rect: &mut LayoutRect, clip_node_info: &mut Vec, - clip_nodes: &[ClipNode], + clip_data_store: &ClipDataStore, clip_scroll_tree: &ClipScrollTree, ) -> bool { - let clip_node = &clip_nodes[clip_node_index.0 as usize]; + let clip_node = clip_data_store.get(&handle); let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_spatial_node_index.0]; let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0]; @@ -1218,7 +1280,7 @@ fn add_clip_node_to_current_chain( } clip_node_info.push(ClipNodeInfo { conversion, - node_index: clip_node_index, + handle, spatial_node_index: clip_spatial_node_index, has_non_root_coord_system: clip_spatial_node.coordinate_system_id != CoordinateSystemId::root(), }) diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 8ae90c61de..f8b9ffa6d9 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -13,7 +13,7 @@ use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, Pipeline use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity}; use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect}; use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData}; -use clip::{ClipChainId, ClipRegion, ClipItem, ClipStore}; +use clip::{ClipDataInterner, ClipChainId, ClipRegion, ClipItemKey, ClipStore}; use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex}; use euclid::vec2; use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig}; @@ -159,6 +159,9 @@ pub struct DisplayListFlattener<'a> { /// The configuration to use for the FrameBuilder. We consult this in /// order to determine the default font. pub config: FrameBuilderConfig, + + /// Reference to the clip interner for this document. + clip_interner: &'a mut ClipDataInterner, } impl<'a> DisplayListFlattener<'a> { @@ -172,6 +175,7 @@ impl<'a> DisplayListFlattener<'a> { new_scene: &mut Scene, scene_id: u64, picture_id_generator: &mut PictureIdGenerator, + clip_interner: &mut ClipDataInterner, ) -> FrameBuilder { // We checked that the root pipeline is available on the render backend. let root_pipeline_id = scene.root_pipeline_id.unwrap(); @@ -196,6 +200,7 @@ impl<'a> DisplayListFlattener<'a> { prim_store: PrimitiveStore::new(), clip_store: ClipStore::new(), picture_id_generator, + clip_interner, }; flattener.push_root( @@ -724,22 +729,22 @@ impl<'a> DisplayListFlattener<'a> { for _ in 0 .. item_clip_node.count { // Get the id of the clip sources entry for that clip chain node. - let (clip_node_index, spatial_node_index) = { + let (handle, spatial_node_index) = { let clip_chain = self .clip_store .get_clip_chain(clip_node_clip_chain_id); clip_node_clip_chain_id = clip_chain.parent_clip_chain_id; - (clip_chain.clip_node_index, clip_chain.spatial_node_index) + (clip_chain.handle, clip_chain.spatial_node_index) }; // Add a new clip chain node, which references the same clip sources, and // parent it to the current parent. clip_chain_id = self .clip_store - .add_clip_chain_node_index( - clip_node_index, + .add_clip_chain_node( + handle, spatial_node_index, clip_chain_id, ); @@ -791,7 +796,7 @@ impl<'a> DisplayListFlattener<'a> { // just return the parent clip chain id directly. fn build_clip_chain( &mut self, - clip_items: Vec, + clip_items: Vec, spatial_node_index: SpatialNodeIndex, parent_clip_chain_id: ClipChainId, ) -> ClipChainId { @@ -801,9 +806,13 @@ impl<'a> DisplayListFlattener<'a> { let mut clip_chain_id = parent_clip_chain_id; for item in clip_items { + // Intern this clip item, and store the handle + // in the clip chain node. + let handle = self.clip_interner.intern(&item); + clip_chain_id = self.clip_store .add_clip_chain_node( - item, + handle, spatial_node_index, clip_chain_id, ); @@ -876,7 +885,7 @@ impl<'a> DisplayListFlattener<'a> { &mut self, clip_and_scroll: ScrollNodeAndClipChain, info: &LayoutPrimitiveInfo, - clip_items: Vec, + clip_items: Vec, container: PrimitiveContainer, ) { if !self.shadow_stack.is_empty() { @@ -890,7 +899,7 @@ impl<'a> DisplayListFlattener<'a> { info.clip_rect = info.clip_rect.translate(&shadow.offset); // Offset any local clip sources by the shadow offset. - let clip_items: Vec = clip_items + let clip_items: Vec = clip_items .iter() .map(|cs| cs.offset(&shadow.offset)) .collect(); @@ -1297,21 +1306,34 @@ impl<'a> DisplayListFlattener<'a> { let mut clip_count = 0; + // Intern each clip item in this clip node, and add the interned + // handle to a clip chain node, parented to form a chain. + // TODO(gw): We could re-structure this to share some of the + // interning and chaining code. + // Build the clip sources from the supplied region. + let handle = self + .clip_interner + .intern(&ClipItemKey::rectangle(clip_region.main, ClipMode::Clip)); + parent_clip_chain_index = self .clip_store .add_clip_chain_node( - ClipItem::Rectangle(clip_region.main, ClipMode::Clip), + handle, spatial_node, parent_clip_chain_index, ); clip_count += 1; - if let Some(image_mask) = clip_region.image_mask { + if let Some(ref image_mask) = clip_region.image_mask { + let handle = self + .clip_interner + .intern(&ClipItemKey::image_mask(image_mask)); + parent_clip_chain_index = self .clip_store .add_clip_chain_node( - ClipItem::Image(image_mask), + handle, spatial_node, parent_clip_chain_index, ); @@ -1319,16 +1341,14 @@ impl<'a> DisplayListFlattener<'a> { } for region in clip_region.complex_clips { - let clip_item = ClipItem::new_rounded_rect( - region.rect, - region.radii, - region.mode, - ); + let handle = self + .clip_interner + .intern(&ClipItemKey::rounded_rect(region.rect, region.radii, region.mode)); parent_clip_chain_index = self .clip_store .add_clip_chain_node( - clip_item, + handle, spatial_node, parent_clip_chain_index, ); @@ -1432,7 +1452,7 @@ impl<'a> DisplayListFlattener<'a> { info: &LayoutPrimitiveInfo, color: ColorF, segments: Option, - extra_clips: Vec, + extra_clips: Vec, ) { if color.a == 0.0 { // Don't add transparent rectangles to the draw list, but do consider them for hit @@ -1528,7 +1548,7 @@ impl<'a> DisplayListFlattener<'a> { LineStyle::Dotted | LineStyle::Dashed => { vec![ - ClipItem::new_line_decoration( + ClipItemKey::line_decoration( info.rect, style, orientation, diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 7f841e2c91..65d4019971 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -5,7 +5,7 @@ use api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel}; use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode, PictureRect}; use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, WorldPoint, WorldRect, WorldPixel}; -use clip::ClipStore; +use clip::{ClipDataStore, ClipStore}; use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; use display_list_flattener::{DisplayListFlattener}; use gpu_cache::GpuCache; @@ -83,6 +83,7 @@ pub struct FrameBuildingState<'a> { pub gpu_cache: &'a mut GpuCache, pub special_render_passes: &'a mut SpecialRenderPasses, pub transforms: &'a mut TransformPalette, + pub clip_data_store: &'a mut ClipDataStore, } pub struct PictureContext { @@ -182,6 +183,7 @@ impl FrameBuilder { device_pixel_scale: DevicePixelScale, scene_properties: &SceneProperties, transform_palette: &mut TransformPalette, + clip_data_store: &mut ClipDataStore, ) -> Option { profile_scope!("cull"); @@ -219,6 +221,7 @@ impl FrameBuilder { gpu_cache, special_render_passes, transforms: transform_palette, + clip_data_store, }; let prim_context = PrimitiveContext::new( @@ -323,6 +326,7 @@ impl FrameBuilder { texture_cache_profile: &mut TextureCacheProfileCounters, gpu_cache_profile: &mut GpuCacheProfileCounters, scene_properties: &SceneProperties, + clip_data_store: &mut ClipDataStore, ) -> Frame { profile_scope!("build"); debug_assert!( @@ -361,6 +365,7 @@ impl FrameBuilder { device_pixel_scale, scene_properties, &mut transform_palette, + clip_data_store, ); resource_cache.block_until_all_resources_added(gpu_cache, @@ -404,6 +409,7 @@ impl FrameBuilder { resource_cache, use_dual_source_blending, clip_scroll_tree, + clip_data_store, }; pass.build( @@ -445,11 +451,16 @@ impl FrameBuilder { } } - pub fn create_hit_tester(&mut self, clip_scroll_tree: &ClipScrollTree) -> HitTester { + pub fn create_hit_tester( + &mut self, + clip_scroll_tree: &ClipScrollTree, + clip_data_store: &ClipDataStore, + ) -> HitTester { HitTester::new( &self.hit_testing_runs, clip_scroll_tree, - &self.clip_store + &self.clip_store, + clip_data_store, ) } } diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs index 8428bf35df..cbe78e12a0 100644 --- a/webrender/src/hit_test.rs +++ b/webrender/src/hit_test.rs @@ -4,12 +4,13 @@ use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint}; use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, VoidPtrToSizeFn, WorldPoint}; -use clip::{ClipNodeIndex, ClipChainNode, ClipNode, ClipItem, ClipStore}; -use clip::{ClipChainId, rounded_rectangle_contains_point}; +use clip::{ClipDataStore, ClipNode, ClipItem, ClipStore}; +use clip::{rounded_rectangle_contains_point}; use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree}; use internal_types::FastHashMap; use prim_store::ScrollNodeAndClipChain; use std::os::raw::c_void; +use std::u32; use util::LayoutToWorldFastTransform; /// A copy of important clip scroll node data to use during hit testing. This a copy of @@ -49,6 +50,26 @@ impl HitTestClipNode { } } +// A hit testing clip chain node is the same as a +// normal clip chain node, except that the clip +// node is embedded inside the clip chain, rather +// than referenced. This means we don't need to +// copy the complete interned clip data store for +// hit testing. + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct HitTestClipChainId(u32); + +impl HitTestClipChainId { + pub const NONE: Self = HitTestClipChainId(u32::MAX); +} + +pub struct HitTestClipChainNode { + pub region: HitTestClipNode, + pub spatial_node_index: SpatialNodeIndex, + pub parent_clip_chain_id: HitTestClipChainId, +} + #[derive(Clone)] pub struct HitTestingItem { rect: LayoutRect, @@ -96,8 +117,7 @@ impl HitTestRegion { pub struct HitTester { runs: Vec, spatial_nodes: Vec, - clip_nodes: Vec, - clip_chains: Vec, + clip_chains: Vec, pipeline_root_nodes: FastHashMap, } @@ -105,27 +125,31 @@ impl HitTester { pub fn new( runs: &Vec, clip_scroll_tree: &ClipScrollTree, - clip_store: &ClipStore + clip_store: &ClipStore, + clip_data_store: &ClipDataStore, ) -> HitTester { let mut hit_tester = HitTester { runs: runs.clone(), spatial_nodes: Vec::new(), - clip_nodes: Vec::new(), clip_chains: Vec::new(), pipeline_root_nodes: FastHashMap::default(), }; - hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store); + hit_tester.read_clip_scroll_tree( + clip_scroll_tree, + clip_store, + clip_data_store, + ); hit_tester } fn read_clip_scroll_tree( &mut self, clip_scroll_tree: &ClipScrollTree, - clip_store: &ClipStore + clip_store: &ClipStore, + clip_data_store: &ClipDataStore, ) { self.spatial_nodes.clear(); self.clip_chains.clear(); - self.clip_nodes.clear(); for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() { let index = SpatialNodeIndex(index); @@ -141,21 +165,25 @@ impl HitTester { }); } - for node in &clip_store.clip_nodes { - self.clip_nodes.push(HitTestClipNode::new(node)); + // For each clip chain node, extract the clip node from the clip + // data store, and store it inline with the clip chain node. + for node in &clip_store.clip_chain_nodes { + let clip_node = clip_data_store.get(&node.handle); + self.clip_chains.push(HitTestClipChainNode { + region: HitTestClipNode::new(clip_node), + spatial_node_index: node.spatial_node_index, + parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0), + }); } - - self.clip_chains - .extend_from_slice(&clip_store.clip_chain_nodes); } fn is_point_clipped_in_for_clip_chain( &self, point: WorldPoint, - clip_chain_id: ClipChainId, + clip_chain_id: HitTestClipChainId, test: &mut HitTest ) -> bool { - if clip_chain_id == ClipChainId::NONE { + if clip_chain_id == HitTestClipChainId::NONE { return true; } @@ -177,7 +205,7 @@ impl HitTester { if !self.is_point_clipped_in_for_clip_node( point, - descriptor.clip_node_index, + clip_chain_id, descriptor.spatial_node_index, test, ) { @@ -192,15 +220,15 @@ impl HitTester { fn is_point_clipped_in_for_clip_node( &self, point: WorldPoint, - node_index: ClipNodeIndex, + clip_chain_node_id: HitTestClipChainId, spatial_node_index: SpatialNodeIndex, test: &mut HitTest ) -> bool { - if let Some(clipped_in) = test.node_cache.get(&node_index) { + if let Some(clipped_in) = test.node_cache.get(&clip_chain_node_id) { return *clipped_in == ClippedIn::ClippedIn; } - let node = &self.clip_nodes[node_index.0 as usize]; + let node = &self.clip_chains[clip_chain_node_id.0 as usize].region; let transform = self .spatial_nodes[spatial_node_index.0] .world_viewport_transform; @@ -210,17 +238,17 @@ impl HitTester { { Some(point) => point, None => { - test.node_cache.insert(node_index, ClippedIn::NotClippedIn); + test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn); return false; } }; if !node.region.contains(&transformed_point) { - test.node_cache.insert(node_index, ClippedIn::NotClippedIn); + test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn); return false; } - test.node_cache.insert(node_index, ClippedIn::ClippedIn); + test.node_cache.insert(clip_chain_node_id, ClippedIn::ClippedIn); true } @@ -246,7 +274,7 @@ impl HitTester { continue; } - let clip_chain_id = clip_and_scroll.clip_chain_id; + let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0); clipped_in |= self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test); if !clipped_in { @@ -290,7 +318,7 @@ impl HitTester { continue; } - let clip_chain_id = clip_and_scroll.clip_chain_id; + let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0); clipped_in = clipped_in || self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test); if !clipped_in { @@ -343,7 +371,6 @@ impl HitTester { unsafe { size += op(self.runs.as_ptr() as *const c_void); size += op(self.spatial_nodes.as_ptr() as *const c_void); - size += op(self.clip_nodes.as_ptr() as *const c_void); size += op(self.clip_chains.as_ptr() as *const c_void); // We can't measure pipeline_root_nodes because we don't have the // real machinery from the malloc_size_of crate. We could estimate @@ -363,7 +390,7 @@ pub struct HitTest { pipeline_id: Option, point: WorldPoint, flags: HitTestFlags, - node_cache: FastHashMap, + node_cache: FastHashMap, clip_chain_cache: Vec>, } @@ -382,7 +409,7 @@ impl HitTest { } } - fn get_from_clip_chain_cache(&mut self, index: ClipChainId) -> Option { + fn get_from_clip_chain_cache(&mut self, index: HitTestClipChainId) -> Option { let index = index.0 as usize; if index >= self.clip_chain_cache.len() { None @@ -391,7 +418,7 @@ impl HitTest { } } - fn set_in_clip_chain_cache(&mut self, index: ClipChainId, value: ClippedIn) { + fn set_in_clip_chain_cache(&mut self, index: HitTestClipChainId, value: ClippedIn) { let index = index.0 as usize; if index >= self.clip_chain_cache.len() { self.clip_chain_cache.resize(index + 1, None); diff --git a/webrender/src/intern.rs b/webrender/src/intern.rs new file mode 100644 index 0000000000..2a400875c6 --- /dev/null +++ b/webrender/src/intern.rs @@ -0,0 +1,288 @@ +/* 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 internal_types::FastHashMap; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::mem; +use std::u64; + +/* + + The interning module provides a generic data structure + interning container. It is similar in concept to a + traditional string interning container, but it is + specialized to the WR thread model. + + There is an Interner structure, that lives in the + scene builder thread, and a DataStore structure + that lives in the frame builder thread. + + Hashing, interning and handle creation is done by + the interner structure during scene building. + + Delta changes for the interner are pushed during + a transaction to the frame builder. The frame builder + is then able to access the content of the interned + handles quickly, via array indexing. + + Epoch tracking ensures that the garbage collection + step which the interner uses to remove items is + only invoked on items that the frame builder thread + is no longer referencing. + + Items in the data store are stored in a traditional + free-list structure, for content access and memory + usage efficiency. + + */ + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq)] +struct Epoch(u64); + +impl Epoch { + pub const INVALID: Self = Epoch(u64::MAX); +} + +// A list of updates to be applied to the data store, +// provided by the interning structure. +pub struct UpdateList { + // The current epoch of the scene builder. + epoch: Epoch, + // The additions and removals to apply. + updates: Vec>, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone)] +pub struct Handle { + index: usize, + epoch: Epoch, + _marker: PhantomData, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum UpdateKind { + Insert(S), + Remove, + UpdateEpoch, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct Update { + index: usize, + kind: UpdateKind, +} + +// The data item is stored with an epoch, for validating +// correct access patterns. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Item { + epoch: Epoch, + data: T, +} + +// The data store lives in the frame builder thread. It +// contains a free-list of items for fast access. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct DataStore { + items: Vec>, + _source: PhantomData, + _marker: PhantomData, +} + +impl DataStore where S: Debug, T: From, M: Debug { + // Construct a new data store + pub fn new() -> Self { + DataStore { + items: Vec::new(), + _source: PhantomData, + _marker: PhantomData, + } + } + + // Apply any updates from the scene builder thread to + // this data store. + pub fn apply_updates( + &mut self, + update_list: UpdateList, + ) { + for update in update_list.updates { + match update.kind { + UpdateKind::Insert(data) => { + let item = Item { + data: T::from(data), + epoch: update_list.epoch, + }; + if self.items.len() == update.index { + self.items.push(item) + } else { + self.items[update.index] = item; + } + } + UpdateKind::Remove => { + self.items[update.index].epoch = Epoch::INVALID; + } + UpdateKind::UpdateEpoch => { + self.items[update.index].epoch = update_list.epoch; + } + } + } + } + + // Retrieve an item from the store via handle + pub fn get(&self, handle: &Handle) -> &T { + let item = &self.items[handle.index]; + assert_eq!(item.epoch, handle.epoch); + &item.data + } + + // Retrieve a mutable item from the store via handle + pub fn get_mut(&mut self, handle: &Handle) -> &mut T { + let item = &mut self.items[handle.index]; + assert_eq!(item.epoch, handle.epoch); + &mut item.data + } +} + +// The main interning data structure. This lives in the +// scene builder thread, and handles hashing and interning +// unique data structures. It also manages a free-list for +// the items in the data store, which is synchronized via +// an update list of additions / removals. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct Interner { + // Uniquely map an interning key to a handle + map: FastHashMap>, + // List of free slots in the data store for re-used. + free_list: Vec, + // The next index to append items to if free-list is empty. + next_index: usize, + // Pending list of updates that need to be applied. + updates: Vec>, + // The current epoch for the interner. + current_epoch: Epoch, +} + +impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + Debug { + // Construct a new interner + pub fn new() -> Self { + Interner { + map: FastHashMap::default(), + free_list: Vec::new(), + next_index: 0, + updates: Vec::new(), + current_epoch: Epoch(1), + } + } + + // Intern a data structure, and return a handle to + // that data. The handle can then be stored in the + // frame builder, and safely accessed via the data + // store that lives in the frame builder thread. + pub fn intern( + &mut self, + data: &S, + ) -> Handle { + // Use get_mut rather than entry here to avoid + // cloning the (sometimes large) key in the common + // case, where the data already exists in the interner. + if let Some(handle) = self.map.get_mut(data) { + // Update the epoch in the data store. This + // is not strictly needed for correctness, but + // is used to ensure items are only accessed + // via valid handles. + if handle.epoch != self.current_epoch { + self.updates.push(Update { + index: handle.index, + kind: UpdateKind::UpdateEpoch, + }) + } + handle.epoch = self.current_epoch; + return *handle; + } + + // We need to intern a new data item. First, find out + // if there is a spare slot in the free-list that we + // can use. Otherwise, append to the end of the list. + let index = match self.free_list.pop() { + Some(index) => index, + None => { + let index = self.next_index; + self.next_index += 1; + index + } + }; + + // Add a pending update to insert the new data. + self.updates.push(Update { + index, + kind: UpdateKind::Insert(data.clone()), + }); + + // Generate a handle for access via the data store. + let handle = Handle { + index, + epoch: self.current_epoch, + _marker: PhantomData, + }; + + // Store this handle so the next time it is + // interned, it gets re-used. + self.map.insert(data.clone(), handle); + + handle + } + + // Retrieve the pending list of updates for an interner + // that need to be applied to the data store. + pub fn get_updates(&mut self) -> UpdateList { + let mut updates = mem::replace(&mut self.updates, Vec::new()); + let free_list = &mut self.free_list; + let current_epoch = self.current_epoch.0; + + // First, run a GC step. Walk through the handles, and + // if we find any that haven't been used for some time, + // remove them. If this ever shows up in profiles, we + // can make the GC step partial (scan only part of the + // map each frame). It also might make sense in the + // future to adjust how long items remain in the cache + // based on the current size of the list. + self.map.retain(|_, handle| { + if handle.epoch.0 + 10 < current_epoch { + // To expire an item: + // - Add index to the free-list for re-use. + // - Add an update to the data store to invalidate this slow. + // - Remove from the hash map. + free_list.push(handle.index); + updates.push(Update { + index: handle.index, + kind: UpdateKind::Remove, + }); + return false; + } + + true + }); + + let updates = UpdateList { + updates, + epoch: self.current_epoch, + }; + + // Begin the next epoch + self.current_epoch = Epoch(self.current_epoch.0 + 1); + + updates + } +} diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index d622192b11..b4e6338496 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -83,6 +83,7 @@ mod gpu_glyph_renderer; mod gpu_types; mod hit_test; mod image; +mod intern; mod internal_types; mod picture; mod prim_store; diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 84c113b181..00795b39ed 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1725,6 +1725,7 @@ impl PrimitiveStore { frame_context.device_pixel_scale, &frame_context.world_rect, &clip_node_collector, + frame_state.clip_data_store, ); let clip_chain = match clip_chain { @@ -2074,16 +2075,17 @@ fn write_brush_segment_description( // Segment the primitive on all the local-space clip sources that we can. let mut local_clip_count = 0; for i in 0 .. clip_chain.clips_range.count { - let (clip_node, flags, _) = frame_state + let clip_instance = frame_state .clip_store - .get_node_from_range(&clip_chain.clips_range, i); + .get_instance_from_range(&clip_chain.clips_range, i); + let clip_node = frame_state.clip_data_store.get(&clip_instance.handle); // If this clip item is positioned by another positioning node, its relative position // could change during scrolling. This means that we would need to resegment. Instead // of doing that, only segment with clips that have the same positioning node. // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only // when necessary while scrolling. - if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { + if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { continue; } @@ -2243,6 +2245,7 @@ impl Primitive { frame_context.device_pixel_scale, &frame_context.world_rect, clip_node_collector, + frame_state.clip_data_store, ); match segment_clip_chain { @@ -2275,6 +2278,7 @@ impl Primitive { frame_state.gpu_cache, frame_state.resource_cache, frame_state.render_tasks, + frame_state.clip_data_store, ); let clip_task_id = frame_state.render_tasks.add(clip_task); @@ -2812,6 +2816,7 @@ impl Primitive { frame_state.gpu_cache, frame_state.resource_cache, frame_state.render_tasks, + frame_state.clip_data_store, ); let clip_task_id = frame_state.render_tasks.add(clip_task); diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index e37345cfb7..e651257b95 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -16,6 +16,9 @@ use api::channel::{MsgReceiver, Payload}; use api::CaptureBits; #[cfg(feature = "replay")] use api::CapturedDocument; +#[cfg(feature = "replay")] +use clip::ClipDataInterner; +use clip::{ClipDataUpdateList, ClipDataStore}; use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree}; #[cfg(feature = "debugger")] use debug_server; @@ -114,6 +117,10 @@ struct Document { /// before rendering again. frame_is_valid: bool, hit_tester_is_valid: bool, + + // The store of currently active / available clip nodes. This is kept + // in sync with the clip interner in the scene builder for each document. + clip_data_store: ClipDataStore, } impl Document { @@ -142,6 +149,7 @@ impl Document { dynamic_properties: SceneProperties::new(), frame_is_valid: false, hit_tester_is_valid: false, + clip_data_store: ClipDataStore::new(), } } @@ -267,8 +275,12 @@ impl Document { &mut resource_profile.texture_cache, &mut resource_profile.gpu_cache, &self.dynamic_properties, + &mut self.clip_data_store, ); - self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree)); + self.hit_tester = Some(frame_builder.create_hit_tester( + &self.clip_scroll_tree, + &self.clip_data_store, + )); frame }; @@ -600,6 +612,7 @@ impl RenderBackend { self.update_document( txn.document_id, replace(&mut txn.resource_updates, Vec::new()), + txn.clip_updates.take(), replace(&mut txn.frame_ops, Vec::new()), replace(&mut txn.notifications, Vec::new()), txn.build_frame, @@ -905,6 +918,7 @@ impl RenderBackend { self.update_document( txn.document_id, replace(&mut txn.resource_updates, Vec::new()), + None, replace(&mut txn.frame_ops, Vec::new()), replace(&mut txn.notifications, Vec::new()), txn.build_frame, @@ -942,6 +956,7 @@ impl RenderBackend { &mut self, document_id: DocumentId, resource_updates: Vec, + clip_updates: Option, mut frame_ops: Vec, mut notifications: Vec, mut build_frame: bool, @@ -965,6 +980,12 @@ impl RenderBackend { let doc = self.documents.get_mut(&document_id).unwrap(); + // If there are any additions or removals of clip modes + // during the scene build, apply them to the data store now. + if let Some(clip_updates) = clip_updates { + doc.clip_data_store.apply_updates(clip_updates); + } + // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used // for something wrench specific and we should remove it. let mut scroll = false; @@ -1280,6 +1301,11 @@ impl RenderBackend { let file_name = format!("frame-{}-{}", (id.0).0, id.1); config.serialize(&rendered_document.frame, file_name); } + + // TODO(gw): Work out serializing the clip interner / store. + //let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1); + // let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1); + // config.serialize(&doc.clip_data_store, clip_data_name); } debug!("\tresource cache"); @@ -1362,6 +1388,14 @@ impl RenderBackend { let scene = CaptureConfig::deserialize::(root, &scene_name) .expect(&format!("Unable to open {}.ron", scene_name)); + let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1); + let clip_interner = CaptureConfig::deserialize::(root, &clip_interner_name) + .expect(&format!("Unable to open {}.ron", clip_interner_name)); + + let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1); + let clip_data_store = CaptureConfig::deserialize::(root, &clip_data_name) + .expect(&format!("Unable to open {}.ron", clip_data_name)); + let mut doc = Document { scene: scene.clone(), removed_pipelines: Vec::new(), @@ -1374,6 +1408,7 @@ impl RenderBackend { hit_tester: None, frame_is_valid: false, hit_tester_is_valid: false, + clip_data_store, }; let frame_name = format!("frame-{}-{}", (id.0).0, id.1); @@ -1414,6 +1449,7 @@ impl RenderBackend { font_instances: self.resource_cache.get_font_instances(), scene_id: last_scene_id, build_frame, + clip_interner, }); self.documents.insert(id, doc); diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index b05051c851..5ec16d44f8 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -7,7 +7,7 @@ use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSid use api::FontRenderMode; use border::BorderCacheKey; use box_shadow::{BoxShadowCacheKey}; -use clip::{ClipItem, ClipStore, ClipNodeRange}; +use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange}; use clip_scroll_tree::SpatialNodeIndex; use device::TextureFilter; #[cfg(feature = "pathfinder")] @@ -432,6 +432,7 @@ impl RenderTask { gpu_cache: &mut GpuCache, resource_cache: &mut ResourceCache, render_tasks: &mut RenderTaskTree, + clip_data_store: &mut ClipDataStore, ) -> Self { let mut children = Vec::new(); @@ -445,7 +446,8 @@ impl RenderTask { // whether a ClipSources contains any box-shadows and skip // this iteration for the majority of cases. for i in 0 .. clip_node_range.count { - let (clip_node, _) = clip_store.get_node_from_range_mut(&clip_node_range, i); + let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); + let clip_node = clip_data_store.get_mut(&clip_instance.handle); match clip_node.item { ClipItem::BoxShadow(ref mut info) => { let (cache_size, cache_key) = info.cache_key diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index b1ab424d22..23240bfb2d 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -7,6 +7,7 @@ use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch}; use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint}; use api::channel::MsgSender; use frame_builder::{FrameBuilderConfig, FrameBuilder}; +use clip::{ClipDataInterner, ClipDataUpdateList}; use clip_scroll_tree::ClipScrollTree; use display_list_flattener::DisplayListFlattener; use internal_types::{FastHashMap, FastHashSet}; @@ -67,6 +68,7 @@ pub struct BuiltTransaction { pub frame_ops: Vec, pub removed_pipelines: Vec, pub notifications: Vec, + pub clip_updates: Option, pub scene_build_start_time: u64, pub scene_build_end_time: u64, pub build_frame: bool, @@ -100,6 +102,7 @@ pub struct LoadScene { pub view: DocumentView, pub config: FrameBuilderConfig, pub build_frame: bool, + pub clip_interner: ClipDataInterner, } pub struct BuiltScene { @@ -137,8 +140,26 @@ pub enum SceneSwapResult { Aborted, } +// A document in the scene builder contains the current scene, +// as well as a persistent clip interner. This allows clips +// to be de-duplicated, and persisted in the GPU cache between +// display lists. +struct Document { + scene: Scene, + clip_interner: ClipDataInterner, +} + +impl Document { + fn new(scene: Scene) -> Self { + Document { + scene, + clip_interner: ClipDataInterner::new(), + } + } +} + pub struct SceneBuilder { - documents: FastHashMap, + documents: FastHashMap, rx: Receiver, tx: Sender, api_tx: MsgSender, @@ -226,7 +247,7 @@ impl SceneBuilder { #[cfg(feature = "replay")] fn load_scenes(&mut self, scenes: Vec) { - for item in scenes { + for mut item in scenes { self.config = item.config; let scene_build_start_time = precise_time_ns(); @@ -246,6 +267,7 @@ impl SceneBuilder { &mut new_scene, item.scene_id, &mut self.picture_id_generator, + &mut item.clip_interner, ); built_scene = Some(BuiltScene { @@ -255,7 +277,10 @@ impl SceneBuilder { }); } - self.documents.insert(item.document_id, item.scene); + self.documents.insert( + item.document_id, + Document::new(item.scene), + ); let txn = Box::new(BuiltTransaction { document_id: item.document_id, @@ -270,6 +295,7 @@ impl SceneBuilder { notifications: Vec::new(), scene_build_start_time, scene_build_end_time: precise_time_ns(), + clip_updates: None, }); self.forward_built_transaction(txn); @@ -281,7 +307,10 @@ impl SceneBuilder { let scene_build_start_time = precise_time_ns(); - let scene = self.documents.entry(txn.document_id).or_insert(Scene::new()); + let doc = self.documents + .entry(txn.document_id) + .or_insert(Document::new(Scene::new())); + let scene = &mut doc.scene; for update in txn.display_list_updates.drain(..) { scene.set_display_list( @@ -307,6 +336,7 @@ impl SceneBuilder { } let mut built_scene = None; + let mut clip_updates = None; if scene.has_root_pipeline() { if let Some(request) = txn.request_scene_build.take() { let mut clip_scroll_tree = ClipScrollTree::new(); @@ -322,8 +352,12 @@ impl SceneBuilder { &mut new_scene, request.scene_id, &mut self.picture_id_generator, + &mut doc.clip_interner, ); + // Retrieve the list of updates from the clip interner. + clip_updates = Some(doc.clip_interner.get_updates()); + built_scene = Some(BuiltScene { scene: new_scene, frame_builder, @@ -360,6 +394,7 @@ impl SceneBuilder { frame_ops: replace(&mut txn.frame_ops, Vec::new()), removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()), notifications: replace(&mut txn.notifications, Vec::new()), + clip_updates, scene_build_start_time, scene_build_end_time: precise_time_ns(), }) diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index 36d21a171b..6b7c590726 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -6,7 +6,7 @@ use api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, Dev use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat}; use api::{LayoutRect, MixBlendMode, PipelineId}; use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image}; -use clip::{ClipStore}; +use clip::{ClipDataStore, ClipStore}; use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex}; use device::{FrameId, Texture}; #[cfg(feature = "pathfinder")] @@ -48,6 +48,7 @@ pub struct RenderTargetContext<'a, 'rc> { pub resource_cache: &'rc mut ResourceCache, pub use_dual_source_blending: bool, pub clip_scroll_tree: &'a ClipScrollTree, + pub clip_data_store: &'a ClipDataStore, } #[cfg_attr(feature = "capture", derive(Serialize))] @@ -599,6 +600,7 @@ impl RenderTarget for AlphaRenderTarget { clip_store, ctx.clip_scroll_tree, transforms, + ctx.clip_data_store, ); } RenderTaskKind::ClipRegion(ref task) => { diff --git a/webrender_api/src/display_item.rs b/webrender_api/src/display_item.rs index 0d63486472..19cfa03e0c 100644 --- a/webrender_api/src/display_item.rs +++ b/webrender_api/src/display_item.rs @@ -233,14 +233,14 @@ pub struct LineDisplayItem { } #[repr(u8)] -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)] pub enum LineOrientation { Vertical, Horizontal, } #[repr(u8)] -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)] pub enum LineStyle { Solid, Dotted, @@ -740,7 +740,7 @@ impl LocalClip { } #[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] pub enum ClipMode { Clip, // Pixels inside the region are visible. ClipOut, // Pixels outside the region are visible. diff --git a/webrender_api/src/units.rs b/webrender_api/src/units.rs index c9c528c2cf..79dad5897e 100644 --- a/webrender_api/src/units.rs +++ b/webrender_api/src/units.rs @@ -121,6 +121,7 @@ pub type RasterToPictureTransform = TypedTransform3D; pub type LayoutRectAu = TypedRect; pub type LayoutSizeAu = TypedSize2D; +pub type LayoutVector2DAu = TypedVector2D; /// Coordinates in normalized space (between zero and one). #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] @@ -154,3 +155,87 @@ impl TexelRect { } } } + +const MAX_AU_FLOAT: f32 = 1.0e6; + +pub trait AuSizeHelpers { + fn from_au(size: LayoutSizeAu) -> Self; + fn to_au(&self) -> LayoutSizeAu; +} + +impl AuSizeHelpers for LayoutSize { + fn from_au(size: LayoutSizeAu) -> Self { + LayoutSize::new( + size.width.to_f32_px(), + size.height.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutSizeAu { + let width = self.width.min(2.0 * MAX_AU_FLOAT); + let height = self.height.min(2.0 * MAX_AU_FLOAT); + + LayoutSizeAu::new( + Au::from_f32_px(width), + Au::from_f32_px(height), + ) + } +} + +pub trait AuVectorHelpers { + fn to_au(&self) -> LayoutVector2DAu; +} + +impl AuVectorHelpers for LayoutVector2D { + fn to_au(&self) -> LayoutVector2DAu { + LayoutVector2DAu::new( + Au::from_f32_px(self.x), + Au::from_f32_px(self.y), + ) + } +} + +pub trait AuPointHelpers { + fn from_au(point: LayoutPointAu) -> Self; + fn to_au(&self) -> LayoutPointAu; +} + +impl AuPointHelpers for LayoutPoint { + fn from_au(point: LayoutPointAu) -> Self { + LayoutPoint::new( + point.x.to_f32_px(), + point.y.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutPointAu { + let x = self.x.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT); + let y = self.y.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT); + + LayoutPointAu::new( + Au::from_f32_px(x), + Au::from_f32_px(y), + ) + } +} + +pub trait AuRectHelpers { + fn from_au(rect: LayoutRectAu) -> Self; + fn to_au(&self) -> LayoutRectAu; +} + +impl AuRectHelpers for LayoutRect { + fn from_au(rect: LayoutRectAu) -> Self { + LayoutRect::new( + LayoutPoint::from_au(rect.origin), + LayoutSize::from_au(rect.size), + ) + } + + fn to_au(&self) -> LayoutRectAu { + LayoutRectAu::new( + self.origin.to_au(), + self.size.to_au(), + ) + } +} From 450a2f17f39e1c7dae7826c73b6902c731d0dc9a Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 19 Sep 2018 07:50:52 +1000 Subject: [PATCH 2/3] Address (most) review comments. --- webrender/src/batch.rs | 2 +- webrender/src/border.rs | 2 +- webrender/src/clip.rs | 7 ++- webrender/src/hit_test.rs | 2 +- webrender/src/intern.rs | 79 ++++++++++++++++++++-------------- webrender/src/prim_store.rs | 2 +- webrender/src/render_task.rs | 2 +- webrender/src/scene_builder.rs | 2 +- webrender_api/src/units.rs | 33 ++++++-------- 9 files changed, 68 insertions(+), 63 deletions(-) diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 8521cd4323..0dbcb4b421 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -1779,7 +1779,7 @@ impl ClipBatcher { ) { for i in 0 .. clip_node_range.count { let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); - let clip_node = clip_data_store.get(&clip_instance.handle); + let clip_node = &clip_data_store[clip_instance.handle]; let clip_transform_id = transforms.get_id( clip_instance.spatial_node_index, diff --git a/webrender/src/border.rs b/webrender/src/border.rs index dc282c1f3c..90eed86a47 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -5,7 +5,7 @@ use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize}; use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale}; use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder}; -use api::{AuSizeHelpers}; +use api::{AuHelpers}; use app_units::Au; use ellipse::Ellipse; use display_list_flattener::DisplayListFlattener; diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index e1abe9a35a..3e831b8eec 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -6,8 +6,7 @@ use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelS use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip}; use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel}; use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform}; -use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey}; -use api::{AuRectHelpers, AuVectorHelpers}; +use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers}; use app_units::Au; use border::{ensure_no_corner_overlap, BorderRadiusAu}; use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey}; @@ -537,7 +536,7 @@ impl ClipStore { // For each potential clip node for node_info in self.clip_node_info.drain(..) { - let node = clip_data_store.get_mut(&node_info.handle); + let node = &mut clip_data_store[node_info.handle]; // See how this clip affects the prim region. let clip_result = match node_info.conversion { @@ -1221,7 +1220,7 @@ fn add_clip_node_to_current_chain( clip_data_store: &ClipDataStore, clip_scroll_tree: &ClipScrollTree, ) -> bool { - let clip_node = clip_data_store.get(&handle); + let clip_node = &clip_data_store[handle]; let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_spatial_node_index.0]; let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0]; diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs index cbe78e12a0..9e0227052d 100644 --- a/webrender/src/hit_test.rs +++ b/webrender/src/hit_test.rs @@ -168,7 +168,7 @@ impl HitTester { // For each clip chain node, extract the clip node from the clip // data store, and store it inline with the clip chain node. for node in &clip_store.clip_chain_nodes { - let clip_node = clip_data_store.get(&node.handle); + let clip_node = &clip_data_store[node.handle]; self.clip_chains.push(HitTestClipChainNode { region: HitTestClipNode::new(clip_node), spatial_node_index: node.spatial_node_index, diff --git a/webrender/src/intern.rs b/webrender/src/intern.rs index 2a400875c6..a3d45d0390 100644 --- a/webrender/src/intern.rs +++ b/webrender/src/intern.rs @@ -7,6 +7,7 @@ use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; use std::mem; +use std::ops; use std::u64; /* @@ -39,6 +40,11 @@ use std::u64; */ +/// The epoch is incremented each time a scene is +/// built. The most recently used scene epoch is +/// stored inside each item and handle. This is +/// then used for cache invalidation (item) and +/// correctness validation (handle). #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, Clone, PartialEq)] @@ -48,12 +54,12 @@ impl Epoch { pub const INVALID: Self = Epoch(u64::MAX); } -// A list of updates to be applied to the data store, -// provided by the interning structure. +/// A list of updates to be applied to the data store, +/// provided by the interning structure. pub struct UpdateList { - // The current epoch of the scene builder. + /// The current epoch of the scene builder. epoch: Epoch, - // The additions and removals to apply. + /// The additions and removals to apply. updates: Vec>, } @@ -81,8 +87,8 @@ pub struct Update { kind: UpdateKind, } -// The data item is stored with an epoch, for validating -// correct access patterns. +/// The data item is stored with an epoch, for validating +/// correct access patterns. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct Item { @@ -90,8 +96,8 @@ struct Item { data: T, } -// The data store lives in the frame builder thread. It -// contains a free-list of items for fast access. +/// The data store lives in the frame builder thread. It +/// contains a free-list of items for fast access. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct DataStore { @@ -101,7 +107,7 @@ pub struct DataStore { } impl DataStore where S: Debug, T: From, M: Debug { - // Construct a new data store + /// Construct a new data store pub fn new() -> Self { DataStore { items: Vec::new(), @@ -110,8 +116,8 @@ impl DataStore where S: Debug, T: From, M: Debug { } } - // Apply any updates from the scene builder thread to - // this data store. + /// Apply any updates from the scene builder thread to + /// this data store. pub fn apply_updates( &mut self, update_list: UpdateList, @@ -138,44 +144,50 @@ impl DataStore where S: Debug, T: From, M: Debug { } } } +} - // Retrieve an item from the store via handle - pub fn get(&self, handle: &Handle) -> &T { +/// Retrieve an item from the store via handle +impl ops::Index> for DataStore { + type Output = T; + fn index(&self, handle: Handle) -> &T { let item = &self.items[handle.index]; assert_eq!(item.epoch, handle.epoch); &item.data } +} - // Retrieve a mutable item from the store via handle - pub fn get_mut(&mut self, handle: &Handle) -> &mut T { +/// Retrieve a mutable item from the store via handle +/// Retrieve an item from the store via handle +impl ops::IndexMut> for DataStore { + fn index_mut(&mut self, handle: Handle) -> &mut T { let item = &mut self.items[handle.index]; assert_eq!(item.epoch, handle.epoch); &mut item.data } } -// The main interning data structure. This lives in the -// scene builder thread, and handles hashing and interning -// unique data structures. It also manages a free-list for -// the items in the data store, which is synchronized via -// an update list of additions / removals. +/// The main interning data structure. This lives in the +/// scene builder thread, and handles hashing and interning +/// unique data structures. It also manages a free-list for +/// the items in the data store, which is synchronized via +/// an update list of additions / removals. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct Interner { - // Uniquely map an interning key to a handle + /// Uniquely map an interning key to a handle map: FastHashMap>, - // List of free slots in the data store for re-used. + /// List of free slots in the data store for re-use. free_list: Vec, - // The next index to append items to if free-list is empty. + /// The next index to append items to if free-list is empty. next_index: usize, - // Pending list of updates that need to be applied. + /// Pending list of updates that need to be applied. updates: Vec>, - // The current epoch for the interner. + /// The current epoch for the interner. current_epoch: Epoch, } impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + Debug { - // Construct a new interner + /// Construct a new interner pub fn new() -> Self { Interner { map: FastHashMap::default(), @@ -186,10 +198,10 @@ impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + Debug { } } - // Intern a data structure, and return a handle to - // that data. The handle can then be stored in the - // frame builder, and safely accessed via the data - // store that lives in the frame builder thread. + /// Intern a data structure, and return a handle to + /// that data. The handle can then be stored in the + /// frame builder, and safely accessed via the data + /// store that lives in the frame builder thread. pub fn intern( &mut self, data: &S, @@ -244,9 +256,10 @@ impl Interner where S: Eq + Hash + Clone + Debug, M: Copy + Debug { handle } - // Retrieve the pending list of updates for an interner - // that need to be applied to the data store. - pub fn get_updates(&mut self) -> UpdateList { + /// Retrieve the pending list of updates for an interner + /// that need to be applied to the data store. Also run + /// a GC step that removes old entries. + pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList { let mut updates = mem::replace(&mut self.updates, Vec::new()); let free_list = &mut self.free_list; let current_epoch = self.current_epoch.0; diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 00795b39ed..0bbb2c8e33 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -2078,7 +2078,7 @@ fn write_brush_segment_description( let clip_instance = frame_state .clip_store .get_instance_from_range(&clip_chain.clips_range, i); - let clip_node = frame_state.clip_data_store.get(&clip_instance.handle); + let clip_node = &frame_state.clip_data_store[clip_instance.handle]; // If this clip item is positioned by another positioning node, its relative position // could change during scrolling. This means that we would need to resegment. Instead diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index 5ec16d44f8..ba77a73761 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -447,7 +447,7 @@ impl RenderTask { // this iteration for the majority of cases. for i in 0 .. clip_node_range.count { let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); - let clip_node = clip_data_store.get_mut(&clip_instance.handle); + let clip_node = &mut clip_data_store[clip_instance.handle]; match clip_node.item { ClipItem::BoxShadow(ref mut info) => { let (cache_size, cache_key) = info.cache_key diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index 23240bfb2d..b17525aaea 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -356,7 +356,7 @@ impl SceneBuilder { ); // Retrieve the list of updates from the clip interner. - clip_updates = Some(doc.clip_interner.get_updates()); + clip_updates = Some(doc.clip_interner.end_frame_and_get_pending_updates()); built_scene = Some(BuiltScene { scene: new_scene, diff --git a/webrender_api/src/units.rs b/webrender_api/src/units.rs index 79dad5897e..faca53c3a6 100644 --- a/webrender_api/src/units.rs +++ b/webrender_api/src/units.rs @@ -158,12 +158,12 @@ impl TexelRect { const MAX_AU_FLOAT: f32 = 1.0e6; -pub trait AuSizeHelpers { - fn from_au(size: LayoutSizeAu) -> Self; - fn to_au(&self) -> LayoutSizeAu; +pub trait AuHelpers { + fn from_au(data: T) -> Self; + fn to_au(&self) -> T; } -impl AuSizeHelpers for LayoutSize { +impl AuHelpers for LayoutSize { fn from_au(size: LayoutSizeAu) -> Self { LayoutSize::new( size.width.to_f32_px(), @@ -182,11 +182,14 @@ impl AuSizeHelpers for LayoutSize { } } -pub trait AuVectorHelpers { - fn to_au(&self) -> LayoutVector2DAu; -} +impl AuHelpers for LayoutVector2D { + fn from_au(size: LayoutVector2DAu) -> Self { + LayoutVector2D::new( + size.x.to_f32_px(), + size.y.to_f32_px(), + ) + } -impl AuVectorHelpers for LayoutVector2D { fn to_au(&self) -> LayoutVector2DAu { LayoutVector2DAu::new( Au::from_f32_px(self.x), @@ -195,12 +198,7 @@ impl AuVectorHelpers for LayoutVector2D { } } -pub trait AuPointHelpers { - fn from_au(point: LayoutPointAu) -> Self; - fn to_au(&self) -> LayoutPointAu; -} - -impl AuPointHelpers for LayoutPoint { +impl AuHelpers for LayoutPoint { fn from_au(point: LayoutPointAu) -> Self { LayoutPoint::new( point.x.to_f32_px(), @@ -219,12 +217,7 @@ impl AuPointHelpers for LayoutPoint { } } -pub trait AuRectHelpers { - fn from_au(rect: LayoutRectAu) -> Self; - fn to_au(&self) -> LayoutRectAu; -} - -impl AuRectHelpers for LayoutRect { +impl AuHelpers for LayoutRect { fn from_au(rect: LayoutRectAu) -> Self { LayoutRect::new( LayoutPoint::from_au(rect.origin), From 7cb78ff4719ddee8c7ca1acaa4bb5a77efba81f4 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 19 Sep 2018 10:26:38 +1000 Subject: [PATCH 3/3] Add capture integration --- webrender/src/capture.rs | 1 + webrender/src/render_backend.rs | 9 +++++---- webrender/src/scene_builder.rs | 27 +++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/webrender/src/capture.rs b/webrender/src/capture.rs index 53da889022..42969c1da4 100644 --- a/webrender/src/capture.rs +++ b/webrender/src/capture.rs @@ -12,6 +12,7 @@ use ron; use serde; +#[derive(Clone)] pub struct CaptureConfig { pub root: PathBuf, pub bits: CaptureBits, diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index e651257b95..8f31903b78 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -1302,12 +1302,13 @@ impl RenderBackend { config.serialize(&rendered_document.frame, file_name); } - // TODO(gw): Work out serializing the clip interner / store. - //let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1); - // let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1); - // config.serialize(&doc.clip_data_store, clip_data_name); + let clip_data_name = format!("clip-data-{}-{}", (id.0).0, id.1); + config.serialize(&doc.clip_data_store, clip_data_name); } + debug!("\tscene builder"); + self.scene_tx.send(SceneBuilderRequest::SaveScene(config.clone())).unwrap(); + debug!("\tresource cache"); let (resources, deferred) = self.resource_cache.save_capture(&config.root); diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index b17525aaea..c2778064d4 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -6,6 +6,8 @@ use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImage use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch}; use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint}; use api::channel::MsgSender; +#[cfg(feature = "capture")] +use capture::CaptureConfig; use frame_builder::{FrameBuilderConfig, FrameBuilder}; use clip::{ClipDataInterner, ClipDataUpdateList}; use clip_scroll_tree::ClipScrollTree; @@ -121,6 +123,8 @@ pub enum SceneBuilderRequest { SimulateLongSceneBuild(u32), SimulateLongLowPrioritySceneBuild(u32), Stop, + #[cfg(feature = "capture")] + SaveScene(CaptureConfig), #[cfg(feature = "replay")] LoadScenes(Vec), } @@ -220,6 +224,10 @@ impl SceneBuilder { Ok(SceneBuilderRequest::LoadScenes(msg)) => { self.load_scenes(msg); } + #[cfg(feature = "capture")] + Ok(SceneBuilderRequest::SaveScene(config)) => { + self.save_scene(config); + } Ok(SceneBuilderRequest::Stop) => { self.tx.send(SceneBuilderResult::Stopped).unwrap(); // We don't need to send a WakeUp to api_tx because we only @@ -245,6 +253,14 @@ impl SceneBuilder { } } + #[cfg(feature = "capture")] + fn save_scene(&mut self, config: CaptureConfig) { + for (id, doc) in &self.documents { + let clip_interner_name = format!("clip-interner-{}-{}", (id.0).0, id.1); + config.serialize(&doc.clip_interner, clip_interner_name); + } + } + #[cfg(feature = "replay")] fn load_scenes(&mut self, scenes: Vec) { for mut item in scenes { @@ -253,6 +269,8 @@ impl SceneBuilder { let scene_build_start_time = precise_time_ns(); let mut built_scene = None; + let mut clip_updates = None; + if item.scene.has_root_pipeline() { let mut clip_scroll_tree = ClipScrollTree::new(); let mut new_scene = Scene::new(); @@ -270,6 +288,8 @@ impl SceneBuilder { &mut item.clip_interner, ); + clip_updates = Some(item.clip_interner.end_frame_and_get_pending_updates()); + built_scene = Some(BuiltScene { scene: new_scene, frame_builder, @@ -279,7 +299,10 @@ impl SceneBuilder { self.documents.insert( item.document_id, - Document::new(item.scene), + Document { + scene: item.scene, + clip_interner: item.clip_interner, + }, ); let txn = Box::new(BuiltTransaction { @@ -295,7 +318,7 @@ impl SceneBuilder { notifications: Vec::new(), scene_build_start_time, scene_build_end_time: precise_time_ns(), - clip_updates: None, + clip_updates, }); self.forward_built_transaction(txn);