diff --git a/gfx/wr/webrender/src/prim_store/mod.rs b/gfx/wr/webrender/src/prim_store/mod.rs index 7bd512f7f275..a2030a3cdcc7 100644 --- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -17,7 +17,7 @@ use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipIte use debug_colors; use debug_render::DebugItem; use display_list_flattener::{CreateShadow, IsVisible}; -use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D}; +use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D, TypedPoint2D}; use euclid::approxeq::ApproxEq; use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState}; use frame_builder::{FrameVisibilityContext, FrameVisibilityState}; @@ -50,7 +50,7 @@ use std::{cmp, fmt, hash, ops, u32, usize, mem}; use std::sync::atomic::{AtomicUsize, Ordering}; use storage; use texture_cache::TEXTURE_REGION_DIMENSIONS; -use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler, TransformedRectKind}; +use util::{ScaleOffset, MatrixHelpers, MaxRect, Recycler}; use util::{pack_as_float, project_rect, raster_rect_to_device_pixels}; use util::{scale_factors, clamp_to_scale_factor}; use internal_types::LayoutPrimitiveInfo; @@ -1371,6 +1371,18 @@ pub struct PrimitiveVisibility { /// The current combined local clip for this primitive, from /// the primitive local clip above and the current clip chain. pub combined_local_clip_rect: LayoutRect, + + /// The snap offsets in device space for this primitive. They are + /// generated based on the visible rect, which is the local rect + /// clipped by the combined local clip for most primitives, or + /// just the local rect for pictures. + pub snap_offsets: SnapOffsets, + + /// The snap offsets in device space for the drop shadow for + /// picture primitives, if applicable. Similar to snap offsets, + /// they are generated based on the local rect translated by the + /// drop shadow offset. + pub shadow_snap_offsets: SnapOffsets, } #[derive(Clone, Debug)] @@ -1765,6 +1777,11 @@ impl PrimitiveStore { frame_context.clip_scroll_tree, ); + let mut map_local_to_raster = SpaceMapper::new( + surface.raster_spatial_node_index, + RasterRect::max_rect(), + ); + let mut surface_rect = PictureRect::zero(); for prim_instance in &mut prim_list.prim_instances { @@ -1788,7 +1805,12 @@ impl PrimitiveStore { frame_context.clip_scroll_tree, ); - let (is_passthrough, prim_local_rect) = match prim_instance.kind { + map_local_to_raster.set_target_spatial_node( + prim_instance.spatial_node_index, + frame_context.clip_scroll_tree, + ); + + let (is_passthrough, snap_to_visible, prim_local_rect, prim_shadow_rect) = match prim_instance.kind { PrimitiveInstanceKind::Picture { pic_index, .. } => { let is_composite = { let pic = &self.pictures[pic_index.0]; @@ -1841,33 +1863,28 @@ impl PrimitiveStore { frame_state.clip_chain_stack.pop_clip(); } - let pic_visible_rect = match pic.raster_config { - Some(ref rc) => { - let visible_rect = match rc.composite_mode { - // If we have a drop shadow filter, we also need to include the shadow in - // our local rect for the purpose of calculating the size of the picture. - PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => { - pic.snapped_local_rect.translate(&offset).union(&pic.snapped_local_rect) - } - _ => pic.snapped_local_rect, - }; - - map_local_to_surface.map(&visible_rect) + let shadow_rect = match pic.raster_config { + Some(ref rc) => match rc.composite_mode { + // If we have a drop shadow filter, we also need to include the shadow in + // our local rect for the purpose of calculating the size of the picture. + PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) => pic.snapped_local_rect.translate(&offset), + _ => LayoutRect::zero(), + } + None => { + if let Some(ref rect) = pic_surface_rect { + surface_rect = surface_rect.union(rect); + } + LayoutRect::zero() } - None => pic_surface_rect, }; - if let Some(ref rect) = pic_visible_rect { - surface_rect = surface_rect.union(rect); - } - if prim_instance.is_chased() { if pic.unsnapped_local_rect != pic.snapped_local_rect { println!("\tsnapped from {:?} to {:?}", pic.unsnapped_local_rect, pic.snapped_local_rect); } } - (pic.raster_config.is_none(), pic.snapped_local_rect) + (pic.raster_config.is_none(), false, pic.snapped_local_rect, shadow_rect) } _ => { let prim_data = &frame_state.data_stores.as_common_data(&prim_instance); @@ -1877,14 +1894,7 @@ impl PrimitiveStore { prim_data.prim_size, ); - let visible_rect = prim_instance.local_clip_rect - .intersection(&prim_rect) - .unwrap_or(LayoutRect::zero()); - if let Some(rect) = map_local_to_surface.map(&visible_rect) { - surface_rect = surface_rect.union(&rect); - } - - (false, prim_rect) + (false, true, prim_rect, LayoutRect::zero()) } }; @@ -1897,6 +1907,8 @@ impl PrimitiveStore { clip_chain: ClipChainInstance::empty(), clip_task_index: ClipTaskIndex::INVALID, combined_local_clip_rect: LayoutRect::zero(), + snap_offsets: SnapOffsets::empty(), + shadow_snap_offsets: SnapOffsets::empty(), } ); @@ -1991,12 +2003,6 @@ impl PrimitiveStore { ); } - let combined_local_clip_rect = if apply_local_clip_rect { - clip_chain.local_clip_rect - } else { - prim_instance.local_clip_rect - }; - // Check if the clip bounding rect (in pic space) is visible on screen // This includes both the prim bounding rect + local prim clip rect! let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) { @@ -2013,6 +2019,55 @@ impl PrimitiveStore { } }; + let combined_local_clip_rect = if apply_local_clip_rect { + clip_chain.local_clip_rect + } else { + prim_instance.local_clip_rect + }; + + // All pictures must snap to their primitive rect instead of the + // visible rect like most primitives. This is because the picture's + // visible rect includes the effect of the picture's clip rect, + // which was not considered by the picture's children. The primitive + // rect however is simply the union of the visible rect of the + // children, which they snapped to, which is precisely what we also + // need to snap to in order to be consistent. + let visible_rect = if snap_to_visible { + combined_local_clip_rect + .intersection(&prim_local_rect) + .unwrap_or(LayoutRect::zero()) + } else { + prim_local_rect + }; + + // This is how primitives get snapped. In general, snapping a picture's + // visible rect here will have no effect, but if it is rasterized in its + // own space, or it has a blur or drop shadow effect applied, it may + // provide a snapping offset. + let (snapped_visible_rect, snap_offsets) = get_snapped_rect( + visible_rect, + &map_local_to_raster, + surface.device_pixel_scale, + ).unwrap_or((visible_rect, SnapOffsets::empty())); + + let (combined_visible_rect, shadow_snap_offsets) = if !prim_shadow_rect.is_empty() { + let (snapped_shadow_rect, shadow_snap_offsets) = get_snapped_rect( + prim_shadow_rect, + &map_local_to_raster, + surface.device_pixel_scale, + ).unwrap_or((prim_shadow_rect, SnapOffsets::empty())); + + (snapped_visible_rect.union(&snapped_shadow_rect), shadow_snap_offsets) + } else { + (snapped_visible_rect, SnapOffsets::empty()) + }; + + // Include the snapped primitive/picture local rect, including any shadows, + // in the area affected by the surface. + if let Some(rect) = map_local_to_surface.map(&combined_visible_rect) { + surface_rect = surface_rect.union(&rect); + } + // When the debug display is enabled, paint a colored rectangle around each // primitive. if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) { @@ -2043,6 +2098,8 @@ impl PrimitiveStore { clip_chain, clip_task_index: ClipTaskIndex::INVALID, combined_local_clip_rect, + snap_offsets, + shadow_snap_offsets, } ); @@ -3726,13 +3783,13 @@ fn mix(x: f32, y: f32, a: f32) -> f32 { /// Given a point within a local rectangle, and the device space corners /// of a snapped primitive, return the snap offsets. This *must* exactly /// match the logic in the GLSL compute_snap_offset_impl function. -fn compute_snap_offset_impl( - reference_pos: PicturePoint, - reference_rect: PictureRect, +fn compute_snap_offset_impl( + reference_pos: TypedPoint2D, + reference_rect: TypedRect, prim_top_left: DevicePoint, prim_bottom_right: DevicePoint, ) -> DeviceVector2D { - let normalized_snap_pos = PicturePoint::new( + let normalized_snap_pos = TypedPoint2D::::new( (reference_pos.x - reference_rect.origin.x) / reference_rect.size.width, (reference_pos.y - reference_rect.origin.y) / reference_rect.size.height, ); @@ -3903,6 +3960,61 @@ pub fn get_raster_rects( Some((clipped.to_i32(), unclipped)) } +/// Snap the given rect in raster space if the transform is +/// axis-aligned. It return the snapped rect transformed back into the +/// given pixel space, and the snap offsets in device space. +pub fn get_snapped_rect( + prim_rect: TypedRect, + map_to_raster: &SpaceMapper, + device_pixel_scale: DevicePixelScale, +) -> Option<(TypedRect, SnapOffsets)> where PixelSpace: fmt::Debug { + let is_axis_aligned = match map_to_raster.kind { + CoordinateSpaceMapping::Local | + CoordinateSpaceMapping::ScaleOffset(..) => true, + CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(), + }; + + if is_axis_aligned { + let raster_rect = map_to_raster.map(&prim_rect)?; + + let device_rect = { + let world_rect = raster_rect * TypedScale::new(1.0); + world_rect * device_pixel_scale + }; + + let top_left = compute_snap_offset_impl( + prim_rect.origin, + prim_rect, + device_rect.origin, + device_rect.bottom_right(), + ); + + let bottom_right = compute_snap_offset_impl( + prim_rect.bottom_right(), + prim_rect, + device_rect.origin, + device_rect.bottom_right(), + ); + + let snap_offsets = SnapOffsets { + top_left, + bottom_right, + }; + + let snapped_device_rect = DeviceRect::new( + device_rect.origin + top_left, + device_rect.size + (bottom_right - top_left).to_size() + ); + + let snapped_world_rect = snapped_device_rect / device_pixel_scale; + let snapped_raster_rect = snapped_world_rect * TypedScale::new(1.0); + let snapped_prim_rect = map_to_raster.unmap(&snapped_raster_rect)?; + Some((snapped_prim_rect, snap_offsets)) + } else { + None + } +} + /// Get the inline (horizontal) and block (vertical) sizes /// for a given line decoration. pub fn get_line_decoration_sizes( diff --git a/gfx/wr/wrench/reftests/filters/filter-drop-shadow-clip.png b/gfx/wr/wrench/reftests/filters/filter-drop-shadow-clip.png index 543ffe227fc5..7e36607b04bc 100644 Binary files a/gfx/wr/wrench/reftests/filters/filter-drop-shadow-clip.png and b/gfx/wr/wrench/reftests/filters/filter-drop-shadow-clip.png differ diff --git a/gfx/wr/wrench/reftests/transforms/nested-preserve-3d.png b/gfx/wr/wrench/reftests/transforms/nested-preserve-3d.png index 4ce47d9b6980..102ed077b686 100644 Binary files a/gfx/wr/wrench/reftests/transforms/nested-preserve-3d.png and b/gfx/wr/wrench/reftests/transforms/nested-preserve-3d.png differ diff --git a/gfx/wr/wrench/reftests/transforms/nested-rotate-x.png b/gfx/wr/wrench/reftests/transforms/nested-rotate-x.png index a057fddec5c0..dba46d889904 100644 Binary files a/gfx/wr/wrench/reftests/transforms/nested-rotate-x.png and b/gfx/wr/wrench/reftests/transforms/nested-rotate-x.png differ diff --git a/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini b/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini index 0c88bf9e90d2..0e65f9998d57 100644 --- a/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini +++ b/testing/web-platform/meta/css/compositing/mix-blend-mode/mix-blend-mode-both-parent-and-blended-with-3D-transform.html.ini @@ -1,3 +1,3 @@ [mix-blend-mode-both-parent-and-blended-with-3D-transform.html] expected: - if os == "android" and not e10s: FAIL + if webrender or (os == "android" and not e10s): FAIL