Source code

Revision control

Copy as Markdown

Other Tools

/* 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 api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU};
use api::{NormalBorder as ApiNormalBorder, RepeatMode};
use api::units::*;
use crate::clip::ClipNodeId;
use crate::ellipse::Ellipse;
use euclid::vec2;
use crate::scene_building::SceneBuilder;
use crate::spatial_tree::SpatialNodeIndex;
use crate::gpu_types::{BorderInstance, BorderSegment, BrushFlags};
use crate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor};
use crate::prim_store::borders::{NormalBorderPrim, NormalBorderData};
use crate::util::{lerp, RectHelpers};
use crate::internal_types::LayoutPrimitiveInfo;
use crate::segment::EdgeAaSegmentMask;
// Using 2048 as the maximum radius in device space before which we
// start stretching is up for debate.
// the value must be chosen so that the corners will not use an
// unreasonable amount of memory but should allow crisp corners in the
// common cases.
/// Maximum resolution in device pixels at which borders are rasterized.
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: u32 = 2048;
// TODO(gw): Perhaps there is a better way to store
// the border cache key than duplicating
// all the border structs with hashable
// variants...
#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BorderRadiusAu {
pub top_left: LayoutSizeAu,
pub top_right: LayoutSizeAu,
pub bottom_left: LayoutSizeAu,
pub bottom_right: LayoutSizeAu,
}
impl From<BorderRadius> for BorderRadiusAu {
fn from(radius: BorderRadius) -> BorderRadiusAu {
BorderRadiusAu {
top_left: radius.top_left.to_au(),
top_right: radius.top_right.to_au(),
bottom_right: radius.bottom_right.to_au(),
bottom_left: radius.bottom_left.to_au(),
}
}
}
impl From<BorderRadiusAu> for BorderRadius {
fn from(radius: BorderRadiusAu) -> Self {
BorderRadius {
top_left: LayoutSize::from_au(radius.top_left),
top_right: LayoutSize::from_au(radius.top_right),
bottom_right: LayoutSize::from_au(radius.bottom_right),
bottom_left: LayoutSize::from_au(radius.bottom_left),
}
}
}
#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BorderSideAu {
pub color: ColorU,
pub style: BorderStyle,
}
impl From<BorderSide> for BorderSideAu {
fn from(side: BorderSide) -> Self {
BorderSideAu {
color: side.color.into(),
style: side.style,
}
}
}
impl From<BorderSideAu> for BorderSide {
fn from(side: BorderSideAu) -> Self {
BorderSide {
color: side.color.into(),
style: side.style,
}
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Clone, Hash, Eq, MallocSizeOf, PartialEq)]
pub struct NormalBorderAu {
pub left: BorderSideAu,
pub right: BorderSideAu,
pub top: BorderSideAu,
pub bottom: BorderSideAu,
pub radius: BorderRadiusAu,
/// Whether to apply anti-aliasing on the border corners.
///
/// Note that for this to be `false` and work, this requires the borders to
/// be solid, and no border-radius.
pub do_aa: bool,
}
impl NormalBorderAu {
// Construct a border based upon self with color
pub fn with_color(&self, color: ColorU) -> Self {
let mut b = self.clone();
b.left.color = color;
b.right.color = color;
b.top.color = color;
b.bottom.color = color;
b
}
}
impl From<ApiNormalBorder> for NormalBorderAu {
fn from(border: ApiNormalBorder) -> Self {
NormalBorderAu {
left: border.left.into(),
right: border.right.into(),
top: border.top.into(),
bottom: border.bottom.into(),
radius: border.radius.into(),
do_aa: border.do_aa,
}
}
}
impl From<NormalBorderAu> for ApiNormalBorder {
fn from(border: NormalBorderAu) -> Self {
ApiNormalBorder {
left: border.left.into(),
right: border.right.into(),
top: border.top.into(),
bottom: border.bottom.into(),
radius: border.radius.into(),
do_aa: border.do_aa,
}
}
}
/// Cache key that uniquely identifies a border
/// segment in the render task cache.
#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BorderSegmentCacheKey {
pub size: LayoutSizeAu,
pub radius: LayoutSizeAu,
pub side0: BorderSideAu,
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(
radius: &mut BorderRadius,
size: LayoutSize,
) {
let mut ratio = 1.0;
let top_left_radius = &mut radius.top_left;
let top_right_radius = &mut radius.top_right;
let bottom_right_radius = &mut radius.bottom_right;
let bottom_left_radius = &mut radius.bottom_left;
let sum = top_left_radius.width + top_right_radius.width;
if size.width < sum {
ratio = f32::min(ratio, size.width / sum);
}
let sum = bottom_left_radius.width + bottom_right_radius.width;
if size.width < sum {
ratio = f32::min(ratio, size.width / sum);
}
let sum = top_left_radius.height + bottom_left_radius.height;
if size.height < sum {
ratio = f32::min(ratio, size.height / sum);
}
let sum = top_right_radius.height + bottom_right_radius.height;
if size.height < sum {
ratio = f32::min(ratio, size.height / sum);
}
if ratio < 1. {
top_left_radius.width *= ratio;
top_left_radius.height *= ratio;
top_right_radius.width *= ratio;
top_right_radius.height *= ratio;
bottom_left_radius.width *= ratio;
bottom_left_radius.height *= ratio;
bottom_right_radius.width *= ratio;
bottom_right_radius.height *= ratio;
}
}
impl<'a> SceneBuilder<'a> {
pub fn add_normal_border(
&mut self,
info: &LayoutPrimitiveInfo,
border: &ApiNormalBorder,
widths: LayoutSideOffsets,
spatial_node_index: SpatialNodeIndex,
clip_node_id: ClipNodeId,
) {
let mut border = *border;
ensure_no_corner_overlap(&mut border.radius, info.rect.size());
self.add_primitive(
spatial_node_index,
clip_node_id,
info,
Vec::new(),
NormalBorderPrim {
border: border.into(),
widths: widths.to_au(),
},
);
}
}
pub trait BorderSideHelpers {
fn border_color(&self, is_inner_border: bool) -> ColorF;
}
impl BorderSideHelpers for BorderSide {
fn border_color(&self, is_inner_border: bool) -> ColorF {
let lighter = match self.style {
BorderStyle::Inset => is_inner_border,
BorderStyle::Outset => !is_inner_border,
_ => return self.color,
};
// The modulate colors below are not part of the specification. They are
// derived from the Gecko source code and experimentation, and used to
// modulate the colors in order to generate colors for the inset/outset
// and groove/ridge border styles.
//
// NOTE(emilio): Gecko at least takes the background color into
// account, should we do the same? Looks a bit annoying for this.
//
// NOTE(emilio): If you change this algorithm, do the same change on
// get_colors_for_side in cs_border_segment.glsl.
if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
let scale = if lighter { 1.0 } else { 2.0 / 3.0 };
return self.color.scale_rgb(scale)
}
let black = if lighter { 0.7 } else { 0.3 };
ColorF::new(black, black, black, self.color.a)
}
}
/// The kind of border corner clip.
#[repr(C)]
#[derive(Copy, Debug, Clone, PartialEq)]
pub enum BorderClipKind {
DashCorner = 1,
DashEdge = 2,
Dot = 3,
}
fn compute_outer_and_clip_sign(
corner_segment: BorderSegment,
radius: DeviceSize,
) -> (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)
}
fn write_dashed_corner_instances(
corner_radius: DeviceSize,
widths: DeviceSize,
segment: BorderSegment,
base_instance: &BorderInstance,
instances: &mut Vec<BorderInstance>,
) -> Result<(), ()> {
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);
if num_half_dashes == 0 {
return Err(());
}
let num_half_dashes = num_half_dashes.min(MAX_DASH_COUNT);
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 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 tangent0 = DeviceVector2D::new(
-tangent0.x * clip_sign.x,
-tangent0.y * clip_sign.y,
);
let point1 = DevicePoint::new(
outer.x + clip_sign.x * (corner_radius.width - point1.x),
outer.y + clip_sign.y * (corner_radius.height - point1.y),
);
let tangent1 = DeviceVector2D::new(
-tangent1.x * clip_sign.x,
-tangent1.y * clip_sign.y,
);
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
});
}
Ok(())
}
fn write_dotted_corner_instances(
corner_radius: DeviceSize,
widths: DeviceSize,
segment: BorderSegment,
base_instance: &BorderInstance,
instances: &mut Vec<BorderInstance>,
) -> 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;
}
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)
};
if max_dot_count == 0 {
return Err(());
}
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;
}
// 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)]
struct DotInfo {
arc_pos: f32,
diameter: f32,
}
impl DotInfo {
fn new(arc_pos: f32, diameter: f32) -> DotInfo {
DotInfo { arc_pos, diameter }
}
}
/// Information needed to place and draw a border edge.
#[derive(Debug)]
struct EdgeInfo {
/// Offset in local space to place the edge from origin.
local_offset: f32,
/// Size of the edge in local space.
local_size: f32,
/// Local stretch size for this edge (repeat past this).
stretch_size: f32,
}
impl EdgeInfo {
fn new(
local_offset: f32,
local_size: f32,
stretch_size: f32,
) -> Self {
Self {
local_offset,
local_size,
stretch_size,
}
}
}
// Given a side width and the available space, compute the half-dash (half of
// the 'on' segment) and the count of them for a given segment.
fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) {
let half_dash = side_width * 1.5;
// 16k dashes should be enough for anyone
let num_half_dashes = (total_size / half_dash).ceil().min(16.0 * 1024.0) as u32;
if num_half_dashes == 0 {
return (0., 0);
}
// TODO(emilio): Gecko has some other heuristics here to start with a full
// dash when the border side is zero, for example. We might consider those
// in the future.
let num_half_dashes = if num_half_dashes % 4 != 0 {
num_half_dashes + 4 - num_half_dashes % 4
} else {
num_half_dashes
};
let half_dash = total_size / num_half_dashes as f32;
(half_dash, num_half_dashes)
}
// Get the needed size in device pixels for an edge,
// based on the border style of that edge. This is used
// to determine how big the render task should be.
fn get_edge_info(
style: BorderStyle,
side_width: f32,
avail_size: f32,
) -> EdgeInfo {
// To avoid division by zero below.
if side_width <= 0.0 || avail_size <= 0.0 {
return EdgeInfo::new(0.0, 0.0, 0.0);
}
match style {
BorderStyle::Dashed => {
// Basically, two times the dash size.
let (half_dash, _num_half_dashes) =
compute_half_dash(side_width, avail_size);
let stretch_size = 2.0 * 2.0 * half_dash;
EdgeInfo::new(0., avail_size, stretch_size)
}
BorderStyle::Dotted => {
let dot_and_space_size = 2.0 * side_width;
if avail_size < dot_and_space_size * 0.75 {
return EdgeInfo::new(0.0, 0.0, 0.0);
}
let approx_dot_count = avail_size / dot_and_space_size;
let dot_count = approx_dot_count.floor().max(1.0);
let used_size = dot_count * dot_and_space_size;
let extra_space = avail_size - used_size;
let stretch_size = dot_and_space_size;
let offset = (extra_space * 0.5).round();
EdgeInfo::new(offset, used_size, stretch_size)
}
_ => {
EdgeInfo::new(0.0, avail_size, 8.0)
}
}
}
/// Create the set of border segments and render task
/// cache keys for a given CSS border.
pub fn create_border_segments(
size: LayoutSize,
border: &ApiNormalBorder,
widths: &LayoutSideOffsets,
border_segments: &mut Vec<BorderSegmentInfo>,
brush_segments: &mut Vec<BrushSegment>,
) {
let rect = LayoutRect::from_size(size);
let overlap = LayoutSize::new(
(widths.left + widths.right - size.width).max(0.0),
(widths.top + widths.bottom - size.height).max(0.0),
);
let non_overlapping_widths = LayoutSideOffsets::new(
widths.top - overlap.height / 2.0,
widths.right - overlap.width / 2.0,
widths.bottom - overlap.height / 2.0,
widths.left - overlap.width / 2.0,
);
let local_size_tl = LayoutSize::new(
border.radius.top_left.width.max(widths.left),
border.radius.top_left.height.max(widths.top),
);
let local_size_tr = LayoutSize::new(
border.radius.top_right.width.max(widths.right),
border.radius.top_right.height.max(widths.top),
);
let local_size_br = LayoutSize::new(
border.radius.bottom_right.width.max(widths.right),
border.radius.bottom_right.height.max(widths.bottom),
);
let local_size_bl = LayoutSize::new(
border.radius.bottom_left.width.max(widths.left),
border.radius.bottom_left.height.max(widths.bottom),
);
let top_edge_info = get_edge_info(
border.top.style,
widths.top,
rect.width() - local_size_tl.width - local_size_tr.width,
);
let bottom_edge_info = get_edge_info(
border.bottom.style,
widths.bottom,
rect.width() - local_size_bl.width - local_size_br.width,
);
let left_edge_info = get_edge_info(
border.left.style,
widths.left,
rect.height() - local_size_tl.height - local_size_bl.height,
);
let right_edge_info = get_edge_info(
border.right.style,
widths.right,
rect.height() - local_size_tr.height - local_size_br.height,
);
add_edge_segment(
LayoutRect::from_floats(
rect.min.x,
rect.min.y + local_size_tl.height + left_edge_info.local_offset,
rect.min.x + non_overlapping_widths.left,
rect.min.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
),
&left_edge_info,
border.left,
non_overlapping_widths.left,
BorderSegment::Left,
EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
brush_segments,
border_segments,
border.do_aa,
);
add_edge_segment(
LayoutRect::from_floats(
rect.min.x + local_size_tl.width + top_edge_info.local_offset,
rect.min.y,
rect.min.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
rect.min.y + non_overlapping_widths.top,
),
&top_edge_info,
border.top,
non_overlapping_widths.top,
BorderSegment::Top,
EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
brush_segments,
border_segments,
border.do_aa,
);
add_edge_segment(
LayoutRect::from_floats(
rect.min.x + rect.width() - non_overlapping_widths.right,
rect.min.y + local_size_tr.height + right_edge_info.local_offset,
rect.min.x + rect.width(),
rect.min.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
),
&right_edge_info,
border.right,
non_overlapping_widths.right,
BorderSegment::Right,
EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
brush_segments,
border_segments,
border.do_aa,
);
add_edge_segment(
LayoutRect::from_floats(
rect.min.x + local_size_bl.width + bottom_edge_info.local_offset,
rect.min.y + rect.height() - non_overlapping_widths.bottom,
rect.min.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
rect.min.y + rect.height(),
),
&bottom_edge_info,
border.bottom,
non_overlapping_widths.bottom,
BorderSegment::Bottom,
EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
brush_segments,
border_segments,
border.do_aa,
);
add_corner_segment(
LayoutRect::from_floats(
rect.min.x,
rect.min.y,
rect.min.x + local_size_tl.width,
rect.min.y + local_size_tl.height,
),
LayoutRect::from_floats(
rect.min.x,
rect.min.y,
rect.max.x - non_overlapping_widths.right,
rect.max.y - non_overlapping_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,
);
add_corner_segment(
LayoutRect::from_floats(
rect.min.x + rect.width() - local_size_tr.width,
rect.min.y,
rect.min.x + rect.width(),
rect.min.y + local_size_tr.height,
),
LayoutRect::from_floats(
rect.min.x + non_overlapping_widths.left,
rect.min.y,
rect.max.x,
rect.max.y - non_overlapping_widths.bottom,
),
border.top,
border.right,
LayoutSize::new(widths.right, widths.top),
border.radius.top_right,
BorderSegment::TopRight,
EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
rect.min,
border.radius.top_left,
rect.max,
border.radius.bottom_right,
brush_segments,
border_segments,
border.do_aa,
);
add_corner_segment(
LayoutRect::from_floats(
rect.min.x + rect.width() - local_size_br.width,
rect.min.y + rect.height() - local_size_br.height,
rect.min.x + rect.width(),
rect.min.y + rect.height(),
),
LayoutRect::from_floats(
rect.min.x + non_overlapping_widths.left,
rect.min.y + non_overlapping_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,
);
add_corner_segment(
LayoutRect::from_floats(
rect.min.x,
rect.min.y + rect.height() - local_size_bl.height,
rect.min.x + local_size_bl.width,
rect.min.y + rect.height(),
),
LayoutRect::from_floats(
rect.min.x,
rect.min.y + non_overlapping_widths.top,
rect.max.x - non_overlapping_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.max,
border.radius.bottom_right,
rect.min,
border.radius.top_left,
brush_segments,
border_segments,
border.do_aa,
);
}
/// Computes the maximum scale that we allow for this set of border parameters.
/// capping the scale will result in rendering very large corners at a lower
/// resolution and stretching them, so they will have the right shape, but
/// blurrier.
pub fn get_max_scale_for_border(
border_data: &NormalBorderData,
) -> LayoutToDeviceScale {
let mut r = 1.0;
for segment in &border_data.border_segments {
let size = segment.local_task_size;
r = size.width.max(size.height.max(r));
}
LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r)
}
fn add_segment(
task_rect: DeviceRect,
style0: BorderStyle,
style1: BorderStyle,
color0: ColorF,
color1: ColorF,
segment: BorderSegment,
instances: &mut Vec<BorderInstance>,
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) |
((style1 as i32) << 16) |
((do_aa as i32) << 28);
let base_instance = BorderInstance {
task_origin: DevicePoint::zero(),
local_rect: task_rect,
flags: base_flags,
color0: color0.premultiplied(),
color1: color1.premultiplied(),
widths,
radius,
clip_params: [0.0; 8],
};
match segment {
BorderSegment::TopLeft |
BorderSegment::TopRight |
BorderSegment::BottomLeft |
BorderSegment::BottomRight => {
// TODO(gw): Similarly to the old border code, we don't correctly handle a a corner
// that is dashed on one edge, and dotted on another. We can handle this
// in the future by submitting two instances, each one with one side
// color set to have an alpha of 0.
if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) ||
(style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) {
warn!("TODO: Handle a corner with dotted / dashed transition.");
}
let dashed_or_dotted_corner = match style0 {
BorderStyle::Dashed => {
write_dashed_corner_instances(
radius,
widths,
segment,
&base_instance,
instances,
)
}
BorderStyle::Dotted => {
write_dotted_corner_instances(
radius,
widths,
segment,
&base_instance,
instances,
)
}
_ => Err(()),
};
if dashed_or_dotted_corner.is_err() {
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 |
BorderSegment::Bottom |
BorderSegment::Right |
BorderSegment::Left => {
let is_vertical = segment == BorderSegment::Left ||
segment == BorderSegment::Right;
match style0 {
BorderStyle::Dashed => {
let (x, y) = if is_vertical {
let half_dash_size = task_rect.height() * 0.25;
(0., half_dash_size)
} else {
let half_dash_size = task_rect.width() * 0.25;
(half_dash_size, 0.)
};
instances.push(BorderInstance {
flags: base_flags | ((BorderClipKind::DashEdge as i32) << 24),
clip_params: [
x, y, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
..base_instance
});
}
BorderStyle::Dotted => {
let (x, y, r) = if is_vertical {
(widths.width * 0.5,
widths.width,
widths.width * 0.5)
} else {
(widths.height,
widths.height * 0.5,
widths.height * 0.5)
};
instances.push(BorderInstance {
flags: base_flags | ((BorderClipKind::Dot as i32) << 24),
clip_params: [
x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0,
],
..base_instance
});
}
_ => {
instances.push(base_instance);
}
}
}
}
}
/// Add a corner segment (if valid) to the list of
/// 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,
) {
if side0.color.a <= 0.0 && side1.color.a <= 0.0 {
return;
}
if widths.width <= 0.0 && widths.height <= 0.0 {
return;
}
if side0.style.is_hidden() && side1.style.is_hidden() {
return;
}
let segment_rect = match image_rect.intersection(&non_overlapping_rect) {
Some(rect) => rect,
None => {
return;
}
};
let texture_rect = segment_rect
.translate(-image_rect.min.to_vector())
.scale(1.0 / image_rect.width(), 1.0 / image_rect.height());
brush_segments.push(
BrushSegment::new(
segment_rect,
/* may_need_clip_mask = */ true,
edge_flags,
[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.max, 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.max, 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 {
do_aa,
side0: side0.into(),
side1: side1.into(),
segment,
radius: radius.to_au(),
size: widths.to_au(),
h_adjacent_corner_outer: (h_corner_outer - image_rect.min).to_point().to_au(),
h_adjacent_corner_radius: h_corner_radius.to_au(),
v_adjacent_corner_outer: (v_corner_outer - image_rect.min).to_point().to_au(),
v_adjacent_corner_radius: v_corner_radius.to_au(),
},
});
}
/// Add an edge segment (if valid) to the list of
/// border segments for this primitive.
fn add_edge_segment(
image_rect: LayoutRect,
edge_info: &EdgeInfo,
side: BorderSide,
width: f32,
segment: BorderSegment,
edge_flags: EdgeAaSegmentMask,
brush_segments: &mut Vec<BrushSegment>,
border_segments: &mut Vec<BorderSegmentInfo>,
do_aa: bool,
) {
if side.color.a <= 0.0 {
return;
}
if side.style.is_hidden() {
return;
}
let (size, brush_flags) = match segment {
BorderSegment::Left | BorderSegment::Right => {
(LayoutSize::new(width, edge_info.stretch_size), BrushFlags::SEGMENT_REPEAT_Y)
}
BorderSegment::Top | BorderSegment::Bottom => {
(LayoutSize::new(edge_info.stretch_size, width), BrushFlags::SEGMENT_REPEAT_X)
}
_ => {
unreachable!();
}
};
if image_rect.width() <= 0. || image_rect.height() <= 0. {
return;
}
brush_segments.push(
BrushSegment::new(
image_rect,
/* may_need_clip_mask = */ true,
edge_flags,
[0.0, 0.0, size.width, size.height],
BrushFlags::SEGMENT_RELATIVE | brush_flags,
)
);
border_segments.push(BorderSegmentInfo {
local_task_size: size,
cache_key: BorderSegmentCacheKey {
do_aa,
side0: side.into(),
side1: side.into(),
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(),
},
});
}
/// Build the set of border instances needed to draw a border
/// segment into the render task cache.
pub fn build_border_instances(
cache_key: &BorderSegmentCacheKey,
cache_size: DeviceIntSize,
border: &ApiNormalBorder,
scale: LayoutToDeviceScale,
) -> Vec<BorderInstance> {
let mut instances = Vec::new();
let (side0, side1, flip0, flip1) = match cache_key.segment {
BorderSegment::Left => (&border.left, &border.left, false, false),
BorderSegment::Top => (&border.top, &border.top, false, false),
BorderSegment::Right => (&border.right, &border.right, true, true),
BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
BorderSegment::TopLeft => (&border.left, &border.top, false, false),
BorderSegment::TopRight => (&border.top, &border.right, false, true),
BorderSegment::BottomRight => (&border.right, &border.bottom, true, true),
BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false),
};
let style0 = if side0.style.is_hidden() {
side1.style
} else {
side0.style
};
let style1 = if side1.style.is_hidden() {
side0.style
} else {
side1.style
};
let color0 = side0.border_color(flip0);
let color1 = side1.border_color(flip1);
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::from_size(cache_size.to_f32()),
style0,
style1,
color0,
color1,
cache_key.segment,
&mut instances,
widths,
radius,
border.do_aa,
h_corner_outer,
h_corner_radius,
v_corner_outer,
v_corner_radius,
);
instances
}
impl NinePatchDescriptor {
pub fn create_segments(
&self,
size: LayoutSize,
) -> Vec<BrushSegment> {
let rect = LayoutRect::from_size(size);
// Calculate the local texel coords of the slices.
let px0 = 0.0;
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 / 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.min.x, rect.min.y);
let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top);
let tr_outer = LayoutPoint::new(rect.min.x + rect.width(), rect.min.y);
let tr_inner = tr_outer + vec2(-self.widths.right, self.widths.top);
let bl_outer = LayoutPoint::new(rect.min.x, rect.min.y + rect.height());
let bl_inner = bl_outer + vec2(self.widths.left, -self.widths.bottom);
let br_outer = rect.max;
let br_inner = br_outer - vec2(self.widths.right, self.widths.bottom);
fn add_segment(
segments: &mut Vec<BrushSegment>,
rect: LayoutRect,
uv_rect: TexelRect,
repeat_horizontal: RepeatMode,
repeat_vertical: RepeatMode,
extra_flags: BrushFlags,
) {
if uv_rect.uv1.x <= uv_rect.uv0.x || uv_rect.uv1.y <= uv_rect.uv0.y {
return;
}
// Use segment relative interpolation for all
// instances in this primitive.
let mut brush_flags =
BrushFlags::SEGMENT_RELATIVE |
BrushFlags::SEGMENT_TEXEL_RECT |
extra_flags;
// Enable repeat modes on the segment.
if repeat_horizontal == RepeatMode::Repeat {
brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_CENTERED;
} else if repeat_horizontal == RepeatMode::Round {
brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_ROUND;
}
if repeat_vertical == RepeatMode::Repeat {
brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_CENTERED;
} else if repeat_vertical == RepeatMode::Round {
brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_ROUND;
}
let segment = BrushSegment::new(
rect,
true,
EdgeAaSegmentMask::empty(),
[
uv_rect.uv0.x,
uv_rect.uv0.y,
uv_rect.uv1.x,
uv_rect.uv1.y,
],
brush_flags,
);
segments.push(segment);
}
// Build the list of image segments
let mut segments = Vec::new();
// Top left
add_segment(
&mut segments,
LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
TexelRect::new(px0, py0, px1, py1),
RepeatMode::Stretch,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Top right
add_segment(
&mut segments,
LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
TexelRect::new(px2, py0, px3, py1),
RepeatMode::Stretch,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Bottom right
add_segment(
&mut segments,
LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
TexelRect::new(px2, py2, px3, py3),
RepeatMode::Stretch,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Bottom left
add_segment(
&mut segments,
LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
TexelRect::new(px0, py2, px1, py3),
RepeatMode::Stretch,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Center
if self.fill {
add_segment(
&mut segments,
LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
TexelRect::new(px1, py1, px2, py2),
self.repeat_horizontal,
self.repeat_vertical,
BrushFlags::SEGMENT_NINEPATCH_MIDDLE,
);
}
// Add edge segments.
// Top
add_segment(
&mut segments,
LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
TexelRect::new(px1, py0, px2, py1),
self.repeat_horizontal,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Bottom
add_segment(
&mut segments,
LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
TexelRect::new(px1, py2, px2, py3),
self.repeat_horizontal,
RepeatMode::Stretch,
BrushFlags::empty(),
);
// Left
add_segment(
&mut segments,
LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
TexelRect::new(px0, py1, px1, py2),
RepeatMode::Stretch,
self.repeat_vertical,
BrushFlags::empty(),
);
// Right
add_segment(
&mut segments,
LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
TexelRect::new(px2, py1, px3, py2),
RepeatMode::Stretch,
self.repeat_vertical,
BrushFlags::empty(),
);
segments
}
}