diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 4864d44042..0dbcb4b421 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[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..90eed86a47 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::{AuHelpers}; 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/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/clip.rs b/webrender/src/clip.rs index 2d5c85e5a2..3e831b8eec 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -6,13 +6,15 @@ 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, AuHelpers}; +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 +92,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 +120,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 +205,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 +222,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 +257,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 +361,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 +388,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 +399,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 +457,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 +481,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 +501,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 +529,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 = &mut clip_data_store[node_info.handle]; // See how this clip affects the prim region. let clip_result = match node_info.conversion { @@ -597,12 +610,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 +625,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 +643,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 +735,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 +939,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 +1212,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[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 +1279,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..9e0227052d 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[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..a3d45d0390 --- /dev/null +++ b/webrender/src/intern.rs @@ -0,0 +1,301 @@ +/* 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::ops; +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. + + */ + +/// 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)] +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 +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 +/// 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. +#[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-use. + 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. 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; + + // 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..0bbb2c8e33 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[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..8f31903b78 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,8 +1301,14 @@ impl RenderBackend { let file_name = format!("frame-{}-{}", (id.0).0, id.1); config.serialize(&rendered_document.frame, file_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); @@ -1362,6 +1389,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 +1409,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 +1450,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..ba77a73761 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 = &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 b1ab424d22..c2778064d4 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -6,7 +6,10 @@ 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; use display_list_flattener::DisplayListFlattener; use internal_types::{FastHashMap, FastHashSet}; @@ -67,6 +70,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 +104,7 @@ pub struct LoadScene { pub view: DocumentView, pub config: FrameBuilderConfig, pub build_frame: bool, + pub clip_interner: ClipDataInterner, } pub struct BuiltScene { @@ -118,6 +123,8 @@ pub enum SceneBuilderRequest { SimulateLongSceneBuild(u32), SimulateLongLowPrioritySceneBuild(u32), Stop, + #[cfg(feature = "capture")] + SaveScene(CaptureConfig), #[cfg(feature = "replay")] LoadScenes(Vec), } @@ -137,8 +144,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, @@ -199,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 @@ -224,14 +253,24 @@ 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 item in scenes { + for mut item in scenes { self.config = item.config; 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(); @@ -246,8 +285,11 @@ impl SceneBuilder { &mut new_scene, item.scene_id, &mut self.picture_id_generator, + &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, @@ -255,7 +297,13 @@ impl SceneBuilder { }); } - self.documents.insert(item.document_id, item.scene); + self.documents.insert( + item.document_id, + Document { + scene: item.scene, + clip_interner: item.clip_interner, + }, + ); let txn = Box::new(BuiltTransaction { document_id: item.document_id, @@ -270,6 +318,7 @@ impl SceneBuilder { notifications: Vec::new(), scene_build_start_time, scene_build_end_time: precise_time_ns(), + clip_updates, }); self.forward_built_transaction(txn); @@ -281,7 +330,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 +359,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 +375,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.end_frame_and_get_pending_updates()); + built_scene = Some(BuiltScene { scene: new_scene, frame_builder, @@ -360,6 +417,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..faca53c3a6 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,80 @@ impl TexelRect { } } } + +const MAX_AU_FLOAT: f32 = 1.0e6; + +pub trait AuHelpers { + fn from_au(data: T) -> Self; + fn to_au(&self) -> T; +} + +impl AuHelpers 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), + ) + } +} + +impl AuHelpers for LayoutVector2D { + fn from_au(size: LayoutVector2DAu) -> Self { + LayoutVector2D::new( + size.x.to_f32_px(), + size.y.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutVector2DAu { + LayoutVector2DAu::new( + Au::from_f32_px(self.x), + Au::from_f32_px(self.y), + ) + } +} + +impl AuHelpers 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), + ) + } +} + +impl AuHelpers 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(), + ) + } +}