diff --git a/webrender/res/cs_clip_border.fs.glsl b/webrender/res/cs_clip_border.fs.glsl new file mode 100644 index 0000000000..1a51066d53 --- /dev/null +++ b/webrender/res/cs_clip_border.fs.glsl @@ -0,0 +1,30 @@ +#line 1 +/* 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/. */ + +void main(void) { + vec2 local_pos = vPos.xy / vPos.z; + + // Get local space position relative to the clip center. + vec2 clip_relative_pos = local_pos - vClipCenter; + + // Get the signed distances to the two clip lines. + float d0 = distance_to_line(vPoint_Tangent0.xy, + vPoint_Tangent0.zw, + clip_relative_pos); + float d1 = distance_to_line(vPoint_Tangent1.xy, + vPoint_Tangent1.zw, + clip_relative_pos); + + // Get AA widths based on zoom / scale etc. + vec2 fw = fwidth(local_pos); + float afwidth = length(fw); + + // Apply AA over half a device pixel for the clip. + float d = smoothstep(-0.5 * afwidth, + 0.5 * afwidth, + max(d0, -d1)); + + oFragColor = vec4(d, 0.0, 0.0, 1.0); +} diff --git a/webrender/res/cs_clip_border.glsl b/webrender/res/cs_clip_border.glsl new file mode 100644 index 0000000000..3f0234522c --- /dev/null +++ b/webrender/res/cs_clip_border.glsl @@ -0,0 +1,12 @@ +#line 1 + +/* 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/. */ + +varying vec3 vPos; + +flat varying vec2 vClipCenter; + +flat varying vec4 vPoint_Tangent0; +flat varying vec4 vPoint_Tangent1; diff --git a/webrender/res/cs_clip_border.vs.glsl b/webrender/res/cs_clip_border.vs.glsl new file mode 100644 index 0000000000..83e282a6d6 --- /dev/null +++ b/webrender/res/cs_clip_border.vs.glsl @@ -0,0 +1,68 @@ +#line 1 +/* 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/. */ + +// Header for a border corner clip. +struct BorderCorner { + RectWithSize rect; + vec2 clip_center; + vec2 sign_modifier; +}; + +BorderCorner fetch_border_corner(int index) { + vec4 data[2] = fetch_data_2(index); + return BorderCorner(RectWithSize(data[0].xy, data[0].zw), + data[1].xy, + data[1].zw); +} + +// Per-dash clip information. +// TODO: Expand this to handle dots in the future! +struct BorderClip { + vec4 point_tangent_0; + vec4 point_tangent_1; +}; + +BorderClip fetch_border_clip(int index) { + vec4 data[2] = fetch_data_2(index); + return BorderClip(data[0], data[1]); +} + +void main(void) { + CacheClipInstance cci = fetch_clip_item(gl_InstanceID); + ClipArea area = fetch_clip_area(cci.render_task_index); + Layer layer = fetch_layer(cci.layer_index); + + // Fetch the header information for this corner clip. + BorderCorner corner = fetch_border_corner(cci.data_index); + vClipCenter = corner.clip_center; + + // Fetch the information about this particular dash. + BorderClip clip = fetch_border_clip(cci.data_index + cci.segment_index + 1); + vPoint_Tangent0 = clip.point_tangent_0 * corner.sign_modifier.xyxy; + vPoint_Tangent1 = clip.point_tangent_1 * corner.sign_modifier.xyxy; + + // Get local vertex position for the corner rect. + // TODO(gw): We could reduce the number of pixels written here + // by calculating a tight fitting bounding box of the dash itself. + vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size; + + // Transform to world pos + vec4 world_pos = layer.transform * vec4(pos, 0.0, 1.0); + world_pos.xyz /= world_pos.w; + + // Scale into device pixels. + vec2 device_pos = world_pos.xy * uDevicePixelRatio; + + // Position vertex within the render task area. + vec2 final_pos = device_pos - + area.screen_origin_target_index.xy + + area.task_bounds.xy; + + // Calculate the local space position for this vertex. + vec4 layer_pos = get_layer_pos(world_pos.xy, layer); + vPos = layer_pos.xyw; + + gl_Position = uTransform * vec4(final_pos, 0.0, 1.0); +} diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 56ebdc161f..619934c6e1 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -103,6 +103,10 @@ RectWithEndpoint intersect_rect(RectWithEndpoint a, RectWithEndpoint b) { return RectWithEndpoint(p.xy, max(p.xy, p.zw)); } +float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) { + vec2 dir_to_p0 = p0 - p; + return dot(normalize(perp_dir), dir_to_p0); +} // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever. flat varying vec4 vClipMaskUvBounds; diff --git a/webrender/res/ps_border_corner.fs.glsl b/webrender/res/ps_border_corner.fs.glsl index 31589c3c60..43c95106fa 100644 --- a/webrender/res/ps_border_corner.fs.glsl +++ b/webrender/res/ps_border_corner.fs.glsl @@ -54,11 +54,6 @@ float sdEllipse( vec2 p, in vec2 ab ) { return length(r - p ) * sign(p.y-r.y); } -float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) { - vec2 dir_to_p0 = p0 - p; - return dot(normalize(perp_dir), dir_to_p0); -} - float distance_to_ellipse(vec2 p, vec2 radii) { // sdEllipse fails on exact circles, so handle equal // radii here. The branch coherency should make this diff --git a/webrender/res/ps_border_edge.fs.glsl b/webrender/res/ps_border_edge.fs.glsl index bd8791f4bf..6a9134d3b9 100644 --- a/webrender/res/ps_border_edge.fs.glsl +++ b/webrender/res/ps_border_edge.fs.glsl @@ -24,11 +24,11 @@ void main(void) { // no effect. // Select the x/y coord, depending on which axis this edge is. - float pos = mix(local_pos.x, local_pos.y, vAxisSelect); + vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect); // Get signed distance from each of the inner edges. - float d0 = pos - vEdgeDistance.x; - float d1 = vEdgeDistance.y - pos; + float d0 = pos.x - vEdgeDistance.x; + float d1 = vEdgeDistance.y - pos.x; // SDF union to select both outer edges. float d = min(d0, d1); @@ -42,6 +42,8 @@ void main(void) { // TODO(gw): Support AA for groove/ridge border edge with transforms. vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0)); - //oFragColor = vec4(d0 * vEdgeDistance.y, -d0 * vEdgeDistance.y, 0, 1.0); + // Apply dashing parameters. + alpha = min(alpha, step(mod(pos.y - vDashParams.x, vDashParams.y), vDashParams.z)); + oFragColor = color * vec4(1.0, 1.0, 1.0, alpha); } diff --git a/webrender/res/ps_border_edge.glsl b/webrender/res/ps_border_edge.glsl index fbffd1873d..5053b00ba1 100644 --- a/webrender/res/ps_border_edge.glsl +++ b/webrender/res/ps_border_edge.glsl @@ -7,6 +7,7 @@ flat varying vec4 vColor1; flat varying vec2 vEdgeDistance; flat varying float vAxisSelect; flat varying float vAlphaSelect; +flat varying vec3 vDashParams; #ifdef WR_FEATURE_TRANSFORM varying vec3 vLocalPos; diff --git a/webrender/res/ps_border_edge.vs.glsl b/webrender/res/ps_border_edge.vs.glsl index c35c703da4..35f5b2e787 100644 --- a/webrender/res/ps_border_edge.vs.glsl +++ b/webrender/res/ps_border_edge.vs.glsl @@ -56,6 +56,30 @@ void write_color(vec4 color, float style, bool flip) { vColor1 = vec4(color.rgb * modulate.y, color.a); } +void write_dash_params(float style, + float border_width, + float edge_length, + float edge_offset) { + // x = offset + // y = dash on + off length + // z = dash length + switch (int(style)) { + case BORDER_STYLE_DASHED: { + float desired_dash_length = border_width * 3.0; + // Consider half total length since there is an equal on/off for each dash. + float dash_count = ceil(0.5 * edge_length / desired_dash_length); + float dash_length = 0.5 * edge_length / dash_count; + vDashParams = vec3(edge_offset - 0.5 * dash_length, + 2.0 * dash_length, + dash_length); + break; + } + default: + vDashParams = vec3(1.0); + break; + } +} + void main(void) { Primitive prim = load_primitive(); Border border = fetch_border(prim.prim_index); @@ -72,6 +96,7 @@ void main(void) { write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0); write_alpha_select(border.style.x); write_color(color, border.style.x, false); + write_dash_params(border.style.x, border.widths.x, segment_rect.size.y, segment_rect.p0.y); break; case 1: segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y); @@ -79,6 +104,7 @@ void main(void) { write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0); write_alpha_select(border.style.y); write_color(color, border.style.y, false); + write_dash_params(border.style.y, border.widths.y, segment_rect.size.x, segment_rect.p0.x); break; case 2: segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y); @@ -86,6 +112,7 @@ void main(void) { write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0); write_alpha_select(border.style.z); write_color(color, border.style.z, true); + write_dash_params(border.style.z, border.widths.z, segment_rect.size.y, segment_rect.p0.y); break; case 3: segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w); @@ -93,6 +120,7 @@ void main(void) { write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0); write_alpha_select(border.style.w); write_color(color, border.style.w, true); + write_dash_params(border.style.w, border.widths.w, segment_rect.size.x, segment_rect.p0.x); break; } diff --git a/webrender/src/border.rs b/webrender/src/border.rs index 594514a135..21d2d533de 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -2,18 +2,28 @@ * 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 ellipse::Ellipse; use frame_builder::FrameBuilder; -use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, PrimitiveContainer}; +use mask_cache::{ClipSource}; +use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, GpuBlock32, PrimitiveContainer}; use tiling::PrimitiveFlags; use util::pack_as_float; use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ClipRegion}; use webrender_traits::{ColorF, LayerPoint, LayerRect, LayerSize, NormalBorder}; -#[derive(Copy, Clone, Debug, PartialEq)] +enum BorderCorner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +#[derive(Clone, Debug, PartialEq)] pub enum BorderCornerKind { None, Solid, Clip, + Mask(BorderCornerClipData, LayerSize, LayerSize), Unhandled, } @@ -25,13 +35,15 @@ pub enum BorderEdgeKind { Unhandled, } -pub trait NormalBorderHelpers { +trait NormalBorderHelpers { fn get_corner(&self, edge0: &BorderSide, width0: f32, edge1: &BorderSide, width1: f32, - radius: &LayerSize) -> BorderCornerKind; + radius: &LayerSize, + corner: BorderCorner, + border_rect: &LayerRect) -> BorderCornerKind; fn get_edge(&self, edge: &BorderSide, @@ -44,7 +56,9 @@ impl NormalBorderHelpers for NormalBorder { width0: f32, edge1: &BorderSide, width1: f32, - radius: &LayerSize) -> BorderCornerKind { + radius: &LayerSize, + corner: BorderCorner, + border_rect: &LayerRect) -> BorderCornerKind { // If either width is zero, a corner isn't formed. if width0 == 0.0 || width1 == 0.0 { return BorderCornerKind::None; @@ -78,6 +92,45 @@ impl NormalBorderHelpers for NormalBorder { (BorderStyle::Groove, BorderStyle::Groove) | (BorderStyle::Ridge, BorderStyle::Ridge) => BorderCornerKind::Clip, + // Dashed border corners get drawn into a clip mask. + (BorderStyle::Dashed, BorderStyle::Dashed) => { + let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height)); + let (origin, clip_center, sign_modifier) = match corner { + BorderCorner::TopLeft => { + let origin = border_rect.origin; + let clip_center = origin + size; + (origin, clip_center, LayerPoint::new(-1.0, -1.0)) + } + BorderCorner::TopRight => { + let origin = LayerPoint::new(border_rect.origin.x + + border_rect.size.width - + size.width, + border_rect.origin.y); + let clip_center = origin + LayerSize::new(0.0, size.height); + (origin, clip_center, LayerPoint::new(1.0, -1.0)) + } + BorderCorner::BottomRight => { + let origin = border_rect.origin + (border_rect.size - size); + let clip_center = origin; + (origin, clip_center, LayerPoint::new(1.0, 1.0)) + } + BorderCorner::BottomLeft => { + let origin = LayerPoint::new(border_rect.origin.x, + border_rect.origin.y + + border_rect.size.height - + size.height); + let clip_center = origin + LayerSize::new(size.width, 0.0); + (origin, clip_center, LayerPoint::new(-1.0, 1.0)) + } + }; + let clip_data = BorderCornerClipData { + corner_rect: LayerRect::new(origin, size), + clip_center: clip_center, + sign_modifier: sign_modifier, + }; + BorderCornerKind::Mask(clip_data, *radius, LayerSize::new(width0, width1)) + } + // Assume complex for these cases. // TODO(gw): There are some cases in here that can be handled with a fast path. // For example, with inset/outset borders, two of the four corners are solid. @@ -108,10 +161,10 @@ impl NormalBorderHelpers for NormalBorder { BorderStyle::Double | BorderStyle::Groove | - BorderStyle::Ridge => (BorderEdgeKind::Clip, width), + BorderStyle::Ridge | + BorderStyle::Dashed => (BorderEdgeKind::Clip, width), - BorderStyle::Dotted | - BorderStyle::Dashed => (BorderEdgeKind::Unhandled, width), + BorderStyle::Dotted => (BorderEdgeKind::Unhandled, width), } } } @@ -123,7 +176,8 @@ impl FrameBuilder { widths: &BorderWidths, clip_and_scroll: ClipAndScrollInfo, clip_region: &ClipRegion, - use_new_border_path: bool) { + use_new_border_path: bool, + extra_clips: &[ClipSource]) { let radius = &border.radius; let left = &border.left; let right = &border.right; @@ -163,7 +217,7 @@ impl FrameBuilder { self.add_primitive(clip_and_scroll, &rect, clip_region, - &[], + extra_clips, PrimitiveContainer::Border(prim_cpu, prim_gpu)); } @@ -193,10 +247,34 @@ impl FrameBuilder { let bottom = &border.bottom; let corners = [ - border.get_corner(left, widths.left, top, widths.top, &radius.top_left), - border.get_corner(top, widths.top, right, widths.right, &radius.top_right), - border.get_corner(right, widths.right, bottom, widths.bottom, &radius.bottom_right), - border.get_corner(bottom, widths.bottom, left, widths.left, &radius.bottom_left), + border.get_corner(left, + widths.left, + top, + widths.top, + &radius.top_left, + BorderCorner::TopLeft, + rect), + border.get_corner(right, + widths.right, + top, + widths.top, + &radius.top_right, + BorderCorner::TopRight, + rect), + border.get_corner(right, + widths.right, + bottom, + widths.bottom, + &radius.bottom_right, + BorderCorner::BottomRight, + rect), + border.get_corner(left, + widths.left, + bottom, + widths.bottom, + &radius.bottom_left, + BorderCorner::BottomLeft, + rect), ]; // If any of the corners are unhandled, fall back to slow path for now. @@ -206,7 +284,8 @@ impl FrameBuilder { widths, clip_and_scroll, clip_region, - false); + false, + &[]); return; } @@ -229,7 +308,8 @@ impl FrameBuilder { widths, clip_and_scroll, clip_region, - false); + false, + &[]); return; } @@ -284,12 +364,25 @@ impl FrameBuilder { PrimitiveFlags::None); } } else { + // Create clip masks for border corners, if required. + let mut extra_clips = Vec::new(); + + for corner in corners.iter() { + if let &BorderCornerKind::Mask(corner_data, corner_radius, widths) = corner { + let clip_source = BorderCornerClipSource::new(corner_data, + corner_radius, + widths); + extra_clips.push(ClipSource::BorderCorner(clip_source)); + } + } + self.add_normal_border_primitive(rect, border, widths, clip_and_scroll, clip_region, - true); + true, + &extra_clips); } } } @@ -327,3 +420,127 @@ impl BorderSideHelpers for BorderSide { } } } + +/// The source data for a border corner clip mask. +#[derive(Debug, Clone)] +pub struct BorderCornerClipSource { + pub corner_data: BorderCornerClipData, + pub dash_count: usize, + dash_arc_length: f32, + ellipse: Ellipse, +} + +impl BorderCornerClipSource { + pub fn new(corner_data: BorderCornerClipData, + corner_radius: LayerSize, + widths: LayerSize) -> BorderCornerClipSource { + let ellipse = Ellipse::new(corner_radius); + + // Work out a dash length (and therefore dash count) + // based on the width of the border edges. The "correct" + // dash length is not mentioned in the CSS borders + // spec. The calculation below is similar, but not exactly + // the same as what Gecko uses. + // TODO(gw): Iterate on this to get it closer to what Gecko + // uses for dash length. + + // Approximate the total arc length of the quarter ellipse. + let total_arc_length = ellipse.get_quarter_arc_length(); + + // The desired dash length is ~3x the border width. + let average_border_width = 0.5 * (widths.width + widths.height); + let desired_dash_arc_length = average_border_width * 3.0; + + // Get the ideal number of dashes for that arc length. + // This is scaled by 0.5 since there is an on/off length + // for each dash. + let desired_count = 0.5 * total_arc_length / desired_dash_arc_length; + + // Round that up to the nearest integer, so that the dash length + // doesn't exceed the ratio above. + let actual_count = desired_count.ceil(); + + // Get the correct dash arc length. + let dash_arc_length = 0.5 * total_arc_length / actual_count; + + // Get the number of dashes we'll need to fit. + let dash_count = actual_count as usize; + + BorderCornerClipSource { + corner_data: corner_data, + dash_count: dash_count, + ellipse: ellipse, + dash_arc_length: dash_arc_length, + } + } + + pub fn populate_gpu_data(&self, slice: &mut [GpuBlock32]) { + let (header, dashes) = slice.split_first_mut().unwrap(); + *header = self.corner_data.into(); + + let mut current_arc_length = self.dash_arc_length * 0.5; + for dash_index in 0..self.dash_count { + let arc_length0 = current_arc_length; + current_arc_length += self.dash_arc_length; + + let arc_length1 = current_arc_length; + current_arc_length += self.dash_arc_length; + + let dash_data = BorderCornerDashClipData::new(arc_length0, + arc_length1, + &self.ellipse); + dashes[dash_index] = dash_data.into(); + } + } +} + +/// Represents the common GPU data for writing a +/// clip mask for a border corner. +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct BorderCornerClipData { + /// Local space rect of the border corner. + corner_rect: LayerRect, + /// Local space point that is the center of the + /// circle or ellipse that we are clipping against. + clip_center: LayerPoint, + /// A constant that flips the local space points + /// and tangents of the ellipse for this specific + /// corner. This is used since the ellipse points + /// and tangents are always generated for a single + /// quadrant only. + sign_modifier: LayerPoint, +} + +/// Represents the GPU data for drawing a single dash +/// to a clip mask. A dash clip is defined by two lines. +/// We store a point on the ellipse curve, and a tangent +/// to that point, which allows for efficient line-distance +/// calculations in the fragment shader. +#[derive(Debug, Clone)] +#[repr(C)] +pub struct BorderCornerDashClipData { + pub point0: LayerPoint, + pub tangent0: LayerPoint, + pub point1: LayerPoint, + pub tangent1: LayerPoint, +} + +impl BorderCornerDashClipData { + pub fn new(arc_length0: f32, + arc_length1: f32, + ellipse: &Ellipse) -> BorderCornerDashClipData { + let alpha = ellipse.find_angle_for_arc_length(arc_length0); + let beta = ellipse.find_angle_for_arc_length(arc_length1); + + let (p0, t0) = ellipse.get_point_and_tangent(alpha); + let (p1, t1) = ellipse.get_point_and_tangent(beta); + + BorderCornerDashClipData { + point0: p0, + tangent0: t0, + point1: p1, + tangent1: t1, + } + } +} diff --git a/webrender/src/ellipse.rs b/webrender/src/ellipse.rs new file mode 100644 index 0000000000..8511f159d0 --- /dev/null +++ b/webrender/src/ellipse.rs @@ -0,0 +1,94 @@ +/* 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 webrender_traits::{LayerPoint, LayerSize}; +use std::f32::consts::FRAC_PI_2; + +/// Number of steps to integrate arc length over. +const STEP_COUNT: usize = 20; + +/// Represents an ellipse centred at a local space origin. +#[derive(Debug, Clone)] +pub struct Ellipse { + pub radius: LayerSize, +} + +impl Ellipse { + pub fn new(radius: LayerSize) -> Ellipse { + Ellipse { + radius: radius, + } + } + + /// Use Simpsons rule to approximate the arc length of + /// part of an ellipse. Note that this only works over + /// the range of [0, pi/2]. + // TODO(gw): This is a simplistic way to estimate the + // arc length of an ellipse segment. We can probably use + // a faster / more accurate method! + fn get_simpson_length(&self, theta: f32) -> f32 { + let df = theta / STEP_COUNT as f32; + let mut sum = 0.0; + + for i in 0..(STEP_COUNT+1) { + let (sin_theta, cos_theta) = (i as f32 * df).sin_cos(); + let a = self.radius.width * sin_theta; + let b = self.radius.height * cos_theta; + let y = (a*a + b*b).sqrt(); + let q = if i == 0 || i == STEP_COUNT { + 1.0 + } else if i % 2 == 0 { + 2.0 + } else { + 4.0 + }; + + sum += q * y; + } + + (df / 3.0) * sum + } + + /// Binary search to estimate the angle of an ellipse + /// for a given arc length. This only searches over the + /// first quadrant of an ellipse. + pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 { + let epsilon = 0.01; + let mut low = 0.0; + let mut high = FRAC_PI_2; + let mut theta = 0.0; + + while low <= high { + theta = 0.5 * (low + high); + let length = self.get_simpson_length(theta); + + if (length - arc_length).abs() < epsilon { + break; + } else if length < arc_length { + low = theta; + } else { + high = theta; + } + } + + theta + } + + /// Approximate the total length of the first quadrant of + /// this ellipse. + pub fn get_quarter_arc_length(&self) -> f32 { + self.get_simpson_length(FRAC_PI_2) + } + + /// Get a point and tangent on this ellipse from a given angle. + /// This only works for the first quadrant of the ellipse. + pub fn get_point_and_tangent(&self, theta: f32) -> (LayerPoint, LayerPoint) { + let (sin_theta, cos_theta) = theta.sin_cos(); + let point = LayerPoint::new(self.radius.width * cos_theta, + self.radius.height * sin_theta); + let tangent = LayerPoint::new(-self.radius.width * sin_theta, + self.radius.height * cos_theta); + (point, tangent) + } +} diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 48f6ac8503..1a5d93f0de 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -52,6 +52,7 @@ mod debug_colors; mod debug_font_data; mod debug_render; mod device; +mod ellipse; mod frame; mod frame_builder; mod freelist; diff --git a/webrender/src/mask_cache.rs b/webrender/src/mask_cache.rs index 9c94d4f5a8..5cfbfa699f 100644 --- a/webrender/src/mask_cache.rs +++ b/webrender/src/mask_cache.rs @@ -2,6 +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 border::BorderCornerClipSource; use gpu_store::GpuStoreAddress; use prim_store::{ClipData, GpuBlock32, PrimitiveStore}; use prim_store::{CLIP_DATA_GPU_SIZE, MASK_DATA_GPU_SIZE}; @@ -47,12 +48,19 @@ pub enum ClipSource { // for clip/scroll nodes, but false for primitives, where // the clip rect is handled in local space. Region(ClipRegion, RegionMode), + + // TODO(gw): This currently only handles dashed style + // clips, where the border style is dashed for both + // adjacent border edges. Expand to handle dotted style + // and different styles per edge. + BorderCorner(BorderCornerClipSource), } impl ClipSource { pub fn image_mask(&self) -> Option { match *self { - ClipSource::Complex(..) => None, + ClipSource::Complex(..) | + ClipSource::BorderCorner{..} => None, ClipSource::Region(ref region, _) => region.image_mask, } } @@ -112,9 +120,10 @@ pub enum MaskBounds { #[derive(Clone, Debug)] pub struct MaskCacheInfo { - pub clip_range: ClipAddressRange, - pub effective_clip_count: usize, + pub complex_clip_range: ClipAddressRange, + pub effective_complex_clip_count: usize, pub image: Option<(ImageMask, GpuStoreAddress)>, + pub border_corners: Vec<(BorderCornerClipSource, GpuStoreAddress)>, pub bounds: Option, pub is_aligned: bool, } @@ -130,42 +139,50 @@ impl MaskCacheInfo { } let mut image = None; - let mut clip_count = 0; + let mut border_corners = Vec::new(); + let mut complex_clip_count = 0; // Work out how much clip data space we need to allocate // and if we have an image mask. for clip in clips { match *clip { ClipSource::Complex(..) => { - clip_count += 1; - }, + complex_clip_count += 1; + } ClipSource::Region(ref region, region_mode) => { if let Some(info) = region.image_mask { debug_assert!(image.is_none()); // TODO(gw): Support >1 image mask! image = Some((info, clip_store.alloc(MASK_DATA_GPU_SIZE))); } - clip_count += region.complex.length; + complex_clip_count += region.complex.length; if region_mode == RegionMode::IncludeRect { - clip_count += 1; + complex_clip_count += 1; } - }, + } + ClipSource::BorderCorner(ref source) => { + // One block for the corner header, plus one + // block per dash to clip out. + let gpu_address = clip_store.alloc(1 + source.dash_count); + border_corners.push((source.clone(), gpu_address)); + } } } - let clip_range = ClipAddressRange { - start: if clip_count > 0 { - clip_store.alloc(CLIP_DATA_GPU_SIZE * clip_count) + let complex_clip_range = ClipAddressRange { + start: if complex_clip_count > 0 { + clip_store.alloc(CLIP_DATA_GPU_SIZE * complex_clip_count) } else { GpuStoreAddress(0) }, - item_count: clip_count, + item_count: complex_clip_count, }; Some(MaskCacheInfo { - clip_range: clip_range, - effective_clip_count: clip_range.item_count, + complex_clip_range: complex_clip_range, + effective_complex_clip_count: complex_clip_range.item_count, image: image, + border_corners: border_corners, bounds: None, is_aligned: true, }) @@ -186,8 +203,9 @@ impl MaskCacheInfo { LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP))); let mut local_inner: Option = None; let mut has_clip_out = false; + let mut has_border_clip = false; - self.effective_clip_count = 0; + self.effective_complex_clip_count = 0; self.is_aligned = is_aligned; for source in sources { @@ -198,9 +216,9 @@ impl MaskCacheInfo { if mode == ClipMode::ClipOut { has_clip_out = true; } - debug_assert!(self.effective_clip_count < self.clip_range.item_count); - let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE; - self.effective_clip_count += 1; + debug_assert!(self.effective_complex_clip_count < self.complex_clip_range.item_count); + let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE; + self.effective_complex_clip_count += 1; let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE); let data = ClipData::uniform(rect, radius, mode); @@ -223,17 +241,17 @@ impl MaskCacheInfo { let clips = aux_lists.complex_clip_regions(®ion.complex); if !self.is_aligned && region_mode == RegionMode::IncludeRect { // we have an extra clip rect coming from the transformed layer - debug_assert!(self.effective_clip_count < self.clip_range.item_count); - let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE; - self.effective_clip_count += 1; + debug_assert!(self.effective_complex_clip_count < self.complex_clip_range.item_count); + let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE; + self.effective_complex_clip_count += 1; let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE); PrimitiveStore::populate_clip_data(slice, ClipData::uniform(region.main, 0.0, ClipMode::Clip)); } - debug_assert!(self.effective_clip_count + clips.len() <= self.clip_range.item_count); - let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE; - self.effective_clip_count += clips.len(); + debug_assert!(self.effective_complex_clip_count + clips.len() <= self.complex_clip_range.item_count); + let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE; + self.effective_complex_clip_count += clips.len(); let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE * clips.len()); for (clip, chunk) in clips.iter().zip(slice.chunks_mut(CLIP_DATA_GPU_SIZE)) { @@ -244,12 +262,20 @@ impl MaskCacheInfo { .and_then(|ref inner| r.intersection(inner))); } } + ClipSource::BorderCorner{..} => {} } } + for &(ref source, gpu_address) in &self.border_corners { + has_border_clip = true; + let slice = clip_store.get_slice_mut(gpu_address, + 1 + source.dash_count); + source.populate_gpu_data(slice); + } + // Work out the type of mask geometry we have, based on the // list of clip sources above. - if has_clip_out { + if has_clip_out || has_border_clip { // For clip-out, the mask rect is not known. self.bounds = Some(MaskBounds::None); } else { @@ -288,10 +314,12 @@ impl MaskCacheInfo { } } - /// Check if this `MaskCacheInfo` actually carries any masks. `effective_clip_count` + /// Check if this `MaskCacheInfo` actually carries any masks. `effective_complex_clip_count` /// can change during the `update` call depending on the transformation, so the mask may /// appear to be empty. pub fn is_masking(&self) -> bool { - self.image.is_some() || self.effective_clip_count != 0 + self.image.is_some() || + self.effective_complex_clip_count != 0 || + !self.border_corners.is_empty() } } diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 167d68561d..eb99e1c598 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; +use border::{BorderCornerClipData, BorderCornerDashClipData}; use euclid::{Size2D}; use gpu_store::GpuStoreAddress; use internal_types::{SourceTexture, PackedTexel}; @@ -1042,6 +1043,7 @@ impl PrimitiveStore { let (rect, is_complex) = match source { ClipSource::Complex(rect, radius, _) => (rect, radius > 0.0), ClipSource::Region(ref region, _) => (region.main, region.is_complex()), + ClipSource::BorderCorner{..} => panic!("Not supported!"), }; self.gpu_geometry.get_mut(GpuStoreAddress(index.0 as i32)) .local_clip_rect = rect; @@ -1340,7 +1342,8 @@ define_gpu_block!(GpuBlock16: [f32; 4] = TextRunPrimitiveGpu, ImagePrimitiveGpu, YuvImagePrimitiveGpu ); define_gpu_block!(GpuBlock32: [f32; 8] = - GradientStopGpu, ClipCorner, ClipRect, ImageMaskData + GradientStopGpu, ClipCorner, ClipRect, ImageMaskData, + BorderCornerClipData, BorderCornerDashClipData ); define_gpu_block!(GpuBlock64: [f32; 16] = GradientPrimitiveGpu, RadialGradientPrimitiveGpu, BoxShadowPrimitiveGpu diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index 5db91128f7..f2c6f2dc2f 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -236,7 +236,7 @@ impl RenderTask { if inner_rect.is_some() && clips.len() == 1 { let (_, ref clip_info) = clips[0]; if clip_info.image.is_none() && - clip_info.effective_clip_count == 1 && + clip_info.effective_complex_clip_count == 1 && clip_info.is_aligned { geometry_kind = MaskGeometryKind::CornersOnly; } diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 2cda9f470d..0fbf7e10c5 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -497,6 +497,7 @@ pub struct Renderer { /// of these shaders are also used by the primitive shaders. cs_clip_rectangle: LazilyCompiledShader, cs_clip_image: LazilyCompiledShader, + cs_clip_border: LazilyCompiledShader, // The are "primitive shaders". These shaders draw and blend // final results on screen. They are aware of tile boundaries. @@ -683,6 +684,14 @@ impl Renderer { options.precache_shaders) }; + let cs_clip_border = try!{ + LazilyCompiledShader::new(ShaderKind::ClipCache, + "cs_clip_border", + &[], + &mut device, + options.precache_shaders) + }; + let ps_rectangle = try!{ PrimitiveShader::new("ps_rectangle", &mut device, @@ -1040,6 +1049,7 @@ impl Renderer { cs_text_run: cs_text_run, cs_blur: cs_blur, cs_clip_rectangle: cs_clip_rectangle, + cs_clip_border: cs_clip_border, cs_clip_image: cs_clip_image, ps_rectangle: ps_rectangle, ps_rectangle_clip: ps_rectangle_clip, @@ -1801,6 +1811,16 @@ impl Renderer { &textures, &projection); } + // draw special border clips + if !target.clip_batcher.borders.is_empty() { + let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders"); + let shader = self.cs_clip_border.get(&mut self.device).unwrap(); + self.draw_instanced_batch(&target.clip_batcher.borders, + vao, + shader, + &BatchTextures::no_texture(), + &projection); + } } } diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index 695427ddb1..1f567e8e76 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -630,6 +630,7 @@ pub struct ClipBatcher { pub rectangles: Vec, /// Image draws apply the image masking. pub images: HashMap>, + pub borders: Vec, } impl ClipBatcher { @@ -637,6 +638,7 @@ impl ClipBatcher { ClipBatcher { rectangles: Vec::new(), images: HashMap::new(), + borders: Vec::new(), } } @@ -654,8 +656,8 @@ impl ClipBatcher { segment: 0, }; - for clip_index in 0..info.effective_clip_count as usize { - let offset = info.clip_range.start.0 + ((CLIP_DATA_GPU_SIZE * clip_index) as i32); + for clip_index in 0..info.effective_complex_clip_count as usize { + let offset = info.complex_clip_range.start.0 + ((CLIP_DATA_GPU_SIZE * clip_index) as i32); match geometry_kind { MaskGeometryKind::Default => { self.rectangles.push(CacheClipInstance { @@ -700,6 +702,16 @@ impl ClipBatcher { ..instance }) } + + for &(ref source, gpu_address) in &info.border_corners { + for dash_index in 0..source.dash_count { + self.borders.push(CacheClipInstance { + address: gpu_address, + segment: dash_index as i32, + ..instance + }) + } + } } } } diff --git a/wrench/reftests/border/border-suite-2.png b/wrench/reftests/border/border-suite-2.png index 71f21fbc04..6df896ad86 100644 Binary files a/wrench/reftests/border/border-suite-2.png and b/wrench/reftests/border/border-suite-2.png differ diff --git a/wrench/reftests/border/border-suite-2.yaml b/wrench/reftests/border/border-suite-2.yaml index ddda497b07..be03f1faa9 100644 --- a/wrench/reftests/border/border-suite-2.yaml +++ b/wrench/reftests/border/border-suite-2.yaml @@ -89,3 +89,57 @@ root: style: ridge color: [ red, green, blue, yellow ] radius: 50 + + - type: border + bounds: [ 10, 230, 100, 100 ] + width: 1 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 16 + - type: border + bounds: [ 120, 230, 100, 100 ] + width: 2 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 230, 230, 100, 100 ] + width: 3 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 340, 230, 100, 100 ] + width: 8 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + + - type: border + bounds: [ 10, 340, 200, 200 ] + width: [4, 8, 16, 8] + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: { + top-left: [32, 64], + top-right: [32, 32], + bottom-left: [64, 32], + bottom-right: [32, 32], + } + - type: border + bounds: [ 230, 340, 200, 200 ] + width: 4 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: { + top-left: [64, 128], + top-right: [16, 32], + bottom-left: [40, 18], + bottom-right: [100, 50], + }