diff --git a/webrender/src/border.rs b/webrender/src/border.rs index aab85a7a75..11360a6581 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -4,7 +4,7 @@ use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF}; use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale}; -use api::{DevicePixel, DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder}; +use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder}; use app_units::Au; use ellipse::Ellipse; use display_list_flattener::DisplayListFlattener; @@ -24,7 +24,7 @@ pub const MAX_BORDER_RESOLUTION: u32 = 2048; /// Maximum number of dots or dashes per segment to avoid freezing and filling up /// memory with unreasonable inputs. It would be better to address this by not building /// a list of per-dot information in the first place. -pub const MAX_DASH_COUNT: usize = 2048; +pub const MAX_DASH_COUNT: u32 = 2048; trait AuSizeConverter { fn to_au(&self) -> LayoutSizeAu; @@ -241,283 +241,271 @@ pub enum BorderClipKind { Dot = 3, } -/// The source data for a border corner clip mask. -#[derive(Debug, Clone)] -struct BorderCornerClipSource { - // FIXME(emilio): the `max_clip_count` name makes no sense for dashed - // borders now that it represents half-dashes. - max_clip_count: usize, - kind: BorderClipKind, - widths: DeviceSize, +fn compute_outer_and_clip_sign( + corner_segment: BorderSegment, radius: DeviceSize, - ellipse: Ellipse, +) -> (DevicePoint, DeviceVector2D) { + let outer_scale = match corner_segment { + BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0), + BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0), + BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0), + BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0), + _ => panic!("bug: expected a corner segment"), + }; + let outer = DevicePoint::new( + outer_scale.x * radius.width, + outer_scale.y * radius.height, + ); + + let clip_sign = DeviceVector2D::new( + 1.0 - 2.0 * outer_scale.x, + 1.0 - 2.0 * outer_scale.y, + ); + + (outer, clip_sign) } -impl BorderCornerClipSource { - pub fn new( - corner_radius: DeviceSize, - widths: DeviceSize, - kind: BorderClipKind, - ) -> BorderCornerClipSource { - // 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. - - let (ellipse, max_clip_count) = match kind { - BorderClipKind::DashEdge => unreachable!("not for corners"), - BorderClipKind::DashCorner => { - let ellipse = Ellipse::new(corner_radius); - - let average_border_width = 0.5 * (widths.width + widths.height); - - let (_half_dash, num_half_dashes) = - compute_half_dash(average_border_width, ellipse.total_arc_length); - - // Round that up to the nearest integer, so that the dash length - // doesn't exceed the ratio above. Add one extra dash to cover - // the last half-dash of the arc. - (ellipse, num_half_dashes as usize) - } - BorderClipKind::Dot => { - let mut corner_radius = corner_radius; - if corner_radius.width < (widths.width / 2.0) { - corner_radius.width = 0.0; - } - if corner_radius.height < (widths.height / 2.0) { - corner_radius.height = 0.0; - } +fn write_dashed_corner_instances( + corner_radius: DeviceSize, + widths: DeviceSize, + segment: BorderSegment, + base_instance: &BorderInstance, + instances: &mut Vec, +) -> Result<(), ()> { + let ellipse = Ellipse::new(corner_radius); - if corner_radius.width == 0. && corner_radius.height == 0. { - (Ellipse::new(corner_radius), 1) - } else { - // The centers of dots follow an ellipse along the middle of the - // border radius. - let inner_radius = (corner_radius - widths * 0.5).abs(); - let ellipse = Ellipse::new(inner_radius); - - // Allocate a "worst case" number of dot clips. This can be - // calculated by taking the minimum edge radius, since that - // will result in the maximum number of dots along the path. - let min_diameter = widths.width.min(widths.height); - - // Get the number of circles (assuming spacing of one diameter - // between dots). - let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter; - - // Add space for one extra dot since they are centered at the - // start of the arc. - (ellipse, max_dot_count.ceil() as usize) - } - } - }; + let average_border_width = 0.5 * (widths.width + widths.height); - BorderCornerClipSource { - kind, - max_clip_count, - ellipse, - widths, - radius: corner_radius, - } + let (_half_dash, num_half_dashes) = + compute_half_dash(average_border_width, ellipse.total_arc_length); + + if num_half_dashes == 0 { + return Err(()); } - // TODO(gw): The naming and structure of BorderCornerClipSource - // don't really make sense. I've left it this way - // for now in order to reduce the size of the - // patch a bit. In the future, when we spent some - // time working on dot/dash placement, we should - // restructure this code to be more consistent - // with how border rendering works now. - pub fn write(self, segment: BorderSegment) -> Vec<[f32; 8]> { - let mut dot_dash_data = Vec::new(); - - if self.max_clip_count == 0 { - return dot_dash_data; - } + let num_half_dashes = num_half_dashes.min(MAX_DASH_COUNT); - let outer_scale = match segment { - BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0), - BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0), - BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0), - BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0), - _ => unreachable!(), + let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius); + + let instance_count = num_half_dashes / 4 + 1; + instances.reserve(instance_count as usize); + + let half_dash_arc_length = + ellipse.total_arc_length / num_half_dashes as f32; + let dash_length = 2. * half_dash_arc_length; + + let mut current_length = 0.; + for i in 0..instance_count { + let arc_length0 = current_length; + current_length += if i == 0 { + half_dash_arc_length + } else { + dash_length }; - let outer = DevicePoint::new( - outer_scale.x * self.radius.width, - outer_scale.y * self.radius.height, + + let arc_length1 = current_length; + current_length += dash_length; + + let alpha = ellipse.find_angle_for_arc_length(arc_length0); + let beta = ellipse.find_angle_for_arc_length(arc_length1); + + let (point0, tangent0) = ellipse.get_point_and_tangent(alpha); + let (point1, tangent1) = ellipse.get_point_and_tangent(beta); + + let point0 = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - point0.x), + outer.y + clip_sign.y * (corner_radius.height - point0.y), ); - let clip_sign = DeviceVector2D::new( - 1.0 - 2.0 * outer_scale.x, - 1.0 - 2.0 * outer_scale.y, + + let tangent0 = DeviceVector2D::new( + -tangent0.x * clip_sign.x, + -tangent0.y * clip_sign.y, ); - let max_clip_count = self.max_clip_count.min(MAX_DASH_COUNT); + let point1 = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - point1.x), + outer.y + clip_sign.y * (corner_radius.height - point1.y), + ); - match self.kind { - BorderClipKind::DashEdge => unreachable!("not for corners"), - BorderClipKind::DashCorner => { - // Get the correct half-dash arc length. - let half_dash_arc_length = - self.ellipse.total_arc_length / max_clip_count as f32; - let dash_length = 2. * half_dash_arc_length; + let tangent1 = DeviceVector2D::new( + -tangent1.x * clip_sign.x, + -tangent1.y * clip_sign.y, + ); - let mut current_length = 0.; + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::DashCorner as i32) << 24), + clip_params: [ + point0.x, + point0.y, + tangent0.x, + tangent0.y, + point1.x, + point1.y, + tangent1.x, + tangent1.y, + ], + .. *base_instance + }); + } - dot_dash_data.reserve(max_clip_count / 4 + 1); - for i in 0 .. (max_clip_count / 4 + 1) { - let arc_length0 = current_length; - current_length += if i == 0 { - half_dash_arc_length - } else { - dash_length - }; + Ok(()) +} - let arc_length1 = current_length; - current_length += dash_length; - - let alpha = self.ellipse.find_angle_for_arc_length(arc_length0); - let beta = self.ellipse.find_angle_for_arc_length(arc_length1); - - let (point0, tangent0) = self.ellipse.get_point_and_tangent(alpha); - let (point1, tangent1) = self.ellipse.get_point_and_tangent(beta); - - let point0 = DevicePoint::new( - outer.x + clip_sign.x * (self.radius.width - point0.x), - outer.y + clip_sign.y * (self.radius.height - point0.y), - ); - - let tangent0 = DeviceVector2D::new( - -tangent0.x * clip_sign.x, - -tangent0.y * clip_sign.y, - ); - - let point1 = DevicePoint::new( - outer.x + clip_sign.x * (self.radius.width - point1.x), - outer.y + clip_sign.y * (self.radius.height - point1.y), - ); - - let tangent1 = DeviceVector2D::new( - -tangent1.x * clip_sign.x, - -tangent1.y * clip_sign.y, - ); - - dot_dash_data.push([ - point0.x, - point0.y, - tangent0.x, - tangent0.y, - point1.x, - point1.y, - tangent1.x, - tangent1.y, - ]); - } - } - BorderClipKind::Dot if max_clip_count == 1 => { - let dot_diameter = lerp(self.widths.width, self.widths.height, 0.5); - dot_dash_data.push([ - self.widths.width / 2.0, self.widths.height / 2.0, 0.5 * dot_diameter, 0., - 0., 0., 0., 0., - ]); - } - BorderClipKind::Dot => { - let mut forward_dots = Vec::with_capacity(max_clip_count / 2 + 1); - let mut back_dots = Vec::with_capacity(max_clip_count / 2 + 1); - let mut leftover_arc_length = 0.0; - - // Alternate between adding dots at the start and end of the - // ellipse arc. This ensures that we always end up with an exact - // half dot at each end of the arc, to match up with the edges. - forward_dots.push(DotInfo::new(self.widths.width, self.widths.width)); - back_dots.push(DotInfo::new( - self.ellipse.total_arc_length - self.widths.height, - self.widths.height, - )); - - for dot_index in 0 .. max_clip_count { - let prev_forward_pos = *forward_dots.last().unwrap(); - let prev_back_pos = *back_dots.last().unwrap(); - - // Select which end of the arc to place a dot from. - // This just alternates between the start and end of - // the arc, which ensures that there is always an - // exact half-dot at each end of the ellipse. - let going_forward = dot_index & 1 == 0; - - let (next_dot_pos, leftover) = if going_forward { - let next_dot_pos = - prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter; - (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos) - } else { - let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter; - (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos) - }; +fn write_dotted_corner_instances( + corner_radius: DeviceSize, + widths: DeviceSize, + segment: BorderSegment, + base_instance: &BorderInstance, + instances: &mut Vec, +) -> Result<(), ()> { + let mut corner_radius = corner_radius; + if corner_radius.width < (widths.width / 2.0) { + corner_radius.width = 0.0; + } + if corner_radius.height < (widths.height / 2.0) { + corner_radius.height = 0.0; + } - // Use a lerp between each edge's dot - // diameter, based on the linear distance - // along the arc to get the diameter of the - // dot at this arc position. - let t = next_dot_pos / self.ellipse.total_arc_length; - let dot_diameter = lerp(self.widths.width, self.widths.height, t); - - // If we can't fit a dot, bail out. - if leftover < dot_diameter { - leftover_arc_length = leftover; - break; - } - - // We can place a dot! - let dot = DotInfo::new(next_dot_pos, dot_diameter); - if going_forward { - forward_dots.push(dot); - } else { - back_dots.push(dot); - } - } + let (ellipse, max_dot_count) = + if corner_radius.width == 0. && corner_radius.height == 0. { + (Ellipse::new(corner_radius), 1) + } else { + // The centers of dots follow an ellipse along the middle of the + // border radius. + let inner_radius = (corner_radius - widths * 0.5).abs(); + let ellipse = Ellipse::new(inner_radius); + + // Allocate a "worst case" number of dot clips. This can be + // calculated by taking the minimum edge radius, since that + // will result in the maximum number of dots along the path. + let min_diameter = widths.width.min(widths.height); + + // Get the number of circles (assuming spacing of one diameter + // between dots). + let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter; + + // Add space for one extra dot since they are centered at the + // start of the arc. + (ellipse, max_dot_count.ceil() as usize) + }; - // Now step through the dots, and distribute any extra - // leftover space on the arc between them evenly. Once - // the final arc position is determined, generate the correct - // arc positions and angles that get passed to the clip shader. - let number_of_dots = forward_dots.len() + back_dots.len(); - let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32; - - let create_dot_data = |ellipse: &Ellipse, arc_length: f32, radius: f32| -> [f32; 8] { - // Represents the GPU data for drawing a single dot to a clip mask. The order - // these are specified must stay in sync with the way this data is read in the - // dot clip shader. - let theta = ellipse.find_angle_for_arc_length(arc_length); - let (center, _) = ellipse.get_point_and_tangent(theta); - - let center = DevicePoint::new( - outer.x + clip_sign.x * (self.radius.width - center.x), - outer.y + clip_sign.y * (self.radius.height - center.y), - ); - - [center.x, center.y, radius, 0.0, 0.0, 0.0, 0.0, 0.0] - }; - - dot_dash_data.reserve(forward_dots.len() + back_dots.len()); - - for (i, dot) in forward_dots.iter().enumerate() { - let extra_dist = i as f32 * extra_space_per_dot; - let dot_data = create_dot_data(&self.ellipse, dot.arc_pos + extra_dist, 0.5 * dot.diameter); - dot_dash_data.push(dot_data); - } + if max_dot_count == 0 { + return Err(()); + } - for (i, dot) in back_dots.iter().enumerate() { - let extra_dist = i as f32 * extra_space_per_dot; - let dot_data = create_dot_data(&self.ellipse, dot.arc_pos - extra_dist, 0.5 * dot.diameter); - dot_dash_data.push(dot_data); - } - } + if max_dot_count == 1 { + let dot_diameter = lerp(widths.width, widths.height, 0.5); + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: [ + widths.width / 2.0, widths.height / 2.0, 0.5 * dot_diameter, 0., + 0., 0., 0., 0., + ], + .. *base_instance + }); + return Ok(()); + } + + let max_dot_count = max_dot_count.min(MAX_DASH_COUNT as usize); + + // FIXME(emilio): Should probably use SmallVec. + let mut forward_dots = Vec::with_capacity(max_dot_count / 2 + 1); + let mut back_dots = Vec::with_capacity(max_dot_count / 2 + 1); + let mut leftover_arc_length = 0.0; + + // Alternate between adding dots at the start and end of the + // ellipse arc. This ensures that we always end up with an exact + // half dot at each end of the arc, to match up with the edges. + forward_dots.push(DotInfo::new(widths.width, widths.width)); + back_dots.push(DotInfo::new( + ellipse.total_arc_length - widths.height, + widths.height, + )); + + let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius); + for dot_index in 0 .. max_dot_count { + let prev_forward_pos = *forward_dots.last().unwrap(); + let prev_back_pos = *back_dots.last().unwrap(); + + // Select which end of the arc to place a dot from. + // This just alternates between the start and end of + // the arc, which ensures that there is always an + // exact half-dot at each end of the ellipse. + let going_forward = dot_index & 1 == 0; + + let (next_dot_pos, leftover) = if going_forward { + let next_dot_pos = + prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter; + (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos) + } else { + let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter; + (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos) + }; + + // Use a lerp between each edge's dot + // diameter, based on the linear distance + // along the arc to get the diameter of the + // dot at this arc position. + let t = next_dot_pos / ellipse.total_arc_length; + let dot_diameter = lerp(widths.width, widths.height, t); + + // If we can't fit a dot, bail out. + if leftover < dot_diameter { + leftover_arc_length = leftover; + break; } - dot_dash_data + // We can place a dot! + let dot = DotInfo::new(next_dot_pos, dot_diameter); + if going_forward { + forward_dots.push(dot); + } else { + back_dots.push(dot); + } } + + // Now step through the dots, and distribute any extra + // leftover space on the arc between them evenly. Once + // the final arc position is determined, generate the correct + // arc positions and angles that get passed to the clip shader. + let number_of_dots = forward_dots.len() + back_dots.len(); + let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32; + + let create_dot_data = |arc_length: f32, dot_radius: f32| -> [f32; 8] { + // Represents the GPU data for drawing a single dot to a clip mask. The order + // these are specified must stay in sync with the way this data is read in the + // dot clip shader. + let theta = ellipse.find_angle_for_arc_length(arc_length); + let (center, _) = ellipse.get_point_and_tangent(theta); + + let center = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - center.x), + outer.y + clip_sign.y * (corner_radius.height - center.y), + ); + + [center.x, center.y, dot_radius, 0.0, 0.0, 0.0, 0.0, 0.0] + }; + + instances.reserve(number_of_dots); + for (i, dot) in forward_dots.iter().enumerate() { + let extra_dist = i as f32 * extra_space_per_dot; + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: create_dot_data(dot.arc_pos + extra_dist, 0.5 * dot.diameter), + .. *base_instance + }); + } + + for (i, dot) in back_dots.iter().enumerate() { + let extra_dist = i as f32 * extra_space_per_dot; + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: create_dot_data(dot.arc_pos - extra_dist, 0.5 * dot.diameter), + .. *base_instance + }); + } + + Ok(()) } #[derive(Copy, Clone, Debug)] @@ -1042,39 +1030,30 @@ fn add_segment( warn!("TODO: Handle a corner with dotted / dashed transition."); } - let clip_kind = match style0 { - BorderStyle::Dashed => Some(BorderClipKind::DashCorner), - BorderStyle::Dotted => Some(BorderClipKind::Dot), - _ => None, - }; - - match clip_kind { - Some(clip_kind) => { - let clip_source = BorderCornerClipSource::new( + let dashed_or_dotted_corner = match style0 { + BorderStyle::Dashed => { + write_dashed_corner_instances( radius, widths, - clip_kind, - ); - - // TODO(gw): Restructure the BorderCornerClipSource code - // so that we don't allocate a Vec here. - let clip_list = clip_source.write(segment); - - if clip_list.is_empty() { - instances.push(base_instance); - } else { - for params in clip_list { - instances.push(BorderInstance { - flags: base_flags | ((clip_kind as i32) << 24), - clip_params: params, - ..base_instance - }); - } - } + segment, + &base_instance, + instances, + ) } - None => { - instances.push(base_instance); + BorderStyle::Dotted => { + write_dotted_corner_instances( + radius, + widths, + segment, + &base_instance, + instances, + ) } + _ => Err(()), + }; + + if dashed_or_dotted_corner.is_err() { + instances.push(base_instance); } } BorderSegment::Top |