Bug 1496540 - Handle overlapping border corners in webrender r=gw

When some of a border's corners have a border-radius, and that radius
is larger than the sum of the border width and element size, then it
results in the corners of the border overlapping. Webrender draws
borders by rasterizing each segment individually in to the cache, then
compositing them together. In this overlapping case, this has 2
problems:

a) we composite overlapping segments on top of eachother
b) corner segments are not correctly clipped to the curve of the
   overlapping adjacent corners

This patch allows corner segments to be clipped by their adjacent
corners. We provide the outer corner position and radii of the
adjacent corners to the border shader, which then applies those clips,
if required, along with the segment's own corner clip when rasterizing
the segment.

As the adjacent corners now affect the result of the cached segment,
they are added to the cache key.

We continue to rasterize the entire segment in to the cache as before,
but now modify the local rect and texel rect of the BrushSegment so
that it only composites the subportion of the corner segment which
does not overlap with the opposite edges of the border.

Differential Revision: https://phabricator.services.mozilla.com/D16872

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jamie Nicol 2019-01-31 18:31:47 +00:00
Родитель b1600d426e
Коммит 74e9823383
14 изменённых файлов: 264 добавлений и 17 удалений

Просмотреть файл

@ -94,8 +94,15 @@ void brush_vs(
// If the extra data is a texel rect, modify the UVs.
if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
uv0 = res.uv_rect.p0 + segment_data.xy;
uv1 = res.uv_rect.p0 + segment_data.zw;
vec2 uv_size = res.uv_rect.p1 - res.uv_rect.p0;
uv0 = res.uv_rect.p0 + segment_data.xy * uv_size;
uv1 = res.uv_rect.p0 + segment_data.zw * uv_size;
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
stretch_size.x = stretch_size.x * uv_size.x;
}
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
stretch_size.y = stretch_size.y * uv_size.y;
}
}
}

Просмотреть файл

@ -56,6 +56,7 @@ void brush_vs(
if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
vPos = (vi.local_pos - segment_rect.p0) / segment_rect.size;
vPos = vPos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
vPos = vPos * local_rect.size;
} else {
vPos = vi.local_pos - local_rect.p0;
}

Просмотреть файл

@ -56,6 +56,7 @@ void brush_vs(
if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) {
vPos = (vi.local_pos - segment_rect.p0) / segment_rect.size;
vPos = vPos * (texel_rect.zw - texel_rect.xy) + texel_rect.xy;
vPos = vPos * local_rect.size;
} else {
vPos = vi.local_pos - local_rect.p0;
}

Просмотреть файл

