diff --git a/webrender/res/clip_shared.glsl b/webrender/res/clip_shared.glsl index 2443528b31..590ca68a8f 100644 --- a/webrender/res/clip_shared.glsl +++ b/webrender/res/clip_shared.glsl @@ -51,7 +51,25 @@ RectWithSize intersect_rect(RectWithSize a, RectWithSize b) { ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect, ClipScrollNode scroll_node, ClipArea area) { - vec2 actual_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size; + vec2 device_pos = area.screen_origin + aPosition.xy * area.common_data.task_rect.size; + vec2 actual_pos = device_pos; + + if (scroll_node.is_axis_aligned) { + vec4 snap_positions = compute_snap_positions( + scroll_node.transform, + local_clip_rect + ); + + vec2 snap_offsets = compute_snap_offset_impl( + device_pos, + scroll_node.transform, + local_clip_rect, + RectWithSize(snap_positions.xy, snap_positions.zw - snap_positions.xy), + snap_positions + ); + + actual_pos -= snap_offsets; + } vec4 node_pos; @@ -64,7 +82,7 @@ ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect, } // compute the point position inside the scroll node, in CSS space - vec2 vertex_pos = actual_pos + + vec2 vertex_pos = device_pos + area.common_data.task_rect.p0 - area.screen_origin; diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 356e15d15f..4dc6c1aed9 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -486,11 +486,8 @@ vec4 get_node_pos(vec2 pos, ClipScrollNode node) { return untransform(pos, n, a, node.inv_transform); } -// Compute a snapping offset in world space (adjusted to pixel ratio), -// given local position on the scroll_node and a snap rectangle. -vec2 compute_snap_offset(vec2 local_pos, - mat4 transform, - RectWithSize snap_rect) { +// Compute a snapping offset in world space (adjusted to pixel ratio) +vec4 compute_snap_positions(mat4 transform, RectWithSize snap_rect) { // Ensure that the snap rect is at *least* one device pixel in size. // TODO(gw): It's not clear to me that this is "correct". Specifically, // how should it interact with sub-pixel snap rects when there @@ -505,15 +502,47 @@ vec2 compute_snap_offset(vec2 local_pos, // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) / vec4(world_snap_p0.ww, world_snap_p1.ww); + return world_snap; +} + +vec2 compute_snap_offset_impl( + vec2 reference_pos, + mat4 transform, + RectWithSize snap_rect, + RectWithSize reference_rect, + vec4 snap_positions) { + /// World offsets applied to the corners of the snap rectangle. - vec4 snap_offsets = floor(world_snap + 0.5) - world_snap; + vec4 snap_offsets = floor(snap_positions + 0.5) - snap_positions; /// Compute the position of this vertex inside the snap rectangle. - vec2 normalized_snap_pos = (local_pos - snap_rect.p0) / snap_rect.size; + vec2 normalized_snap_pos = (reference_pos - reference_rect.p0) / reference_rect.size; + /// Compute the actual world offset for this vertex needed to make it snap. return mix(snap_offsets.xy, snap_offsets.zw, normalized_snap_pos); } +// Compute a snapping offset in world space (adjusted to pixel ratio), +// given local position on the scroll_node and a snap rectangle. +vec2 compute_snap_offset(vec2 local_pos, + mat4 transform, + RectWithSize snap_rect) { + vec4 snap_positions = compute_snap_positions( + transform, + snap_rect + ); + + vec2 snap_offsets = compute_snap_offset_impl( + local_pos, + transform, + snap_rect, + snap_rect, + snap_positions + ); + + return snap_offsets; +} + struct VertexInfo { vec2 local_pos; vec2 screen_pos; diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 2a8f47ad6f..6a9ff9cdad 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -2,7 +2,7 @@ * 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 api::{AlphaType, DeviceIntRect, DeviceIntSize}; +use api::{AlphaType, ClipMode, DeviceIntRect, DeviceIntSize}; use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect}; use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat}; use api::{LayerToWorldTransform, WorldPixel}; @@ -1627,8 +1627,9 @@ impl ClipBatcher { ..instance }); } - ClipSource::Rectangle(..) => { - if work_item.coordinate_system_id != coordinate_system_id { + ClipSource::Rectangle(_, mode) => { + if work_item.coordinate_system_id != coordinate_system_id || + mode == ClipMode::ClipOut { self.rectangles.push(ClipMaskInstance { clip_data_address: gpu_address, ..instance diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index 67be7a49c9..b25695210d 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -81,7 +81,7 @@ impl ClipRegion { #[derive(Debug)] pub enum ClipSource { - Rectangle(LayerRect), + Rectangle(LayerRect, ClipMode), RoundedRectangle(LayerRect, BorderRadius, ClipMode), Image(ImageMask), /// TODO(gw): This currently only handles dashed style @@ -101,7 +101,7 @@ impl From for ClipSources { clips.push(ClipSource::Image(info)); } - clips.push(ClipSource::Rectangle(region.main)); + clips.push(ClipSource::Rectangle(region.main, ClipMode::Clip)); for complex in region.complex_clips { clips.push(ClipSource::new_rounded_rect( @@ -121,12 +121,16 @@ impl ClipSource { mut radii: BorderRadius, clip_mode: ClipMode ) -> ClipSource { - ensure_no_corner_overlap(&mut radii, &rect); - ClipSource::RoundedRectangle( - rect, - radii, - clip_mode, - ) + if radii.is_zero() { + ClipSource::Rectangle(rect, clip_mode) + } else { + ensure_no_corner_overlap(&mut radii, &rect); + ClipSource::RoundedRectangle( + rect, + radii, + clip_mode, + ) + } } pub fn new_line_decoration( @@ -306,7 +310,14 @@ impl ClipSources { } local_inner = None; } - ClipSource::Rectangle(rect) => { + ClipSource::Rectangle(rect, mode) => { + // Once we encounter a clip-out, we just assume the worst + // case clip mask size, for now. + if mode == ClipMode::ClipOut { + can_calculate_inner_rect = false; + break; + } + can_calculate_outer_rect = true; local_outer = local_outer.and_then(|r| r.intersection(&rect)); local_inner = local_inner.and_then(|r| r.intersection(&rect)); @@ -378,8 +389,8 @@ impl ClipSources { ]); request.push(info.prim_shadow_rect); } - ClipSource::Rectangle(rect) => { - let data = ClipData::uniform(rect, 0.0, ClipMode::Clip); + ClipSource::Rectangle(rect, mode) => { + let data = ClipData::uniform(rect, 0.0, mode); data.write(&mut request); } ClipSource::RoundedRectangle(ref rect, ref radius, mode) => { diff --git a/webrender/src/hit_test.rs b/webrender/src/hit_test.rs index a184aedc02..2fafd81e42 100644 --- a/webrender/src/hit_test.rs +++ b/webrender/src/hit_test.rs @@ -75,14 +75,17 @@ impl HitTestingItem { pub struct HitTestingRun(pub Vec, pub ScrollNodeAndClipChain); enum HitTestRegion { - Rectangle(LayerRect), + Rectangle(LayerRect, ClipMode), RoundedRectangle(LayerRect, BorderRadius, ClipMode), } impl HitTestRegion { pub fn contains(&self, point: &LayerPoint) -> bool { match *self { - HitTestRegion::Rectangle(ref rectangle) => rectangle.contains(point), + HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) => + rectangle.contains(point), + HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) => + !rectangle.contains(point), HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) => rounded_rectangle_contains_point(point, &rect, &radii), HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) => @@ -307,10 +310,10 @@ fn get_regions_for_clip_scroll_node( clips.iter().map(|source| { match source.0 { - ClipSource::Rectangle(ref rect) => HitTestRegion::Rectangle(*rect), + ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode), ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) => HitTestRegion::RoundedRectangle(*rect, *radii, *mode), - ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect), + ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip), ClipSource::BorderCorner(_) | ClipSource::LineDecoration(_) | ClipSource::BoxShadow(_) => { diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 669d30e32f..8e9ab61966 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1544,12 +1544,21 @@ impl PrimitiveStore { if !brush.kind.supports_segments() { return; } - if metadata.local_rect.size.area() <= MIN_BRUSH_SPLIT_AREA { - return; - } } } + // If the brush is small, we generally want to skip building segments + // and just draw it as a single primitive with clip mask. However, + // if the clips are purely rectangles that have no per-fragment + // clip masks, we will segment anyway. This allows us to completely + // skip allocating a clip mask in these cases. + let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA; + + // TODO(gw): We should probably detect and store this on each + // ClipSources instance, to avoid having to iterate + // the clip sources here. + let mut rect_clips_only = true; + let mut segment_builder = SegmentBuilder::new( metadata.local_rect, None, @@ -1574,12 +1583,16 @@ impl PrimitiveStore { for &(ref clip, _) in &local_clips.clips { let (local_clip_rect, radius, mode) = match *clip { ClipSource::RoundedRectangle(rect, radii, clip_mode) => { + rect_clips_only = false; + (rect, Some(radii), clip_mode) } - ClipSource::Rectangle(rect) => { - (rect, None, ClipMode::Clip) + ClipSource::Rectangle(rect, mode) => { + (rect, None, mode) } ClipSource::BoxShadow(ref info) => { + rect_clips_only = false; + // For inset box shadows, we can clip out any // pixels that are inside the shadow region // and are beyond the inner rect, as they can't @@ -1607,6 +1620,8 @@ impl PrimitiveStore { ClipSource::BorderCorner(..) | ClipSource::LineDecoration(..) | ClipSource::Image(..) => { + rect_clips_only = false; + // TODO(gw): We can easily extend the segment builder // to support these clip sources in the // future, but they are rarely used. @@ -1638,32 +1653,34 @@ impl PrimitiveStore { } } - match brush.segment_desc { - Some(ref mut segment_desc) => { - segment_desc.clip_mask_kind = clip_mask_kind; - } - None => { - // TODO(gw): We can probably make the allocation - // patterns of this and the segment - // builder significantly better, by - // retaining it across primitives. - let mut segments = Vec::new(); - - segment_builder.build(|segment| { - segments.push( - BrushSegment::new( - segment.rect.origin, - segment.rect.size, - segment.has_mask, - segment.edge_flags, - ), - ); - }); + if is_large || rect_clips_only { + match brush.segment_desc { + Some(ref mut segment_desc) => { + segment_desc.clip_mask_kind = clip_mask_kind; + } + None => { + // TODO(gw): We can probably make the allocation + // patterns of this and the segment + // builder significantly better, by + // retaining it across primitives. + let mut segments = Vec::new(); + + segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect.origin, + segment.rect.size, + segment.has_mask, + segment.edge_flags, + ), + ); + }); - brush.segment_desc = Some(BrushSegmentDescriptor { - segments, - clip_mask_kind, - }); + brush.segment_desc = Some(BrushSegmentDescriptor { + segments, + clip_mask_kind, + }); + } } } } diff --git a/wrench/reftests/border/degenerate-curve.png b/wrench/reftests/border/degenerate-curve.png index d4cf073f2a..bf045f602d 100644 Binary files a/wrench/reftests/border/degenerate-curve.png and b/wrench/reftests/border/degenerate-curve.png differ diff --git a/wrench/reftests/boxshadow/inset-subpx.png b/wrench/reftests/boxshadow/inset-subpx.png index 8aaf0683f8..edcbb1652a 100644 Binary files a/wrench/reftests/boxshadow/inset-subpx.png and b/wrench/reftests/boxshadow/inset-subpx.png differ diff --git a/wrench/reftests/boxshadow/no-stretch.png b/wrench/reftests/boxshadow/no-stretch.png index 73b85f8789..679811aa82 100644 Binary files a/wrench/reftests/boxshadow/no-stretch.png and b/wrench/reftests/boxshadow/no-stretch.png differ diff --git a/wrench/reftests/clip/reftest.list b/wrench/reftests/clip/reftest.list index c6be88f66a..942fc7c256 100644 --- a/wrench/reftests/clip/reftest.list +++ b/wrench/reftests/clip/reftest.list @@ -9,3 +9,4 @@ == fixed-position-clipping.yaml fixed-position-clipping-ref.yaml == segmentation-with-other-coordinate-system-clip.yaml segmentation-with-other-coordinate-system-clip-ref.yaml == stacking-context-clip.yaml stacking-context-clip-ref.yaml +== snapping.yaml snapping-ref.yaml diff --git a/wrench/reftests/clip/snapping-ref.yaml b/wrench/reftests/clip/snapping-ref.yaml new file mode 100644 index 0000000000..7be527df93 --- /dev/null +++ b/wrench/reftests/clip/snapping-ref.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 1000, 1000] + complex: + - rect: [50, 50, 100, 100] + radius: 16 + items: + - type: rect + bounds: 50 50 100 100 + color: red + + - type: rect + bounds: 200 50 100 100 + color: green + diff --git a/wrench/reftests/clip/snapping.yaml b/wrench/reftests/clip/snapping.yaml new file mode 100644 index 0000000000..d2ad449ea9 --- /dev/null +++ b/wrench/reftests/clip/snapping.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 1000, 1000] + complex: + - rect: [50.3, 50.3, 100, 100] + radius: 16 + items: + - type: rect + bounds: 50.3 50.3 100 100 + color: red + + - type: rect + bounds: 200.3 50.3 100 100 + color: green + diff --git a/wrench/reftests/transforms/prim-suite.png b/wrench/reftests/transforms/prim-suite.png index f2ad594782..20f830728a 100644 Binary files a/wrench/reftests/transforms/prim-suite.png and b/wrench/reftests/transforms/prim-suite.png differ