diff --git a/gfx/wr/webrender/src/prim_store/backdrop.rs b/gfx/wr/webrender/src/prim_store/backdrop.rs index c45bf78eef44..7ebfd2be10c2 100644 --- a/gfx/wr/webrender/src/prim_store/backdrop.rs +++ b/gfx/wr/webrender/src/prim_store/backdrop.rs @@ -89,7 +89,6 @@ impl InternablePrimitive for Backdrop { _key: BackdropKey, data_handle: BackdropDataHandle, _prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::Backdrop { data_handle, diff --git a/gfx/wr/webrender/src/prim_store/borders.rs b/gfx/wr/webrender/src/prim_store/borders.rs index ac4673c8c22d..5d837c8a8aa6 100644 --- a/gfx/wr/webrender/src/prim_store/borders.rs +++ b/gfx/wr/webrender/src/prim_store/borders.rs @@ -167,7 +167,6 @@ impl InternablePrimitive for NormalBorderPrim { _key: NormalBorderKey, data_handle: NormalBorderDataHandle, _: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::NormalBorder { data_handle, @@ -355,7 +354,6 @@ impl InternablePrimitive for ImageBorder { _key: ImageBorderKey, data_handle: ImageBorderDataHandle, _: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::ImageBorder { data_handle diff --git a/gfx/wr/webrender/src/prim_store/gradient/conic.rs b/gfx/wr/webrender/src/prim_store/gradient/conic.rs index dfeb958b6a39..133e45bb2e14 100644 --- a/gfx/wr/webrender/src/prim_store/gradient/conic.rs +++ b/gfx/wr/webrender/src/prim_store/gradient/conic.rs @@ -288,7 +288,6 @@ impl InternablePrimitive for ConicGradient { _key: ConicGradientKey, data_handle: ConicGradientDataHandle, _prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::ConicGradient { data_handle, diff --git a/gfx/wr/webrender/src/prim_store/gradient/linear.rs b/gfx/wr/webrender/src/prim_store/gradient/linear.rs index f930aa8db8c9..8f9d0a911e30 100644 --- a/gfx/wr/webrender/src/prim_store/gradient/linear.rs +++ b/gfx/wr/webrender/src/prim_store/gradient/linear.rs @@ -594,7 +594,6 @@ impl InternablePrimitive for LinearGradient { key: LinearGradientKey, data_handle: LinearGradientDataHandle, _prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { if key.cached { PrimitiveInstanceKind::CachedLinearGradient { diff --git a/gfx/wr/webrender/src/prim_store/gradient/radial.rs b/gfx/wr/webrender/src/prim_store/gradient/radial.rs index 2129309cdc8b..6395f1ef05e3 100644 --- a/gfx/wr/webrender/src/prim_store/gradient/radial.rs +++ b/gfx/wr/webrender/src/prim_store/gradient/radial.rs @@ -293,7 +293,6 @@ impl InternablePrimitive for RadialGradient { _key: RadialGradientKey, data_handle: RadialGradientDataHandle, _prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::RadialGradient { data_handle, diff --git a/gfx/wr/webrender/src/prim_store/image.rs b/gfx/wr/webrender/src/prim_store/image.rs index d1649c8b9c26..28ab18f574d7 100644 --- a/gfx/wr/webrender/src/prim_store/image.rs +++ b/gfx/wr/webrender/src/prim_store/image.rs @@ -441,7 +441,6 @@ impl InternablePrimitive for Image { _key: ImageKey, data_handle: ImageDataHandle, prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { // TODO(gw): Refactor this to not need a separate image // instance (see ImageInstance struct). @@ -647,7 +646,6 @@ impl InternablePrimitive for YuvImage { _key: YuvImageKey, data_handle: YuvImageDataHandle, _prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::YuvImage { data_handle, diff --git a/gfx/wr/webrender/src/prim_store/line_dec.rs b/gfx/wr/webrender/src/prim_store/line_dec.rs index 496bab75691c..7b0d2f356c9e 100644 --- a/gfx/wr/webrender/src/prim_store/line_dec.rs +++ b/gfx/wr/webrender/src/prim_store/line_dec.rs @@ -144,7 +144,6 @@ impl InternablePrimitive for LineDecoration { _key: LineDecorationKey, data_handle: LineDecorationDataHandle, _: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { PrimitiveInstanceKind::LineDecoration { data_handle, diff --git a/gfx/wr/webrender/src/prim_store/mod.rs b/gfx/wr/webrender/src/prim_store/mod.rs index c80106dddeb5..5ab5aabd8a1a 100644 --- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -650,7 +650,6 @@ impl InternablePrimitive for PrimitiveKeyKind { key: PrimitiveKey, data_handle: PrimitiveDataHandle, prim_store: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { match key.kind { PrimitiveKeyKind::Clear => { @@ -1453,7 +1452,6 @@ pub trait InternablePrimitive: intern::Internable + Sized { key: Self::Key, data_handle: intern::Handle, prim_store: &mut PrimitiveStore, - reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind; } diff --git a/gfx/wr/webrender/src/prim_store/picture.rs b/gfx/wr/webrender/src/prim_store/picture.rs index d0815cdac877..ae7abbe2d543 100644 --- a/gfx/wr/webrender/src/prim_store/picture.rs +++ b/gfx/wr/webrender/src/prim_store/picture.rs @@ -6,7 +6,7 @@ use api::{ ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace, PropertyBinding, PropertyBindingId, CompositeOperator, }; -use api::units::{Au, LayoutVector2D}; +use api::units::Au; use crate::scene_building::IsVisible; use crate::filterdata::SFilterData; use crate::intern::ItemUid; @@ -292,7 +292,6 @@ impl InternablePrimitive for Picture { _key: PictureKey, _: PictureDataHandle, _: &mut PrimitiveStore, - _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { // Should never be hit as this method should not be // called for pictures. diff --git a/gfx/wr/webrender/src/prim_store/text_run.rs b/gfx/wr/webrender/src/prim_store/text_run.rs index 4f3b3d2817a9..21cf2d829815 100644 --- a/gfx/wr/webrender/src/prim_store/text_run.rs +++ b/gfx/wr/webrender/src/prim_store/text_run.rs @@ -16,7 +16,7 @@ use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonDat use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH}; use crate::resource_cache::{ResourceCache}; use crate::util::{MatrixHelpers}; -use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind}; +use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind, VectorKey}; use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, ROOT_SPATIAL_NODE_INDEX}; use crate::space::SpaceSnapper; use crate::util::PrimaryArc; @@ -36,6 +36,7 @@ pub struct TextRunKey { pub glyphs: PrimaryArc>, pub shadow: bool, pub requested_raster_space: RasterSpace, + pub reference_frame_relative_offset: VectorKey, } impl TextRunKey { @@ -49,6 +50,7 @@ impl TextRunKey { glyphs: PrimaryArc(text_run.glyphs), shadow: text_run.shadow, requested_raster_space: text_run.requested_raster_space, + reference_frame_relative_offset: text_run.reference_frame_relative_offset.into(), } } } @@ -149,6 +151,7 @@ pub struct TextRun { pub glyphs: Arc>, pub shadow: bool, pub requested_raster_space: RasterSpace, + pub reference_frame_relative_offset: LayoutVector2D, } impl intern::Internable for TextRun { @@ -173,13 +176,12 @@ impl InternablePrimitive for TextRun { key: TextRunKey, data_handle: TextRunDataHandle, prim_store: &mut PrimitiveStore, - reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { let run_index = prim_store.text_runs.push(TextRunPrimitive { used_font: key.font.clone(), glyph_keys_range: storage::Range::empty(), - reference_frame_relative_offset, - snapped_reference_frame_relative_offset: reference_frame_relative_offset, + reference_frame_relative_offset: key.reference_frame_relative_offset.into(), + snapped_reference_frame_relative_offset: key.reference_frame_relative_offset.into(), shadow: key.shadow, raster_scale: 1.0, requested_raster_space: key.requested_raster_space, @@ -215,6 +217,7 @@ impl CreateShadow for TextRun { glyphs: self.glyphs.clone(), shadow: true, requested_raster_space, + reference_frame_relative_offset: self.reference_frame_relative_offset, } } } @@ -497,8 +500,8 @@ fn test_struct_sizes() { // test expectations and move on. // (b) You made a structure larger. This is not necessarily a problem, but should only // be done with care, and after checking if talos performance regresses badly. - assert_eq!(mem::size_of::(), 64, "TextRun size changed"); + assert_eq!(mem::size_of::(), 72, "TextRun size changed"); assert_eq!(mem::size_of::(), 80, "TextRunTemplate size changed"); - assert_eq!(mem::size_of::(), 80, "TextRunKey size changed"); + assert_eq!(mem::size_of::(), 88, "TextRunKey size changed"); assert_eq!(mem::size_of::(), 80, "TextRunPrimitive size changed"); } diff --git a/gfx/wr/webrender/src/scene_building.rs b/gfx/wr/webrender/src/scene_building.rs index 2da8ec88eb9d..8e7f56d9da87 100644 --- a/gfx/wr/webrender/src/scene_building.rs +++ b/gfx/wr/webrender/src/scene_building.rs @@ -89,110 +89,6 @@ use crate::util::{MaxRect, VecHelper}; use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey}; use smallvec::SmallVec; -/// The offset stack for a given reference frame. -struct ReferenceFrameState { - /// A stack of current offsets from the current reference frame scope. - offsets: Vec, -} - -/// Maps from stacking context layout coordinates into reference frame -/// relative coordinates. -struct ReferenceFrameMapper { - /// A stack of reference frame scopes. - frames: Vec, -} - -impl ReferenceFrameMapper { - fn new() -> Self { - ReferenceFrameMapper { - frames: vec![ - ReferenceFrameState { - offsets: vec![ - LayoutVector2D::zero(), - ], - } - ], - } - } - - /// Push a new scope. This resets the current offset to zero, and is - /// used when a new reference frame or iframe is pushed. - fn push_scope(&mut self) { - self.frames.push(ReferenceFrameState { - offsets: vec![ - LayoutVector2D::zero(), - ], - }); - } - - /// Pop a reference frame scope off the stack. - fn pop_scope(&mut self) { - self.frames.pop().unwrap(); - } - - /// Push a new offset for the current scope. This is used when - /// a new stacking context is pushed. - fn push_offset(&mut self, offset: LayoutVector2D) { - let frame = self.frames.last_mut().unwrap(); - let current_offset = *frame.offsets.last().unwrap(); - frame.offsets.push(current_offset + offset); - } - - /// Pop a local stacking context offset from the current scope. - fn pop_offset(&mut self) { - let frame = self.frames.last_mut().unwrap(); - frame.offsets.pop().unwrap(); - } - - /// Retrieve the current offset to allow converting a stacking context - /// relative coordinate to be relative to the owing reference frame. - /// TODO(gw): We could perhaps have separate coordinate spaces for this, - /// however that's going to either mean a lot of changes to - /// public API code, or a lot of changes to internal code. - /// Before doing that, we should revisit how Gecko would - /// prefer to provide coordinates. - /// TODO(gw): For now, this includes only the reference frame relative - /// offset. Soon, we will expand this to include the initial - /// scroll offsets that are now available on scroll nodes. This - /// will allow normalizing the coordinates even between display - /// lists where APZ has scrolled the content. - fn current_offset(&self) -> LayoutVector2D { - *self.frames.last().unwrap().offsets.last().unwrap() - } -} - -/// Offsets primitives (and clips) by the external scroll offset -/// supplied to scroll nodes. -pub struct ScrollOffsetMapper { - pub current_spatial_node: SpatialNodeIndex, - pub current_offset: LayoutVector2D, -} - -impl ScrollOffsetMapper { - fn new() -> Self { - ScrollOffsetMapper { - current_spatial_node: SpatialNodeIndex::INVALID, - current_offset: LayoutVector2D::zero(), - } - } - - /// Return the accumulated external scroll offset for a spatial - /// node. This caches the last result, which is the common case, - /// or defers to the spatial tree to build the value. - fn external_scroll_offset( - &mut self, - spatial_node_index: SpatialNodeIndex, - spatial_tree: &SpatialTree, - ) -> LayoutVector2D { - if spatial_node_index != self.current_spatial_node { - self.current_spatial_node = spatial_node_index; - self.current_offset = spatial_tree.external_scroll_offset(spatial_node_index); - } - - self.current_offset - } -} - /// A data structure that keeps track of mapping between API Ids for spatials and the indices /// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives /// and clips during frame building. @@ -486,12 +382,6 @@ pub struct SceneBuilder<'a> { /// Reference to the set of data that is interned across display lists. interners: &'a mut Interners, - /// Helper struct to map stacking context coords <-> reference frame coords. - rf_mapper: ReferenceFrameMapper, - - /// Helper struct to map spatial nodes to external scroll offsets. - external_scroll_mapper: ScrollOffsetMapper, - /// The current recursion depth of iframes encountered. Used to restrict picture /// caching slices to only the top-level content frame. iframe_size: Vec, @@ -567,8 +457,6 @@ impl<'a> SceneBuilder<'a> { prim_store: PrimitiveStore::new(&stats.prim_store_stats), clip_store: ClipStore::new(&stats.clip_store_stats), interners, - rf_mapper: ReferenceFrameMapper::new(), - external_scroll_mapper: ScrollOffsetMapper::new(), iframe_size: Vec::new(), root_iframe_clip: None, quality_settings: view.quality_settings, @@ -610,28 +498,6 @@ impl<'a> SceneBuilder<'a> { } } - /// Retrieve the current offset to allow converting a stacking context - /// relative coordinate to be relative to the owing reference frame, - /// also considering any external scroll offset on the provided - /// spatial node. - fn current_offset( - &mut self, - spatial_node_index: SpatialNodeIndex, - ) -> LayoutVector2D { - // Get the current offset from stacking context <-> reference frame space. - let rf_offset = self.rf_mapper.current_offset(); - - // Get the external scroll offset, if applicable. - let scroll_offset = self - .external_scroll_mapper - .external_scroll_offset( - spatial_node_index, - &self.spatial_tree, - ); - - rf_offset + scroll_offset - } - fn build_all(&mut self, root_pipeline: &ScenePipeline) { enum ContextKind<'a> { Root, @@ -699,7 +565,6 @@ impl<'a> SceneBuilder<'a> { bc.pipeline_id, ); - self.rf_mapper.push_offset(info.origin.to_vector()); let new_context = BuildContext { pipeline_id: bc.pipeline_id, kind: ContextKind::StackingContext { @@ -717,7 +582,6 @@ impl<'a> SceneBuilder<'a> { profile_scope!("build_reference_frame"); let parent_space = self.get_space(info.parent_spatial_id); let mut subtraversal = item.sub_iter(); - let current_offset = self.current_offset(parent_space); let transform = match info.reference_frame.transform { ReferenceTransformBinding::Static { binding } => binding, @@ -772,10 +636,9 @@ impl<'a> SceneBuilder<'a> { info.reference_frame.transform_style, transform, info.reference_frame.kind, - current_offset + info.origin.to_vector(), + info.origin.to_vector(), ); - self.rf_mapper.push_scope(); let new_context = BuildContext { pipeline_id: bc.pipeline_id, kind: ContextKind::ReferenceFrame, @@ -813,8 +676,6 @@ impl<'a> SceneBuilder<'a> { assert!(self.root_iframe_clip.is_none()); self.root_iframe_clip = Some(clip_chain_id); } - - self.rf_mapper.push_scope(); self.iframe_size.push(size); let new_context = BuildContext { @@ -836,16 +697,11 @@ impl<'a> SceneBuilder<'a> { match bc.kind { ContextKind::Root => {} ContextKind::StackingContext { sc_info } => { - self.rf_mapper.pop_offset(); self.pop_stacking_context(sc_info); } - ContextKind::ReferenceFrame => { - self.rf_mapper.pop_scope(); - } + ContextKind::ReferenceFrame => {} ContextKind::Iframe { parent_traversal } => { self.iframe_size.pop(); - self.rf_mapper.pop_scope(); - self.clip_store.pop_clip_root(); if self.iframe_size.is_empty() { assert!(self.root_iframe_clip.is_some()); @@ -883,10 +739,8 @@ impl<'a> SceneBuilder<'a> { info: &StickyFrameDisplayItem, parent_node_index: SpatialNodeIndex, ) { - let current_offset = self.current_offset(parent_node_index); - let frame_rect = info.bounds.translate(current_offset); let sticky_frame_info = StickyFrameInfo::new( - frame_rect, + info.bounds, info.margins, info.vertical_offset_bounds, info.horizontal_offset_bounds, @@ -907,11 +761,9 @@ impl<'a> SceneBuilder<'a> { parent_node_index: SpatialNodeIndex, pipeline_id: PipelineId, ) { - let current_offset = self.current_offset(parent_node_index); // This is useful when calculating scroll extents for the // SpatialNode::scroll(..) API as well as for properly setting sticky // positioning offsets. - let frame_rect = info.frame_rect.translate(current_offset); let content_size = info.content_rect.size(); self.add_scroll_frame( @@ -919,7 +771,7 @@ impl<'a> SceneBuilder<'a> { parent_node_index, info.external_id, pipeline_id, - &frame_rect, + &info.frame_rect, &content_size, info.scroll_sensitivity, ScrollFrameKind::Explicit, @@ -941,13 +793,10 @@ impl<'a> SceneBuilder<'a> { }, }; - let current_offset = self.current_offset(spatial_node_index); - let clip_rect = info.clip_rect.translate(current_offset); - self.add_rect_clip_node( ClipId::root(iframe_pipeline_id), &info.space_and_clip, - &clip_rect, + &info.clip_rect, ); self.clip_store.push_clip_root( @@ -955,11 +804,6 @@ impl<'a> SceneBuilder<'a> { true, ); - let bounds = self.snap_rect( - &info.bounds.translate(current_offset), - spatial_node_index, - ); - let spatial_node_index = self.push_reference_frame( SpatialId::root_reference_frame(iframe_pipeline_id), Some(spatial_node_index), @@ -970,10 +814,10 @@ impl<'a> SceneBuilder<'a> { is_2d_scale_translation: false, should_snap: false }, - bounds.min.to_vector(), + info.bounds.min.to_vector(), ); - let iframe_rect = LayoutRect::from_size(bounds.size()); + let iframe_rect = LayoutRect::from_size(info.bounds.size()); let is_root_pipeline = self.iframe_size.is_empty(); self.add_scroll_frame( @@ -982,7 +826,7 @@ impl<'a> SceneBuilder<'a> { ExternalScrollId(0, iframe_pipeline_id), iframe_pipeline_id, &iframe_rect, - &bounds.size(), + &info.bounds.size(), ScrollSensitivity::ScriptAndInputEvents, ScrollFrameKind::PipelineRoot { is_root_pipeline, @@ -990,7 +834,7 @@ impl<'a> SceneBuilder<'a> { LayoutVector2D::zero(), ); - Some((bounds.size(), pipeline.display_list.iter())) + Some((info.bounds.size(), pipeline.display_list.iter())) } fn get_space( @@ -1011,44 +855,24 @@ impl<'a> SceneBuilder<'a> { &mut self, common: &CommonItemProperties, bounds: Option<&LayoutRect>, - ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { + ) -> (LayoutPrimitiveInfo, SpatialNodeIndex, ClipChainId) { let spatial_node_index = self.get_space(common.spatial_id); let clip_chain_id = self.get_clip_chain(common.clip_id); - let current_offset = self.current_offset(spatial_node_index); - - let unsnapped_clip_rect = common.clip_rect.translate(current_offset); - let clip_rect = self.snap_rect( - &unsnapped_clip_rect, - spatial_node_index, - ); - - let unsnapped_rect = bounds.map(|bounds| { - bounds.translate(current_offset) - }); - - // If no bounds rect is given, default to clip rect. - let rect = unsnapped_rect.map_or(clip_rect, |bounds| { - self.snap_rect( - &bounds, - spatial_node_index, - ) - }); - let layout = LayoutPrimitiveInfo { - rect, - clip_rect, + rect: bounds.cloned().unwrap_or(common.clip_rect), + clip_rect: common.clip_rect, flags: common.flags, }; - (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id) + (layout, spatial_node_index, clip_chain_id) } fn process_common_properties_with_bounds( &mut self, common: &CommonItemProperties, bounds: &LayoutRect, - ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { + ) -> (LayoutPrimitiveInfo, SpatialNodeIndex, ClipChainId) { self.process_common_properties( common, Some(bounds), @@ -1076,7 +900,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::Image(ref info) => { profile_scope!("image"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1096,14 +920,14 @@ impl<'a> SceneBuilder<'a> { DisplayItem::RepeatingImage(ref info) => { profile_scope!("repeating_image"); - let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let stretch_size = process_repeat_size( &layout.rect, - &unsnapped_rect, + &info.unsnapped_rect, info.stretch_size, ); @@ -1122,7 +946,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::YuvImage(ref info) => { profile_scope!("yuv_image"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1147,7 +971,7 @@ impl<'a> SceneBuilder<'a> { // are subtle interactions between the primitive origin and the glyph offset // which appear to be significant (presumably due to some sort of accumulated // error throughout the layers). We should fix this at some point. - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1160,12 +984,13 @@ impl<'a> SceneBuilder<'a> { &info.color, item.glyphs(), info.glyph_options, + info.reference_frame_relative_offset, ); } DisplayItem::Rectangle(ref info) => { profile_scope!("rect"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1185,7 +1010,7 @@ impl<'a> SceneBuilder<'a> { // TODO(gw): We could skip building the clip-chain here completely, as it's not used by // hit-test items. - let (layout, _, spatial_node_index, _) = self.process_common_properties( + let (layout, spatial_node_index, _) = self.process_common_properties( &info.common, None, ); @@ -1203,7 +1028,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::ClearRectangle(ref info) => { profile_scope!("clear"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1217,7 +1042,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::Line(ref info) => { profile_scope!("line"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.area, ); @@ -1239,14 +1064,14 @@ impl<'a> SceneBuilder<'a> { return; } - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let mut tile_size = process_repeat_size( &layout.rect, - &unsnapped_rect, + &info.unsnapped_rect, info.tile_size, ); @@ -1315,7 +1140,7 @@ impl<'a> SceneBuilder<'a> { return; } - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1326,7 +1151,7 @@ impl<'a> SceneBuilder<'a> { let mut tile_size = process_repeat_size( &layout.rect, - &unsnapped_rect, + &info.unsnapped_rect, info.tile_size, ); @@ -1393,14 +1218,14 @@ impl<'a> SceneBuilder<'a> { return; } - let (mut layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (mut layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); let tile_size = process_repeat_size( &layout.rect, - &unsnapped_rect, + &info.unsnapped_rect, info.tile_size, ); @@ -1438,7 +1263,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::BoxShadow(ref info) => { profile_scope!("box_shadow"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.box_bounds, ); @@ -1458,7 +1283,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::Border(ref info) => { profile_scope!("border"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( &info.common, &info.bounds, ); @@ -1474,18 +1299,10 @@ impl<'a> SceneBuilder<'a> { DisplayItem::ImageMaskClip(ref info) => { profile_scope!("image_clip"); - let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); - let current_offset = self.current_offset(parent_space); - - let image_mask = ImageMask { - rect: info.image_mask.rect.translate(current_offset), - ..info.image_mask - }; - self.add_image_mask_clip_node( info.id, &info.parent_space_and_clip, - &image_mask, + &info.image_mask, info.fill_rule, item.points(), ); @@ -1493,27 +1310,19 @@ impl<'a> SceneBuilder<'a> { DisplayItem::RoundedRectClip(ref info) => { profile_scope!("rounded_clip"); - let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); - let current_offset = self.current_offset(parent_space); - self.add_rounded_rect_clip_node( info.id, &info.parent_space_and_clip, &info.clip, - current_offset, ); } DisplayItem::RectClip(ref info) => { profile_scope!("rect_clip"); - let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); - let current_offset = self.current_offset(parent_space); - let clip_rect = info.clip_rect.translate(current_offset); - self.add_rect_clip_node( info.id, &info.parent_space_and_clip, - &clip_rect, + &info.clip_rect, ); } DisplayItem::ClipChain(ref info) => { @@ -1556,7 +1365,7 @@ impl<'a> SceneBuilder<'a> { DisplayItem::BackdropFilter(ref info) => { profile_scope!("backdrop"); - let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties( + let (layout, spatial_node_index, clip_chain_id) = self.process_common_properties( &info.common, None, ); @@ -1664,7 +1473,6 @@ impl<'a> SceneBuilder<'a> { fn create_primitive

( &mut self, info: &LayoutPrimitiveInfo, - spatial_node_index: SpatialNodeIndex, clip_chain_id: ClipChainId, prim: P, ) -> PrimitiveInstance @@ -1675,7 +1483,6 @@ impl<'a> SceneBuilder<'a> { // Build a primitive key. let prim_key = prim.into_key(info); - let current_offset = self.current_offset(spatial_node_index); let interner = self.interners.as_mut(); let prim_data_handle = interner .intern(&prim_key, || ()); @@ -1684,7 +1491,6 @@ impl<'a> SceneBuilder<'a> { prim_key, prim_data_handle, &mut self.prim_store, - current_offset, ); PrimitiveInstance::new( @@ -1832,7 +1638,6 @@ impl<'a> SceneBuilder<'a> { { let prim_instance = self.create_primitive( info, - spatial_node_index, clip_chain_id, prim, ); @@ -2441,10 +2246,6 @@ impl<'a> SceneBuilder<'a> { ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); - let snapped_mask_rect = self.snap_rect( - &image_mask.rect, - spatial_node_index, - ); let points: Vec = points_range.iter().collect(); // If any points are provided, then intern a polygon with the points and fill rule. @@ -2460,7 +2261,7 @@ impl<'a> SceneBuilder<'a> { } let item = ClipItemKey { - kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect, polygon_handle), + kind: ClipItemKeyKind::image_mask(image_mask, image_mask.rect, polygon_handle), }; let handle = self @@ -2493,13 +2294,8 @@ impl<'a> SceneBuilder<'a> { ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); - let snapped_clip_rect = self.snap_rect( - clip_rect, - spatial_node_index, - ); - let item = ClipItemKey { - kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), + kind: ClipItemKeyKind::rectangle(*clip_rect, ClipMode::Clip), }; let handle = self .interners @@ -2522,22 +2318,17 @@ impl<'a> SceneBuilder<'a> { ); } - pub fn add_rounded_rect_clip_node( + fn add_rounded_rect_clip_node( &mut self, new_node_id: ClipId, space_and_clip: &SpaceAndClipInfo, clip: &ComplexClipRegion, - current_offset: LayoutVector2D, ) { let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); - let snapped_region_rect = self.snap_rect( - &clip.rect.translate(current_offset), - spatial_node_index, - ); let item = ClipItemKey { kind: ClipItemKeyKind::rounded_rect( - snapped_region_rect, + clip.rect, clip.radii, clip.mode, ), @@ -2814,7 +2605,6 @@ impl<'a> SceneBuilder<'a> { // Construct and add a primitive for the given shadow. let shadow_prim_instance = self.create_primitive( &info, - pending_primitive.spatial_node_index, pending_primitive.clip_chain_id, pending_primitive.prim.create_shadow( &pending_shadow.shadow, @@ -3229,9 +3019,8 @@ impl<'a> SceneBuilder<'a> { text_color: &ColorF, glyph_range: ItemRange, glyph_options: Option, + reference_frame_relative_offset: LayoutVector2D, ) { - let offset = self.current_offset(spatial_node_index); - let text_run = { let instance_map = self.font_instances.lock().unwrap(); let font_instance = match instance_map.get(font_instance_key) { @@ -3267,20 +3056,12 @@ impl<'a> SceneBuilder<'a> { flags, ); - // TODO(gw): It'd be nice not to have to allocate here for creating - // the primitive key, when the common case is that the - // hash will match and we won't end up creating a new - // primitive template. - let prim_offset = prim_info.rect.min.to_vector() - offset; - let glyphs = glyph_range - .iter() - .map(|glyph| { - GlyphInstance { - index: glyph.index, - point: glyph.point - prim_offset, - } - }) - .collect(); + // TODO(gw): Although we no longer need to remap the glyph coords here, + // we still allocate. Instead, perhaps we could have a flat + // array of glyphs inside the display list builder, and text + // runs store a glyph range? That would reduce a lot of allocations + // that we do here. + let glyphs = glyph_range.iter().collect(); // Query the current requested raster space (stack handled by push/pop // stacking context). @@ -3294,6 +3075,7 @@ impl<'a> SceneBuilder<'a> { font, shadow: false, requested_raster_space, + reference_frame_relative_offset, } }; @@ -3415,7 +3197,6 @@ impl<'a> SceneBuilder<'a> { // region. By makings sure to include this, the clip chain instance computes the correct clip rect, // but we don't actually apply the filtered backdrop clip yet (this is done to the last instance in // the filter chain below). - backdrop_spatial_node_index, clip_chain_id, Backdrop { pic_index: backdrop_pic_index, diff --git a/gfx/wr/webrender/src/util.rs b/gfx/wr/webrender/src/util.rs index e9d7b1b546aa..a6216319aef8 100644 --- a/gfx/wr/webrender/src/util.rs +++ b/gfx/wr/webrender/src/util.rs @@ -5,7 +5,7 @@ use api::BorderRadius; use api::units::*; use euclid::{Point2D, Rect, Box2D, Size2D, Vector2D, point2}; -use euclid::{default, Transform2D, Transform3D, Scale}; +use euclid::{Transform2D, Transform3D, Scale}; use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; use plane_split::{Clipper, Polygon}; use std::{i32, f32, fmt, ptr}; @@ -15,6 +15,7 @@ use std::os::raw::c_void; use std::sync::Arc; use std::mem::replace; +pub use api::ScaleOffset; // Matches the definition of SK_ScalarNearlyZero in Skia. const NEARLY_ZERO: f32 = 1.0 / 4096.0; @@ -118,253 +119,6 @@ impl VecHelper for Vec { } } - -// Represents an optimized transform where there is only -// a scale and translation (which are guaranteed to maintain -// an axis align rectangle under transformation). The -// scaling is applied first, followed by the translation. -// TODO(gw): We should try and incorporate F <-> T units here, -// but it's a bit tricky to do that now with the -// way the current spatial tree works. -#[derive(Debug, Clone, Copy, MallocSizeOf)] -#[cfg_attr(feature = "capture", derive(Serialize))] -#[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct ScaleOffset { - pub scale: default::Vector2D, - pub offset: default::Vector2D, -} - -impl ScaleOffset { - pub fn identity() -> Self { - ScaleOffset { - scale: Vector2D::new(1.0, 1.0), - offset: Vector2D::zero(), - } - } - - // Construct a ScaleOffset from a transform. Returns - // None if the matrix is not a pure scale / translation. - pub fn from_transform( - m: &Transform3D, - ) -> Option { - - // To check that we have a pure scale / translation: - // Every field must match an identity matrix, except: - // - Any value present in tx,ty - // - Any value present in sx,sy - - if m.m12.abs() > NEARLY_ZERO || - m.m13.abs() > NEARLY_ZERO || - m.m14.abs() > NEARLY_ZERO || - m.m21.abs() > NEARLY_ZERO || - m.m23.abs() > NEARLY_ZERO || - m.m24.abs() > NEARLY_ZERO || - m.m31.abs() > NEARLY_ZERO || - m.m32.abs() > NEARLY_ZERO || - (m.m33 - 1.0).abs() > NEARLY_ZERO || - m.m34.abs() > NEARLY_ZERO || - m.m43.abs() > NEARLY_ZERO || - (m.m44 - 1.0).abs() > NEARLY_ZERO { - return None; - } - - Some(ScaleOffset { - scale: Vector2D::new(m.m11, m.m22), - offset: Vector2D::new(m.m41, m.m42), - }) - } - - pub fn from_offset(offset: default::Vector2D) -> Self { - ScaleOffset { - scale: Vector2D::new(1.0, 1.0), - offset, - } - } - - pub fn from_scale(scale: default::Vector2D) -> Self { - ScaleOffset { - scale, - offset: Vector2D::new(0.0, 0.0), - } - } - - pub fn inverse(&self) -> Self { - ScaleOffset { - scale: Vector2D::new( - 1.0 / self.scale.x, - 1.0 / self.scale.y, - ), - offset: Vector2D::new( - -self.offset.x / self.scale.x, - -self.offset.y / self.scale.y, - ), - } - } - - pub fn offset(&self, offset: default::Vector2D) -> Self { - self.accumulate( - &ScaleOffset { - scale: Vector2D::new(1.0, 1.0), - offset, - } - ) - } - - pub fn scale(&self, scale: f32) -> Self { - self.accumulate( - &ScaleOffset { - scale: Vector2D::new(scale, scale), - offset: Vector2D::zero(), - } - ) - } - - /// Produce a ScaleOffset that includes both self and other. - /// The 'self' ScaleOffset is applied after other. - /// This is equivalent to `Transform3D::pre_transform`. - pub fn accumulate(&self, other: &ScaleOffset) -> Self { - ScaleOffset { - scale: Vector2D::new( - self.scale.x * other.scale.x, - self.scale.y * other.scale.y, - ), - offset: Vector2D::new( - self.offset.x + self.scale.x * other.offset.x, - self.offset.y + self.scale.y * other.offset.y, - ), - } - } - - pub fn map_rect(&self, rect: &Box2D) -> Box2D { - // TODO(gw): The logic below can return an unexpected result if the supplied - // rect is invalid (has size < 0). Since Gecko currently supplied - // invalid rects in some cases, adding a max(0) here ensures that - // mapping an invalid rect retains the property that rect.is_empty() - // will return true (the mapped rect output will have size 0 instead - // of a negative size). In future we could catch / assert / fix - // these invalid rects earlier, and assert here instead. - - let w = rect.width().max(0.0); - let h = rect.height().max(0.0); - - let mut x0 = rect.min.x * self.scale.x + self.offset.x; - let mut y0 = rect.min.y * self.scale.y + self.offset.y; - - let mut sx = w * self.scale.x; - let mut sy = h * self.scale.y; - // Handle negative scale. Previously, branchless float math was used to find the - // min / max vertices and size. However, that sequence of operations was producind - // additional floating point accuracy on android emulator builds, causing one test - // to fail an assert. Instead, we retain the same math as previously, and adjust - // the origin / size if required. - - if self.scale.x < 0.0 { - x0 += sx; - sx = -sx; - } - if self.scale.y < 0.0 { - y0 += sy; - sy = -sy; - } - - Box2D::from_origin_and_size( - Point2D::new(x0, y0), - Size2D::new(sx, sy), - ) - } - - pub fn unmap_rect(&self, rect: &Box2D) -> Box2D { - // TODO(gw): The logic below can return an unexpected result if the supplied - // rect is invalid (has size < 0). Since Gecko currently supplied - // invalid rects in some cases, adding a max(0) here ensures that - // mapping an invalid rect retains the property that rect.is_empty() - // will return true (the mapped rect output will have size 0 instead - // of a negative size). In future we could catch / assert / fix - // these invalid rects earlier, and assert here instead. - - let w = rect.width().max(0.0); - let h = rect.height().max(0.0); - - let mut x0 = (rect.min.x - self.offset.x) / self.scale.x; - let mut y0 = (rect.min.y - self.offset.y) / self.scale.y; - - let mut sx = w / self.scale.x; - let mut sy = h / self.scale.y; - - // Handle negative scale. Previously, branchless float math was used to find the - // min / max vertices and size. However, that sequence of operations was producind - // additional floating point accuracy on android emulator builds, causing one test - // to fail an assert. Instead, we retain the same math as previously, and adjust - // the origin / size if required. - - if self.scale.x < 0.0 { - x0 += sx; - sx = -sx; - } - if self.scale.y < 0.0 { - y0 += sy; - sy = -sy; - } - - Box2D::from_origin_and_size( - Point2D::new(x0, y0), - Size2D::new(sx, sy), - ) - } - - pub fn map_vector(&self, vector: &Vector2D) -> Vector2D { - Vector2D::new( - vector.x * self.scale.x, - vector.y * self.scale.y, - ) - } - - pub fn unmap_vector(&self, vector: &Vector2D) -> Vector2D { - Vector2D::new( - vector.x / self.scale.x, - vector.y / self.scale.y, - ) - } - - pub fn map_point(&self, point: &Point2D) -> Point2D { - Point2D::new( - point.x * self.scale.x + self.offset.x, - point.y * self.scale.y + self.offset.y, - ) - } - - pub fn unmap_point(&self, point: &Point2D) -> Point2D { - Point2D::new( - (point.x - self.offset.x) / self.scale.x, - (point.y - self.offset.y) / self.scale.y, - ) - } - - pub fn to_transform(&self) -> Transform3D { - Transform3D::new( - self.scale.x, - 0.0, - 0.0, - 0.0, - - 0.0, - self.scale.y, - 0.0, - 0.0, - - 0.0, - 0.0, - 1.0, - 0.0, - - self.offset.x, - self.offset.y, - 0.0, - 1.0, - ) - } -} - // TODO: Implement these in euclid! pub trait MatrixHelpers { /// A port of the preserves2dAxisAlignment function in Skia. diff --git a/gfx/wr/webrender_api/src/display_item.rs b/gfx/wr/webrender_api/src/display_item.rs index aa3e15618abd..1c286313e2bc 100644 --- a/gfx/wr/webrender_api/src/display_item.rs +++ b/gfx/wr/webrender_api/src/display_item.rs @@ -391,6 +391,7 @@ pub struct TextDisplayItem { pub font_key: font::FontInstanceKey, pub color: ColorF, pub glyph_options: Option, + pub reference_frame_relative_offset: LayoutVector2D, } // IMPLICIT: glyphs: Vec #[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] @@ -646,6 +647,9 @@ pub struct GradientDisplayItem { /// The space between tiles of the gradient (common case: 0) pub tile_spacing: LayoutSize, pub gradient: Gradient, + /// The unsnapped rect is used for calculating repeats, so is replicated here + /// to maintain existing behavior. + pub unsnapped_rect: LayoutRect, } #[repr(C)] @@ -709,6 +713,9 @@ pub struct RadialGradientDisplayItem { pub gradient: RadialGradient, pub tile_size: LayoutSize, pub tile_spacing: LayoutSize, + /// The unsnapped rect is used for calculating repeats, so is replicated here + /// to maintain existing behavior. + pub unsnapped_rect: LayoutRect, } #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] @@ -721,6 +728,9 @@ pub struct ConicGradientDisplayItem { pub gradient: ConicGradient, pub tile_size: LayoutSize, pub tile_spacing: LayoutSize, + /// The unsnapped rect is used for calculating repeats, so is replicated here + /// to maintain existing behavior. + pub unsnapped_rect: LayoutRect, } /// Renders a filtered region of its backdrop @@ -1307,6 +1317,9 @@ pub struct RepeatingImageDisplayItem { pub alpha_type: AlphaType, /// A hack used by gecko to color a simple bitmap font used for tofu glyphs pub color: ColorF, + /// The unsnapped rect is used for calculating repeats, so is replicated here + /// to maintain existing behavior. + pub unsnapped_rect: LayoutRect, } #[repr(u8)] diff --git a/gfx/wr/webrender_api/src/display_list.rs b/gfx/wr/webrender_api/src/display_list.rs index 2ed9c4fae81e..5248df4ccb08 100644 --- a/gfx/wr/webrender_api/src/display_list.rs +++ b/gfx/wr/webrender_api/src/display_list.rs @@ -26,6 +26,7 @@ use crate::color::ColorF; use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions}; use crate::image::{ColorDepth, ImageKey}; use crate::units::*; +use crate::util::{RectHelpers, ScaleOffset}; // We don't want to push a long text-run. If a text-run is too long, split it into several parts. @@ -1016,6 +1017,26 @@ pub enum DisplayListSection { Chunk, } +/// A small portion of a normal spatial node that we store during DL construction to +/// enable snapping and reference frame <-> stacking context coord mapping. In future +/// we'll aim to remove this and have the full spatial tree available during DL build. +#[derive(Clone)] +pub struct SpatialNodeInfo { + /// The total external scroll offset applicable at this node + accumulated_external_scroll_offset: LayoutVector2D, + /// The 2d-axis-aligned snapping transform, if this node is in the root coord space + snapping_transform: Option, +} + +impl SpatialNodeInfo { + fn identity() -> Self { + SpatialNodeInfo { + accumulated_external_scroll_offset: LayoutVector2D::zero(), + snapping_transform: Some(ScaleOffset::identity()), + } + } +} + #[derive(Clone)] pub struct DisplayListBuilder { payload: DisplayListPayload, @@ -1033,6 +1054,12 @@ pub struct DisplayListBuilder { cache_size: usize, serialized_content_buffer: Option, + + /// Helper struct to map stacking context coords <-> reference frame coords. + rf_mapper: ReferenceFrameMapper, + + /// Minimal info about encountered spatial nodes to allow snapping during DL building + spatial_nodes: Vec, } #[repr(C)] @@ -1078,6 +1105,9 @@ impl DisplayListBuilder { save_state: None, cache_size: 0, serialized_content_buffer: None, + + rf_mapper: ReferenceFrameMapper::new(), + spatial_nodes: vec![SpatialNodeInfo::identity(); FIRST_SPATIAL_NODE_INDEX + 1], } } @@ -1260,14 +1290,61 @@ impl DisplayListBuilder { Self::push_iter_impl(&mut buffer, iter); } + /// Apply snapping and coord space mapping to item props + fn process_common_props( + &mut self, + common: &di::CommonItemProperties, + ) -> di::CommonItemProperties { + let current_offset = self.current_offset(common.spatial_id); + + let clip_rect = self.snap_rect( + &common.clip_rect.translate(current_offset), + common.spatial_id, + ); + + di::CommonItemProperties { + clip_rect, + ..*common + } + } + + /// Apply snapping and coord space mapping to item props + prim bounds + fn process_common_props_with_bounds( + &mut self, + bounds: &LayoutRect, + common: &di::CommonItemProperties, + ) -> (LayoutRect, di::CommonItemProperties) { + let current_offset = self.current_offset(common.spatial_id); + + let clip_rect = self.snap_rect( + &common.clip_rect.translate(current_offset), + common.spatial_id, + ); + + let bounds = self.snap_rect( + &bounds.translate(current_offset), + common.spatial_id, + ); + + ( + bounds, + di::CommonItemProperties { + clip_rect, + ..*common + } + ) + } + pub fn push_rect( &mut self, common: &di::CommonItemProperties, bounds: LayoutRect, color: ColorF, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { - common: *common, + common, color: PropertyBinding::Value(color), bounds, }); @@ -1280,8 +1357,10 @@ impl DisplayListBuilder { bounds: LayoutRect, color: PropertyBinding, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { - common: *common, + common, color, bounds, }); @@ -1293,8 +1372,10 @@ impl DisplayListBuilder { common: &di::CommonItemProperties, bounds: LayoutRect, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::ClearRectangle(di::ClearRectangleDisplayItem { - common: *common, + common, bounds, }); self.push_item(&item); @@ -1305,8 +1386,10 @@ impl DisplayListBuilder { common: &di::CommonItemProperties, tag: di::ItemTag, ) { + let common = self.process_common_props(common); + let item = di::DisplayItem::HitTest(di::HitTestDisplayItem { - common: *common, + common, tag, }); self.push_item(&item); @@ -1321,8 +1404,10 @@ impl DisplayListBuilder { color: &ColorF, style: di::LineStyle, ) { + let common = self.process_common_props(common); + let item = di::DisplayItem::Line(di::LineDisplayItem { - common: *common, + common, area: *area, wavy_line_thickness, orientation, @@ -1342,8 +1427,10 @@ impl DisplayListBuilder { key: ImageKey, color: ColorF, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::Image(di::ImageDisplayItem { - common: *common, + common, bounds, image_key: key, image_rendering, @@ -1357,7 +1444,7 @@ impl DisplayListBuilder { pub fn push_repeating_image( &mut self, common: &di::CommonItemProperties, - bounds: LayoutRect, + rect: LayoutRect, stretch_size: LayoutSize, tile_spacing: LayoutSize, image_rendering: di::ImageRendering, @@ -1365,8 +1452,10 @@ impl DisplayListBuilder { key: ImageKey, color: ColorF, ) { + let (bounds, common) = self.process_common_props_with_bounds(&rect, common); + let item = di::DisplayItem::RepeatingImage(di::RepeatingImageDisplayItem { - common: *common, + common, bounds, image_key: key, stretch_size, @@ -1374,6 +1463,7 @@ impl DisplayListBuilder { image_rendering, alpha_type, color, + unsnapped_rect: rect, }); self.push_item(&item); @@ -1390,8 +1480,10 @@ impl DisplayListBuilder { color_range: di::ColorRange, image_rendering: di::ImageRendering, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::YuvImage(di::YuvImageDisplayItem { - common: *common, + common, bounds, yuv_data, color_depth, @@ -1411,17 +1503,29 @@ impl DisplayListBuilder { color: ColorF, glyph_options: Option, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + + let current_offset = self.current_offset(common.spatial_id); + let item = di::DisplayItem::Text(di::TextDisplayItem { - common: *common, + common, bounds, color, font_key, glyph_options, + reference_frame_relative_offset: current_offset, }); + let prim_offset = bounds.min.to_vector() - current_offset; + for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) { self.push_item(&item); - self.push_iter(split_glyphs); + self.push_iter(split_glyphs.iter().map(|glyph| { + GlyphInstance { + index: glyph.index, + point: glyph.point - prim_offset, + } + })); } } @@ -1477,8 +1581,10 @@ impl DisplayListBuilder { widths: LayoutSideOffsets, details: di::BorderDetails, ) { + let (bounds, common) = self.process_common_props_with_bounds(&bounds, common); + let item = di::DisplayItem::Border(di::BorderDisplayItem { - common: *common, + common, bounds, details, widths, @@ -1498,8 +1604,10 @@ impl DisplayListBuilder { border_radius: di::BorderRadius, clip_mode: di::BoxShadowClipMode, ) { + let (box_bounds, common) = self.process_common_props_with_bounds(&box_bounds, common); + let item = di::DisplayItem::BoxShadow(di::BoxShadowDisplayItem { - common: *common, + common, box_bounds, offset, color, @@ -1529,17 +1637,20 @@ impl DisplayListBuilder { pub fn push_gradient( &mut self, common: &di::CommonItemProperties, - bounds: LayoutRect, + rect: LayoutRect, gradient: di::Gradient, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { + let (bounds, common) = self.process_common_props_with_bounds(&rect, common); + let item = di::DisplayItem::Gradient(di::GradientDisplayItem { - common: *common, + common, bounds, gradient, tile_size, tile_spacing, + unsnapped_rect: rect, }); self.push_item(&item); @@ -1551,17 +1662,20 @@ impl DisplayListBuilder { pub fn push_radial_gradient( &mut self, common: &di::CommonItemProperties, - bounds: LayoutRect, + rect: LayoutRect, gradient: di::RadialGradient, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { + let (bounds, common) = self.process_common_props_with_bounds(&rect, common); + let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem { - common: *common, + common, bounds, gradient, tile_size, tile_spacing, + unsnapped_rect: rect, }); self.push_item(&item); @@ -1573,17 +1687,20 @@ impl DisplayListBuilder { pub fn push_conic_gradient( &mut self, common: &di::CommonItemProperties, - bounds: LayoutRect, + rect: LayoutRect, gradient: di::ConicGradient, tile_size: LayoutSize, tile_spacing: LayoutSize, ) { + let (bounds, common) = self.process_common_props_with_bounds(&rect, common); + let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem { - common: *common, + common, bounds, gradient, tile_size, tile_spacing, + unsnapped_rect: rect, }); self.push_item(&item); @@ -1599,6 +1716,39 @@ impl DisplayListBuilder { ) -> di::SpatialId { let id = self.generate_spatial_index(); + let current_offset = self.current_offset(parent_spatial_id); + let origin = origin + current_offset; + + let parent = &self.spatial_nodes[parent_spatial_id.0]; + + let snapping_transform = parent.snapping_transform.and_then(|parent| { + let snapping_transform = match transform { + PropertyBinding::Value(ref value) => { + // We can only get a ScaleOffset if the transform is 2d axis + // aligned. + ScaleOffset::from_transform(value).map(|scale_offset| { + ScaleOffset::from_offset(origin.to_vector().to_untyped()) + .accumulate(&scale_offset) + }) + } + + // Assume animations start at the identity transform for snapping purposes. + // We still want to incorporate the reference frame offset however. + // TODO(aosmond): Is there a better known starting point? + PropertyBinding::Binding(..) => { + Some(ScaleOffset::from_offset(origin.to_vector().to_untyped())) + } + }; + + snapping_transform.map(|ref s| parent.accumulate(s)) + }); + + self.add_spatial_node_info( + id, + LayoutVector2D::zero(), + snapping_transform, + ); + let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { parent_spatial_id, origin, @@ -1612,6 +1762,7 @@ impl DisplayListBuilder { }, }); + self.rf_mapper.push_scope(); self.push_item(&item); id } @@ -1626,6 +1777,9 @@ impl DisplayListBuilder { ) -> di::SpatialId { let id = self.generate_spatial_index(); + let current_offset = self.current_offset(parent_spatial_id); + let origin = origin + current_offset; + let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { parent_spatial_id, origin, @@ -1649,6 +1803,7 @@ impl DisplayListBuilder { } pub fn pop_reference_frame(&mut self) { + self.rf_mapper.pop_scope(); self.push_item(&di::DisplayItem::PopReferenceFrame); } @@ -1668,8 +1823,10 @@ impl DisplayListBuilder { ) { self.push_filters(filters, filter_datas, filter_primitives); + let current_offset = self.current_offset(spatial_id); + let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem { - origin, + origin: origin + current_offset, spatial_id, prim_flags, stacking_context: di::StackingContext { @@ -1681,6 +1838,8 @@ impl DisplayListBuilder { }, }); + self.rf_mapper.push_offset(origin.to_vector()); + self.push_item(&item); } @@ -1727,6 +1886,7 @@ impl DisplayListBuilder { } pub fn pop_stacking_context(&mut self) { + self.rf_mapper.pop_offset(); self.push_item(&di::DisplayItem::PopStackingContext); } @@ -1745,10 +1905,12 @@ impl DisplayListBuilder { filter_datas: &[di::FilterData], filter_primitives: &[di::FilterPrimitive], ) { + let common = self.process_common_props(common); + self.push_filters(filters, filter_datas, filter_primitives); let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem { - common: *common, + common, }); self.push_item(&item); } @@ -1807,6 +1969,19 @@ impl DisplayListBuilder { external_scroll_offset: LayoutVector2D, ) -> di::SpatialId { let scroll_frame_id = self.generate_spatial_index(); + + let current_offset = self.current_offset(parent_space); + let content_rect = content_rect.translate(current_offset); + let frame_rect = frame_rect.translate(current_offset); + + let parent = self.spatial_nodes[parent_space.0].clone(); + + self.add_spatial_node_info( + scroll_frame_id, + parent.accumulated_external_scroll_offset + external_scroll_offset, + parent.snapping_transform, + ); + let item = di::DisplayItem::ScrollFrame(di::ScrollFrameDisplayItem { content_rect, frame_rect, @@ -1840,11 +2015,18 @@ impl DisplayListBuilder { pub fn define_clip_image_mask( &mut self, parent_space_and_clip: &di::SpaceAndClipInfo, - image_mask: di::ImageMask, + mut image_mask: di::ImageMask, points: &[LayoutPoint], fill_rule: di::FillRule, ) -> di::ClipId { let id = self.generate_clip_index(); + + let current_offset = self.current_offset(parent_space_and_clip.spatial_id); + image_mask.rect = self.snap_rect( + &image_mask.rect.translate(current_offset), + parent_space_and_clip.spatial_id, + ); + let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem { id, parent_space_and_clip: *parent_space_and_clip, @@ -1870,6 +2052,14 @@ impl DisplayListBuilder { clip_rect: LayoutRect, ) -> di::ClipId { let id = self.generate_clip_index(); + + let current_offset = self.current_offset(parent_space_and_clip.spatial_id); + + let clip_rect = self.snap_rect( + &clip_rect.translate(current_offset), + parent_space_and_clip.spatial_id, + ); + let item = di::DisplayItem::RectClip(di::RectClipDisplayItem { id, parent_space_and_clip: *parent_space_and_clip, @@ -1883,9 +2073,16 @@ impl DisplayListBuilder { pub fn define_clip_rounded_rect( &mut self, parent_space_and_clip: &di::SpaceAndClipInfo, - clip: di::ComplexClipRegion, + mut clip: di::ComplexClipRegion, ) -> di::ClipId { let id = self.generate_clip_index(); + + let current_offset = self.current_offset(parent_space_and_clip.spatial_id); + clip.rect = self.snap_rect( + &clip.rect.translate(current_offset), + parent_space_and_clip.spatial_id, + ); + let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem { id, parent_space_and_clip: *parent_space_and_clip, @@ -1906,6 +2103,18 @@ impl DisplayListBuilder { previously_applied_offset: LayoutVector2D, ) -> di::SpatialId { let id = self.generate_spatial_index(); + + let current_offset = self.current_offset(parent_spatial_id); + let frame_rect = frame_rect.translate(current_offset); + + let parent = self.spatial_nodes[parent_spatial_id.0].clone(); + + self.add_spatial_node_info( + id, + parent.accumulated_external_scroll_offset, + parent.snapping_transform, + ); + let item = di::DisplayItem::StickyFrame(di::StickyFrameDisplayItem { parent_spatial_id, id, @@ -1928,6 +2137,18 @@ impl DisplayListBuilder { pipeline_id: PipelineId, ignore_missing_pipeline: bool ) { + let current_offset = self.current_offset(space_and_clip.spatial_id); + + let bounds = self.snap_rect( + &bounds.translate(current_offset), + space_and_clip.spatial_id, + ); + + let clip_rect = self.snap_rect( + &clip_rect.translate(current_offset), + space_and_clip.spatial_id, + ); + let item = di::DisplayItem::Iframe(di::IframeDisplayItem { bounds, clip_rect, @@ -2047,4 +2268,124 @@ impl DisplayListBuilder { }, ) } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame, + /// also considering any external scroll offset on the provided + /// spatial node. + fn current_offset( + &mut self, + spatial_id: di::SpatialId, + ) -> LayoutVector2D { + // Get the current offset from stacking context <-> reference frame space. + let rf_offset = self.rf_mapper.current_offset(); + + // Get the external scroll offset, if applicable. + let scroll_offset = self.spatial_nodes[spatial_id.0].accumulated_external_scroll_offset; + + rf_offset + scroll_offset + } + + /// Add info about a spatial node that is needed during DL building. + fn add_spatial_node_info( + &mut self, + id: di::SpatialId, + accumulated_external_scroll_offset: LayoutVector2D, + snapping_transform: Option, + ) { + self.spatial_nodes.resize(id.0 + 1, SpatialNodeInfo::identity()); + + let info = &mut self.spatial_nodes[id.0]; + info.accumulated_external_scroll_offset = accumulated_external_scroll_offset; + info.snapping_transform = snapping_transform; + } + + /// Snap a local rect, if applicable + fn snap_rect( + &self, + rect: &LayoutRect, + spatial_id: di::SpatialId, + ) -> LayoutRect { + match self.spatial_nodes[spatial_id.0].snapping_transform { + Some(ref scale_offset) => { + let snapped_device_rect: LayoutRect = scale_offset.map_rect(rect).snap(); + scale_offset.unmap_rect(&snapped_device_rect) + } + None => *rect, + } + } +} + +/// The offset stack for a given reference frame. +#[derive(Clone)] +struct ReferenceFrameState { + /// A stack of current offsets from the current reference frame scope. + offsets: Vec, +} + +/// Maps from stacking context layout coordinates into reference frame +/// relative coordinates. +#[derive(Clone)] +struct ReferenceFrameMapper { + /// A stack of reference frame scopes. + frames: Vec, +} + +impl ReferenceFrameMapper { + fn new() -> Self { + ReferenceFrameMapper { + frames: vec![ + ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + } + ], + } + } + + /// Push a new scope. This resets the current offset to zero, and is + /// used when a new reference frame or iframe is pushed. + fn push_scope(&mut self) { + self.frames.push(ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + }); + } + + /// Pop a reference frame scope off the stack. + fn pop_scope(&mut self) { + self.frames.pop().unwrap(); + } + + /// Push a new offset for the current scope. This is used when + /// a new stacking context is pushed. + fn push_offset(&mut self, offset: LayoutVector2D) { + let frame = self.frames.last_mut().unwrap(); + let current_offset = *frame.offsets.last().unwrap(); + frame.offsets.push(current_offset + offset); + } + + /// Pop a local stacking context offset from the current scope. + fn pop_offset(&mut self) { + let frame = self.frames.last_mut().unwrap(); + frame.offsets.pop().unwrap(); + } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame. + /// TODO(gw): We could perhaps have separate coordinate spaces for this, + /// however that's going to either mean a lot of changes to + /// public API code, or a lot of changes to internal code. + /// Before doing that, we should revisit how Gecko would + /// prefer to provide coordinates. + /// TODO(gw): For now, this includes only the reference frame relative + /// offset. Soon, we will expand this to include the initial + /// scroll offsets that are now available on scroll nodes. This + /// will allow normalizing the coordinates even between display + /// lists where APZ has scrolled the content. + fn current_offset(&self) -> LayoutVector2D { + *self.frames.last().unwrap().offsets.last().unwrap() + } } diff --git a/gfx/wr/webrender_api/src/lib.rs b/gfx/wr/webrender_api/src/lib.rs index a60ce239c564..7ff8b91241dd 100644 --- a/gfx/wr/webrender_api/src/lib.rs +++ b/gfx/wr/webrender_api/src/lib.rs @@ -48,6 +48,7 @@ mod font; mod gradient_builder; mod image; pub mod units; +mod util; pub use crate::color::*; pub use crate::display_item::*; @@ -56,6 +57,7 @@ pub use crate::display_list::*; pub use crate::font::*; pub use crate::gradient_builder::*; pub use crate::image::*; +pub use crate::util::ScaleOffset; use crate::units::*; use crate::channel::Receiver; diff --git a/gfx/wr/webrender_api/src/util.rs b/gfx/wr/webrender_api/src/util.rs new file mode 100644 index 000000000000..4d68bc149e17 --- /dev/null +++ b/gfx/wr/webrender_api/src/util.rs @@ -0,0 +1,266 @@ +/* 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 euclid::{Point2D, Box2D, Size2D, Vector2D}; +use euclid::{default, Transform3D}; + +// Matches the definition of SK_ScalarNearlyZero in Skia. +const NEARLY_ZERO: f32 = 1.0 / 4096.0; + +// Represents an optimized transform where there is only +// a scale and translation (which are guaranteed to maintain +// an axis align rectangle under transformation). The +// scaling is applied first, followed by the translation. +// TODO(gw): We should try and incorporate F <-> T units here, +// but it's a bit tricky to do that now with the +// way the current spatial tree works. +#[derive(Debug, Clone, Copy, MallocSizeOf)] +#[derive(Serialize, Deserialize)] +pub struct ScaleOffset { + pub scale: default::Vector2D, + pub offset: default::Vector2D, +} + +impl ScaleOffset { + pub fn identity() -> Self { + ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset: Vector2D::zero(), + } + } + + // Construct a ScaleOffset from a transform. Returns + // None if the matrix is not a pure scale / translation. + pub fn from_transform( + m: &Transform3D, + ) -> Option { + // To check that we have a pure scale / translation: + // Every field must match an identity matrix, except: + // - Any value present in tx,ty + // - Any value present in sx,sy + + if m.m12.abs() > NEARLY_ZERO || + m.m13.abs() > NEARLY_ZERO || + m.m14.abs() > NEARLY_ZERO || + m.m21.abs() > NEARLY_ZERO || + m.m23.abs() > NEARLY_ZERO || + m.m24.abs() > NEARLY_ZERO || + m.m31.abs() > NEARLY_ZERO || + m.m32.abs() > NEARLY_ZERO || + (m.m33 - 1.0).abs() > NEARLY_ZERO || + m.m34.abs() > NEARLY_ZERO || + m.m43.abs() > NEARLY_ZERO || + (m.m44 - 1.0).abs() > NEARLY_ZERO { + return None; + } + + Some(ScaleOffset { + scale: Vector2D::new(m.m11, m.m22), + offset: Vector2D::new(m.m41, m.m42), + }) + } + + pub fn from_offset(offset: default::Vector2D) -> Self { + ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset, + } + } + + pub fn from_scale(scale: default::Vector2D) -> Self { + ScaleOffset { + scale, + offset: Vector2D::new(0.0, 0.0), + } + } + + pub fn inverse(&self) -> Self { + ScaleOffset { + scale: Vector2D::new( + 1.0 / self.scale.x, + 1.0 / self.scale.y, + ), + offset: Vector2D::new( + -self.offset.x / self.scale.x, + -self.offset.y / self.scale.y, + ), + } + } + + pub fn offset(&self, offset: default::Vector2D) -> Self { + self.accumulate( + &ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset, + } + ) + } + + pub fn scale(&self, scale: f32) -> Self { + self.accumulate( + &ScaleOffset { + scale: Vector2D::new(scale, scale), + offset: Vector2D::zero(), + } + ) + } + + /// Produce a ScaleOffset that includes both self and other. + /// The 'self' ScaleOffset is applied after other. + /// This is equivalent to `Transform3D::pre_transform`. + pub fn accumulate(&self, other: &ScaleOffset) -> Self { + ScaleOffset { + scale: Vector2D::new( + self.scale.x * other.scale.x, + self.scale.y * other.scale.y, + ), + offset: Vector2D::new( + self.offset.x + self.scale.x * other.offset.x, + self.offset.y + self.scale.y * other.offset.y, + ), + } + } + + pub fn map_rect(&self, rect: &Box2D) -> Box2D { + // TODO(gw): The logic below can return an unexpected result if the supplied + // rect is invalid (has size < 0). Since Gecko currently supplied + // invalid rects in some cases, adding a max(0) here ensures that + // mapping an invalid rect retains the property that rect.is_empty() + // will return true (the mapped rect output will have size 0 instead + // of a negative size). In future we could catch / assert / fix + // these invalid rects earlier, and assert here instead. + + let w = rect.width().max(0.0); + let h = rect.height().max(0.0); + + let mut x0 = rect.min.x * self.scale.x + self.offset.x; + let mut y0 = rect.min.y * self.scale.y + self.offset.y; + + let mut sx = w * self.scale.x; + let mut sy = h * self.scale.y; + // Handle negative scale. Previously, branchless float math was used to find the + // min / max vertices and size. However, that sequence of operations was producind + // additional floating point accuracy on android emulator builds, causing one test + // to fail an assert. Instead, we retain the same math as previously, and adjust + // the origin / size if required. + + if self.scale.x < 0.0 { + x0 += sx; + sx = -sx; + } + if self.scale.y < 0.0 { + y0 += sy; + sy = -sy; + } + + Box2D::from_origin_and_size( + Point2D::new(x0, y0), + Size2D::new(sx, sy), + ) + } + + pub fn unmap_rect(&self, rect: &Box2D) -> Box2D { + // TODO(gw): The logic below can return an unexpected result if the supplied + // rect is invalid (has size < 0). Since Gecko currently supplied + // invalid rects in some cases, adding a max(0) here ensures that + // mapping an invalid rect retains the property that rect.is_empty() + // will return true (the mapped rect output will have size 0 instead + // of a negative size). In future we could catch / assert / fix + // these invalid rects earlier, and assert here instead. + + let w = rect.width().max(0.0); + let h = rect.height().max(0.0); + + let mut x0 = (rect.min.x - self.offset.x) / self.scale.x; + let mut y0 = (rect.min.y - self.offset.y) / self.scale.y; + + let mut sx = w / self.scale.x; + let mut sy = h / self.scale.y; + + // Handle negative scale. Previously, branchless float math was used to find the + // min / max vertices and size. However, that sequence of operations was producind + // additional floating point accuracy on android emulator builds, causing one test + // to fail an assert. Instead, we retain the same math as previously, and adjust + // the origin / size if required. + + if self.scale.x < 0.0 { + x0 += sx; + sx = -sx; + } + if self.scale.y < 0.0 { + y0 += sy; + sy = -sy; + } + + Box2D::from_origin_and_size( + Point2D::new(x0, y0), + Size2D::new(sx, sy), + ) + } + + pub fn map_vector(&self, vector: &Vector2D) -> Vector2D { + Vector2D::new( + vector.x * self.scale.x, + vector.y * self.scale.y, + ) + } + + pub fn unmap_vector(&self, vector: &Vector2D) -> Vector2D { + Vector2D::new( + vector.x / self.scale.x, + vector.y / self.scale.y, + ) + } + + pub fn map_point(&self, point: &Point2D) -> Point2D { + Point2D::new( + point.x * self.scale.x + self.offset.x, + point.y * self.scale.y + self.offset.y, + ) + } + + pub fn unmap_point(&self, point: &Point2D) -> Point2D { + Point2D::new( + (point.x - self.offset.x) / self.scale.x, + (point.y - self.offset.y) / self.scale.y, + ) + } + + pub fn to_transform(&self) -> Transform3D { + Transform3D::new( + self.scale.x, + 0.0, + 0.0, + 0.0, + + 0.0, + self.scale.y, + 0.0, + 0.0, + + 0.0, + 0.0, + 1.0, + 0.0, + + self.offset.x, + self.offset.y, + 0.0, + 1.0, + ) + } +} + +pub trait RectHelpers +where + Self: Sized, +{ + fn snap(&self) -> Self; +} + +impl RectHelpers for Box2D { + fn snap(&self) -> Self { + self.round() + } +} diff --git a/gfx/wr/wrench/reftests/gradient/reftest.list b/gfx/wr/wrench/reftests/gradient/reftest.list index 84cce84fa87b..58e49e89cf6c 100644 --- a/gfx/wr/wrench/reftests/gradient/reftest.list +++ b/gfx/wr/wrench/reftests/gradient/reftest.list @@ -76,7 +76,7 @@ fuzzy(1,7) == tiling-conic-3.yaml tiling-conic-3-ref.yaml == radial-zero-size-2.yaml radial-zero-size-ref.yaml == radial-zero-size-3.yaml radial-zero-size-ref.yaml -== linear-adjust-tile-size.yaml linear-adjust-tile-size-ref.yaml +fuzzy(2,34) == linear-adjust-tile-size.yaml linear-adjust-tile-size-ref.yaml == linear-repeat-clip.yaml linear-repeat-clip-ref.yaml platform(linux,mac) == linear-aligned-border-radius.yaml linear-aligned-border-radius.png