@ -29,6 +29,12 @@ flat varying vec4 vClipCenter_Sign;
// corner clipping.
flat varying vec4 vClipRadii;
// Position, scale, and radii of horizontally and vertically adjacent corner clips.
flat varying vec4 vHorizontalClipCenter_Sign;
flat varying vec2 vHorizontalClipRadii;
flat varying vec4 vVerticalClipCenter_Sign;
flat varying vec2 vVerticalClipRadii;
// Local space position
varying vec2 vPos;
@ -46,6 +52,8 @@ in vec4 aColor1;
in int aFlags;
in vec2 aWidths;
in vec2 aRadii;
in vec4 aHorizontallyAdjacentCorner;
in vec4 aVerticallyAdjacentCorner;
vec2 get_outer_corner_scale(int segment) {
vec2 p;
@ -102,6 +110,18 @@ void main(void) {
vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
vec2 horizontal_clip_sign = vec2(-clip_sign.x, clip_sign.y);
vHorizontalClipCenter_Sign = vec4(aHorizontallyAdjacentCorner.xy +
horizontal_clip_sign * aHorizontallyAdjacentCorner.zw,
horizontal_clip_sign);
vHorizontalClipRadii = aHorizontallyAdjacentCorner.zw;
vec2 vertical_clip_sign = vec2(clip_sign.x, -clip_sign.y);
vVerticalClipCenter_Sign = vec4(aVerticallyAdjacentCorner.xy +
vertical_clip_sign * aVerticallyAdjacentCorner.zw,
vertical_clip_sign);
vVerticalClipRadii = aVerticallyAdjacentCorner.zw;
gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
}
#endif
@ -121,7 +141,7 @@ void main(void) {
}
}
// Check if inside corner clip-region
// Check if inside main corner clip-region
vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
@ -132,6 +152,22 @@ void main(void) {
d = max(d_radii_a, -d_radii_b);
}
// And again for horizontally-adjacent corner
clip_relative_pos = vPos - vHorizontalClipCenter_Sign.xy;
in_clip_region = all(lessThan(vHorizontalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
if (in_clip_region) {
float d_radii = distance_to_ellipse(clip_relative_pos, vHorizontalClipRadii.xy, aa_range);
d = max(d_radii, d);
}
// And finally for vertically-adjacent corner
clip_relative_pos = vPos - vVerticalClipCenter_Sign.xy;
in_clip_region = all(lessThan(vVerticalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
if (in_clip_region) {
float d_radii = distance_to_ellipse(clip_relative_pos, vVerticalClipRadii.xy, aa_range);
d = max(d_radii, d);
}
float alpha = do_aa ? distance_aa(aa_range, d) : 1.0;
vec4 color = mix(vColor0, vColor1, mix_factor);
oFragColor = color * alpha;

Просмотреть файл

@ -5,7 +5,7 @@
use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize};
use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, DeviceIntSize};
use api::{AuHelpers, LayoutPoint, RepeatMode, TexelRect, LayoutVector2D};
use api::{AuHelpers, LayoutPoint, LayoutPointAu, RepeatMode, TexelRect, LayoutVector2D};
use api::NormalBorder as ApiNormalBorder;
use ellipse::Ellipse;
use euclid::vec2;
@ -158,6 +158,10 @@ pub struct BorderSegmentCacheKey {
pub side1: BorderSideAu,
pub segment: BorderSegment,
pub do_aa: bool,
pub h_adjacent_corner_outer: LayoutPointAu,
pub h_adjacent_corner_radius: LayoutSizeAu,
pub v_adjacent_corner_outer: LayoutPointAu,
pub v_adjacent_corner_radius: LayoutSizeAu,
}
pub fn ensure_no_corner_overlap(
@ -764,12 +768,22 @@ pub fn create_border_segments(
rect.origin.x + local_size_tl.width,
rect.origin.y + local_size_tl.height,
),
LayoutRect::from_floats(
rect.origin.x,
rect.origin.y,
rect.max_x() - widths.right,
rect.max_y() - widths.bottom
),
border.left,
border.top,
LayoutSize::new(widths.left, widths.top),
border.radius.top_left,
BorderSegment::TopLeft,
EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
rect.top_right(),
border.radius.top_right,
rect.bottom_left(),
border.radius.bottom_left,
brush_segments,
border_segments,
border.do_aa,
@ -781,12 +795,22 @@ pub fn create_border_segments(
rect.origin.x + rect.size.width,
rect.origin.y + local_size_tr.height,
),
LayoutRect::from_floats(
rect.origin.x + widths.left,
rect.origin.y,
rect.max_x(),
rect.max_y() - widths.bottom,
),
border.top,
border.right,
LayoutSize::new(widths.right, widths.top),
border.radius.top_right,
BorderSegment::TopRight,
EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
rect.origin,
border.radius.top_left,
rect.bottom_right(),
border.radius.bottom_right,
brush_segments,
border_segments,
border.do_aa,
@ -798,12 +822,22 @@ pub fn create_border_segments(
rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height,
),
LayoutRect::from_floats(
rect.origin.x + widths.left,
rect.origin.y + widths.top,
rect.max_x(),
rect.max_y(),
),
border.right,
border.bottom,
LayoutSize::new(widths.right, widths.bottom),
border.radius.bottom_right,
BorderSegment::BottomRight,
EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT,
rect.bottom_left(),
border.radius.bottom_left,
rect.top_right(),
border.radius.top_right,
brush_segments,
border_segments,
border.do_aa,
@ -815,12 +849,22 @@ pub fn create_border_segments(
rect.origin.x + local_size_bl.width,
rect.origin.y + rect.size.height,
),
LayoutRect::from_floats(
rect.origin.x,
rect.origin.y + widths.top,
rect.max_x() - widths.right,
rect.max_y(),
),
border.bottom,
border.left,
LayoutSize::new(widths.left, widths.bottom),
border.radius.bottom_left,
BorderSegment::BottomLeft,
EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
rect.bottom_right(),
border.radius.bottom_right,
rect.origin,
border.radius.top_left,
brush_segments,
border_segments,
border.do_aa,
@ -862,6 +906,10 @@ fn add_segment(
widths: DeviceSize,
radius: DeviceSize,
do_aa: bool,
h_adjacent_corner_outer: DevicePoint,
h_adjacent_corner_radius: DeviceSize,
v_adjacent_corner_outer: DevicePoint,
v_adjacent_corner_radius: DeviceSize,
) {
let base_flags = (segment as i32) |
((style0 as i32) << 8) |
@ -916,7 +964,21 @@ fn add_segment(
};
if dashed_or_dotted_corner.is_err() {
instances.push(base_instance);
let clip_params = [
h_adjacent_corner_outer.x,
h_adjacent_corner_outer.y,
h_adjacent_corner_radius.width,
h_adjacent_corner_radius.height,
v_adjacent_corner_outer.x,
v_adjacent_corner_outer.y,
v_adjacent_corner_radius.width,
v_adjacent_corner_radius.height,
];
instances.push(BorderInstance {
clip_params,
..base_instance
});
}
}
BorderSegment::Top |
@ -975,12 +1037,17 @@ fn add_segment(
/// border segments for this primitive.
fn add_corner_segment(
image_rect: LayoutRect,
non_overlapping_rect: LayoutRect,
side0: BorderSide,
side1: BorderSide,
widths: LayoutSize,
radius: LayoutSize,
segment: BorderSegment,
edge_flags: EdgeAaSegmentMask,
h_adjacent_corner_outer: LayoutPoint,
h_adjacent_corner_radius: LayoutSize,
v_adjacent_corner_outer: LayoutPoint,
v_adjacent_corner_radius: LayoutSize,
brush_segments: &mut Vec<BrushSegment>,
border_segments: &mut Vec<BorderSegmentInfo>,
do_aa: bool,
@ -997,20 +1064,95 @@ fn add_corner_segment(
return;
}
if image_rect.size.width <= 0. || image_rect.size.height <= 0. {
let segment_rect = image_rect.intersection(&non_overlapping_rect)
.unwrap_or(LayoutRect::zero());
if segment_rect.size.width <= 0. || segment_rect.size.height <= 0. {
return;
}
let texture_rect = segment_rect
.translate(&-image_rect.origin.to_vector())
.scale(1.0 / image_rect.size.width, 1.0 / image_rect.size.height);
brush_segments.push(
BrushSegment::new(
image_rect,
segment_rect,
/* may_need_clip_mask = */ true,
edge_flags,
[0.0; 4],
BrushFlags::SEGMENT_RELATIVE,
[texture_rect.min_x(), texture_rect.min_y(), texture_rect.max_x(), texture_rect.max_y()],
BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_TEXEL_RECT,
)
);
// If the radii of the adjacent corners do not overlap with this segment,
// then set the outer position to this segment's corner and the radii to zero.
// That way the cache key is unaffected by non-overlapping corners, resulting
// in fewer misses.
let (h_corner_outer, h_corner_radius) = match segment {
BorderSegment::TopLeft => {
if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max_x() {
(h_adjacent_corner_outer, h_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.max_x(), image_rect.min_y()), LayoutSize::zero())
}
}
BorderSegment::TopRight => {
if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min_x() {
(h_adjacent_corner_outer, h_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.min_x(), image_rect.min_y()), LayoutSize::zero())
}
}
BorderSegment::BottomRight => {
if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min_x() {
(h_adjacent_corner_outer, h_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.min_x(), image_rect.max_y()), LayoutSize::zero())
}
}
BorderSegment::BottomLeft => {
if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max_x() {
(h_adjacent_corner_outer, h_adjacent_corner_radius)
} else {
(image_rect.bottom_right(), LayoutSize::zero())
}
}
_ => unreachable!()
};
let (v_corner_outer, v_corner_radius) = match segment {
BorderSegment::TopLeft => {
if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max_y() {
(v_adjacent_corner_outer, v_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.min_x(), image_rect.max_y()), LayoutSize::zero())
}
}
BorderSegment::TopRight => {
if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max_y() {
(v_adjacent_corner_outer, v_adjacent_corner_radius)
} else {
(image_rect.bottom_right(), LayoutSize::zero())
}
}
BorderSegment::BottomRight => {
if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min_y() {
(v_adjacent_corner_outer, v_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.max_x(), image_rect.min_y()), LayoutSize::zero())
}
}
BorderSegment::BottomLeft => {
if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min_y() {
(v_adjacent_corner_outer, v_adjacent_corner_radius)
} else {
(LayoutPoint::new(image_rect.min_x(), image_rect.min_y()), LayoutSize::zero())
}
}
_ => unreachable!()
};
border_segments.push(BorderSegmentInfo {
local_task_size: image_rect.size,
cache_key: BorderSegmentCacheKey {
@ -1020,6 +1162,10 @@ fn add_corner_segment(
segment,
radius: radius.to_au(),
size: widths.to_au(),
h_adjacent_corner_outer: (h_corner_outer - image_rect.origin).to_point().to_au(),
h_adjacent_corner_radius: h_corner_radius.to_au(),
v_adjacent_corner_outer: (v_corner_outer - image_rect.origin).to_point().to_au(),
v_adjacent_corner_radius: v_corner_radius.to_au(),
},
});
}
@ -1080,6 +1226,10 @@ fn add_edge_segment(
radius: LayoutSizeAu::zero(),
size: size.to_au(),
segment,
h_adjacent_corner_outer: LayoutPointAu::zero(),
h_adjacent_corner_radius: LayoutSizeAu::zero(),
v_adjacent_corner_outer: LayoutPointAu::zero(),
v_adjacent_corner_radius: LayoutSizeAu::zero(),
},
});
}
@ -1122,6 +1272,11 @@ pub fn build_border_instances(
let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil();
let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil();
let h_corner_outer = (LayoutPoint::from_au(cache_key.h_adjacent_corner_outer) * scale).round();
let h_corner_radius = (LayoutSize::from_au(cache_key.h_adjacent_corner_radius) * scale).ceil();
let v_corner_outer = (LayoutPoint::from_au(cache_key.v_adjacent_corner_outer) * scale).round();
let v_corner_radius = (LayoutSize::from_au(cache_key.v_adjacent_corner_radius) * scale).ceil();
add_segment(
DeviceRect::new(DevicePoint::zero(), cache_size.to_f32()),
style0,
@ -1133,6 +1288,10 @@ pub fn build_border_instances(
widths,
radius,
border.do_aa,
h_corner_outer,
h_corner_radius,
v_corner_outer,
v_corner_radius,
);
instances
@ -1161,14 +1320,14 @@ impl NinePatchDescriptor {
// Calculate the local texel coords of the slices.
let px0 = 0.0;
let px1 = self.slice.left as f32;
let px2 = self.width as f32 - self.slice.right as f32;
let px3 = self.width as f32;
let px1 = self.slice.left as f32 / self.width as f32;
let px2 = (self.width as f32 - self.slice.right as f32) / self.width as f32;
let px3 = 1.0;
let py0 = 0.0;
let py1 = self.slice.top as f32;
let py2 = self.height as f32 - self.slice.bottom as f32;
let py3 = self.height as f32;
let py1 = self.slice.top as f32 / self.height as f32;
let py2 = (self.height as f32 - self.slice.bottom as f32) / self.height as f32;
let py3 = 1.0;
let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y);
let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top);

Просмотреть файл

@ -16,3 +16,6 @@ root:
repeat-vertical: stretch
repeat-horizontal: stretch
fill: true
- type: rect
bounds: [ 100, 100, 192, 192 ]
color: white

Просмотреть файл

@ -16,3 +16,6 @@ root:
repeat-vertical: stretch
repeat-horizontal: stretch
fill: true
- type: rect
bounds: [ 100, 100, 192, 192 ]
color: white

Просмотреть файл

@ -0,0 +1,19 @@
---
root:
items:
- type: stacking-context
bounds: [0, 0, 200, 200]
items:
- type: clip
bounds: [ 0, 0, 200, 200 ]
complex:
- rect: [ 10, 10, 180, 180 ]
radius:
top-left: [180, 180]
top-right: [0, 0]
bottom-left: [0, 0]
bottom-right: [180, 180]
items:
- type: rect
bounds: [ 0, 0, 200, 200 ]
color: [ 0, 0, 255, 0.5 ]

Просмотреть файл

@ -0,0 +1,17 @@
---
root:
items:
- type: stacking-context
bounds: [0, 0, 200, 200]
items:
- type: border
bounds: [ 10, 10, 180, 180 ]
width: 90
border-type: normal
style: solid
radius:
top-left: 180
bottom-right: 180
top-right: 0
bottom-left: 0
color: [ [0, 0, 255, 0.5] ]

Двоичные данные
gfx/wr/wrench/reftests/border/border-suite-2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 48 KiB

После

Ширина:  |  Высота:  |  Размер: 48 KiB

Просмотреть файл

@ -5,6 +5,7 @@ platform(linux,mac) == border-gradient-nine-patch.yaml border-gradient-nine-patc
platform(linux,mac) == border-radial-gradient-nine-patch.yaml border-radial-gradient-nine-patch.png
== border-radii.yaml border-radii.png
== border-none.yaml border-none-ref.yaml
fuzzy(1,68) == border-overlapping.yaml border-overlapping-ref.yaml
== border-invisible.yaml border-invisible-ref.yaml
platform(linux,mac) == border-suite.yaml border-suite.png
platform(linux,mac) fuzzy(8,8) == border-suite-2.yaml border-suite-2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.6 KiB

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Просмотреть файл

@ -15,7 +15,7 @@ root:
bounds: [10, 10, 100, 200]
clip-and-scroll: 2
type: border
width: [300, 300, 300, 300]
width: [100, 50, 100, 50]
border-type: normal
color: green
style: solid

Просмотреть файл

@ -29,7 +29,7 @@ fuzzy-if(gtkWidget,0-14,0-280) fuzzy-if(cocoaWidget,0-4,0-582) fuzzy-if(Android,
# Corners
fuzzy-if(skiaContent,0-17,0-47) fuzzy-if(webrender,30-30,58-58) == corner-1.html corner-1-ref.svg # bottom corners different radius than top corners
fuzzy-if(gtkWidget,0-23,0-5) fuzzy-if(winWidget&&!d2d,0-23,0-5) fuzzy-if(d2d,0-32,0-8) fuzzy-if(Android,0-10,0-8) fuzzy-if(skiaContent,0-18,0-49) fuzzy-if(webrender,30-30,57-57) == corner-2.html corner-2-ref.svg # right corners different radius than left corners; see bug 500804
fuzzy-if(gtkWidget,0-3,0-10) fuzzy-if(winWidget&&!d2d,0-3,0-10) fuzzy-if(d2d,0-15,0-32) fuzzy-if(Android,0-3,0-15) fuzzy-if(skiaContent,0-18,0-90) fails-if(webrender) == corner-3.html corner-3-ref.svg
fuzzy-if(gtkWidget,0-3,0-10) fuzzy-if(winWidget&&!d2d,0-3,0-10) fuzzy-if(d2d,0-15,0-32) fuzzy-if(Android,0-3,0-15) fuzzy-if(skiaContent,0-18,0-90) fuzzy-if(webrender,23-23,105-105) == corner-3.html corner-3-ref.svg
fuzzy-if(skiaContent,0-13,0-83) fuzzy-if(webrender,13-13,104-104) == corner-4.html corner-4-ref.svg
# Test that radii too long are reduced