/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::BorderRadius; use api::units::*; use euclid::{Point2D, Rect, Box2D, Size2D, Vector2D, point2, point3}; use euclid::{default, Transform2D, Transform3D, Scale, approxeq::ApproxEq}; use plane_split::{Clipper, Polygon}; use std::{i32, f32, fmt, ptr}; use std::borrow::Cow; use std::num::NonZeroUsize; use std::sync::Arc; use std::mem::replace;
usecrate::internal_types::FrameVec;
// Matches the definition of SK_ScalarNearlyZero in Skia. const NEARLY_ZERO: f32 = 1.0 / 4096.0;
/// A typesafe helper that separates new value construction from /// vector growing, allowing LLVM to ideally construct the element in place. pubstruct Allocation<'a, T: 'a> {
vec: &'a mut Vec<T>,
index: usize,
}
impl<'a, T> Allocation<'a, T> { // writing is safe because alloc() ensured enough capacity // and `Allocation` holds a mutable borrow to prevent anyone else // from breaking this invariant. #[inline(always)] pubfn init(self, value: T) -> usize { unsafe {
ptr::write(self.vec.as_mut_ptr().add(self.index), value); self.vec.set_len(self.index + 1);
} self.index
}
}
/// An entry into a vector, similar to `std::collections::hash_map::Entry`. pubenum VecEntry<'a, T: 'a> {
Vacant(Allocation<'a, T>),
Occupied(&'a mut T),
}
pubtrait VecHelper<T> { /// Growns the vector by a single entry, returning the allocation. fn alloc(&mutself) -> Allocation<T>; /// Either returns an existing elemenet, or grows the vector by one. /// Doesn't expect indices to be higher than the current length. fn entry(&mutself, index: usize) -> VecEntry<T>;
/// Equivalent to `mem::replace(&mut vec, Vec::new())` fn take(&mutself) -> Self;
/// Functionally equivalent to `mem::replace(&mut vec, Vec::new())` but tries /// to keep the allocation in the caller if it is empty or replace it with a /// pre-allocated vector. fn take_and_preallocate(&mutself) -> Self;
}
impl<T> VecHelper<T> for Vec<T> { fn alloc(&mutself) -> Allocation<T> { let index = self.len(); ifself.capacity() == index { self.reserve(1);
}
Allocation {
vec: self,
index,
}
}
fn take_and_preallocate(&mutself) -> Self { let len = self.len(); if len == 0 { self.clear(); return Vec::new();
}
replace(self, Vec::with_capacity(len + 8))
}
}
// Represents an optimized transform where there is only // a scale and translation (which are guaranteed to maintain // an axis align rectangle under transformation). The // scaling is applied first, followed by the translation. // TODO(gw): We should try and incorporate F <-> T units here, // but it's a bit tricky to do that now with the // way the current spatial tree works. #[repr(C)] #[derive(Debug, Clone, Copy, MallocSizeOf, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ScaleOffset { pub scale: euclid::Vector2D<f32, euclid::UnknownUnit>, pub offset: euclid::Vector2D<f32, euclid::UnknownUnit>,
}
// Construct a ScaleOffset from a transform. Returns // None if the matrix is not a pure scale / translation. pubfn from_transform<F, T>(
m: &Transform3D<f32, F, T>,
) -> Option<ScaleOffset> {
// To check that we have a pure scale / translation: // Every field must match an identity matrix, except: // - Any value present in tx,ty // - Any value present in sx,sy
pubfn inverse(&self) -> Self { // If either of the scale factors is 0, inverse also has scale 0 // TODO(gw): Consider making this return Option<Self> in future // so that callers can detect and handle when inverse // fails here. ifself.scale.x.approx_eq(&0.0) || self.scale.y.approx_eq(&<span style='color: green'>0.0) { return ScaleOffset::new(0.0, 0.0, 0.0, 0.0);
}
/// Produce a ScaleOffset that includes both self and other. /// The 'self' ScaleOffset is applied after `other`. /// This is equivalent to `Transform3D::pre_transform`. pubfn pre_transform(&self, other: &ScaleOffset) -> Self {
ScaleOffset {
scale: Vector2D::new( self.scale.x * other.scale.x, self.scale.y * other.scale.y,
),
offset: Vector2D::new( self.offset.x + self.scale.x * other.offset.x, self.offset.y + self.scale.y * other.offset.y,
),
}
}
/// Produce a ScaleOffset that includes both self and other. /// The 'other' ScaleOffset is applied after `self`. /// This is equivalent to `Transform3D::then`. #[allow(unused)] pubfn then(&self, other: &ScaleOffset) -> Self {
ScaleOffset {
scale: Vector2D::new( self.scale.x * other.scale.x, self.scale.y * other.scale.y,
),
offset: Vector2D::new(
other.scale.x * self.offset.x + other.offset.x,
other.scale.y * self.offset.y + other.offset.y,
),
}
}
pubfn map_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> { let x0 = rect.min.x * self.scale.x + self.offset.x; let y0 = rect.min.y * self.scale.y + self.offset.y; // TODO: If the supplied rect is invalid (has size < 0) we must ensure that the // returned rect has size zero else some tests fail. Using the max() of the min // and max points ensures that is the case. In future we could catch / assert / // fix these invalid rects earlier, and assert here instead. let x1 = rect.min.x.max(rect.max.x) * self.scale.x + self.offset.x; let y1 = rect.min.y.max(rect.max.y) * self.scale.y + self.offset.y;
pubfn unmap_rect<F, T>(&self, rect: &Box2D<f32, F>) -> Box2D<f32, T> { let x0 = (rect.min.x - self.offset.x) / self.scale.x; let y0 = (rect.min.y - self.offset.y) / self.scale.y; // TODO: If the supplied rect is invalid (has size < 0) we must ensure that the // returned rect has size zero else some tests fail. Using the max() of the min // and max points ensures that is the case. In future we could catch / assert / // fix these invalid rects earlier, and assert here instead. let x1 = (rect.min.x.max(rect.max.x) - self.offset.x) / self.scale.x; let y1 = (rect.min.y.max(rect.max.y) - self.offset.y) / self.scale.y;
// TODO: Implement these in euclid! pubtrait MatrixHelpers<Src, Dst> { /// A port of the preserves2dAxisAlignment function in Skia. /// Defined in the SkMatrix44 class. fn preserves_2d_axis_alignment(&self) -> bool; fn has_perspective_component(&self) -> bool; fn has_2d_inverse(&self) -> bool; /// Check if the matrix post-scaling on either the X or Y axes could cause geometry /// transformed by this matrix to have scaling exceeding the supplied limit. fn exceeds_2d_scale(&self, limit: f64) -> bool; fn inverse_project(&self, target: &Point2D<f32, Dst>) -> Option<Point2D<f32, Src>>; fn inverse_rect_footprint(&self, rect: &Box2D<f32, Dst>) -> Option<Box2D<f32, Src>>; fn is_simple_translation(&self) -> bool; fn is_simple_2d_translation(&self) -> bool; fn is_2d_scale_translation(&self) -> bool; /// If this transform is a rotation or reflection by a multiple of 90 degrees /// (with unit scale, no z-coupling and no perspective), decompose it into a /// `ScaleOffset` plus whether the x and y axes are swapped (the 90/270-degree /// case, where the `ScaleOffset` applies after the swap). Returns `None` /// otherwise. Such a transform keeps content on the same pixel grid, so a /// rect can be snapped across it losslessly. Unlike /// `preserves_2d_axis_alignment`, this also rejects perspective (`m34`) and /// rescaling. fn as_grid_aligned_rotation(&self) -> Option<(ScaleOffset, bool)>; /// Return the determinant of the 2D part of the matrix. fn determinant_2d(&self) -> f32; /// Turn Z transformation into identity. This is useful when crossing "flat" /// transform styled stacking contexts upon traversing the coordinate systems. fn flatten_z_output(&mutself);
/// Find out a point in `Src` that would be projected into the `target`. fn inverse_project(&self, target: &Point2D<f32, Dst>) -> Option<Point2D<f32, Src>> { // form the linear equation for the hyperplane intersection let m = Transform2D::<f32, Src, Dst>::new( self.m11 - target.x * self.m14, self.m12 - target.y * self.m14, self.m21 - target.x * self.m24, self.m22 - target.y * self.m24, self.m41 - target.x * self.m44, self.m42 - target.y * self.m44,
); let inv = m.inverse()?; // we found the point, now check if it maps to the positive hemisphere if inv.m31 * self.m14 + inv.m32 * self.m24 + self.m44 > 0.0 {
Some(Point2D::new(inv.m31, inv.m32))
} else {
None
}
}
fn as_grid_aligned_rotation(&self) -> Option<(ScaleOffset, bool)> { let is_zero = |v: f32| v.abs() < NEARLY_ZERO; let is_one = |v: f32| (v - 1.0).abs() < NEARLY_ZERO; let is_unit = |v: f32| (v.abs() - 1.0).abs() < NEARLY_ZERO;
// Must be a flat 2D transform: no z coupling and no perspective. // Translation (m41, m42) is unconstrained; tz (m43) must be zero. if !(is_zero(self.m13) && is_zero(self.m14) && is_zero(self.m23) && is_zero(self.m24) &&
is_zero(self.m31) && is_zero(self.m32) && is_one(self.m33) && is_zero(self.m34) &&
is_zero(self.m43) && is_one(self.m44)) { return None;
}
// The remaining 2x2 must only rotate/flip by a right angle, never scale. // A 0/180-degree rotation or axis flip (entries on the diagonal) maps to // a `ScaleOffset` directly; a 90/270-degree rotation (entries off the // diagonal) maps to the same `ScaleOffset` applied after swapping x and y. if is_unit(self.m11) && is_unit(self.m22) && is_zero(self.m12) && is_zero(self.m21) {
Some((ScaleOffset::new(self.m11, self.m22, self.m41, self.m42), false))
} elseif is_unit(self.m12) && is_unit(self.m21) && is_zero(self.m11) && is_zero(self.m22) {
Some((ScaleOffset::new(self.m21, self.m12, self.m41, self.m42), true))
} else {
None
}
}
fn flatten_z_output(&mutself) { self.m13 = 0.0; self.m23 = 0.0; self.m33 = 1.0; self.m43 = 0.0; //Note: we used to zero out m3? as well, see "reftests/flatten-all-flat.yaml" test
}
#[inline(always)] pubfn pack_as_float(value: u32) -> f32 {
value as f32 + 0.5
}
#[inline] fn extract_inner_rect_impl<U>(
rect: &Box2D<f32, U>,
radii: &BorderRadius,
k: f32,
) -> Option<Box2D<f32, U>> { // `k` defines how much border is taken into account // We enforce the offsets to be rounded to pixel boundaries // by `ceil`-ing and `floor`-ing them
let xl = (k * radii.top_left.width.max(radii.bottom_left.width)).ceil(); let xr = (rect.width() - k * radii.top_right.width.max(radii.bottom_right.width)).floor(); let yt = (k * radii.top_left.height.max(radii.top_right.height)).ceil(); let yb =
(rect.height() - k * radii.bottom_left.height.max(radii.bottom_right.height)).floor();
/// Return an aligned rectangle that is inside the clip region and doesn't intersect /// any of the bounding rectangles of the rounded corners. pubfn extract_inner_rect_safe<U>(
rect: &Box2D<f32, U>,
radii: &BorderRadius,
) -> Option<Box2D<f32, U>> { // value of `k==1.0` is used for extraction of the corner rectangles // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
extract_inner_rect_impl(rect, radii, 1.0)
}
/// Return an aligned rectangle that is inside the clip region and doesn't intersect /// any of the bounding rectangles of the rounded corners, with a specific k factor /// to control how much of the rounded corner is included. pubfn extract_inner_rect_k<U>(
rect: &Box2D<f32, U>,
radii: &BorderRadius,
k: f32,
) -> Option<Box2D<f32, U>> {
extract_inner_rect_impl(rect, radii, k)
}
#[cfg(test)] use euclid::vec3;
#[cfg(test)] pubmod test { usesuper::*; use euclid::default::{Box2D, Point2D, Size2D, Transform3D}; use euclid::{Angle, approxeq::ApproxEq}; use std::f32::consts::PI; usecrate::clip::{is_left_of_line, polygon_contains_point}; usecrate::prim_store::PolygonKey; use api::FillRule;
#[test] fn inverse_project() { let m0 = Transform3D::identity(); let p0 = Point2D::new(1.0, 2.0); // an identical transform doesn't need any inverse projection
assert_eq!(m0.inverse_project(&p0), Some(p0)); let m1 = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(-PI / 3.0)); // rotation by 60 degrees would imply scaling of X component by a factor of 2
assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0)));
}
#[test] fn inverse_project_footprint() { let m = Transform3D::new( 0.477499992, 0.135000005, -1.0, 0.000624999986,
-0.642787635, 0.766044438, 0.0, 0.0, 0.766044438, 0.642787635, 0.0, 0.0, 1137.10986, 113.71286, 402.0, 0.748749971,
); let r = Box2D::from_size(Size2D::new(804.0, 804.0));
{ let points = &[
r.top_left(),
r.top_right(),
r.bottom_left(),
r.bottom_right(),
]; let mi = m.inverse().unwrap(); // In this section, we do the forward and backward transformation // to confirm that its bijective. // We also do the inverse projection path, and confirm it functions the same way.
info!("Points:"); for p in points { let pp = m.transform_point2d_homogeneous(*p); let p3 = pp.to_point3d().unwrap(); let pi = mi.transform_point3d_homogeneous(p3); let px = pi.to_point2d().unwrap(); let py = m.inverse_project(&pp.to_point2d().unwrap()).unwrap();
info!("\t{:?} -> {:?} -> {:?} -> ({:?} -> {:?}, {:?})", p, pp, p3, pi, px, py);
assert!(px.approx_eq_eps(p, &Point2D::new(0.001, 0.001)));
assert!(py.approx_eq_eps(p, &Point2D::new(0.001, 0.001)));
}
} // project let rp = project_rect(&m, &r, &Box2D::from_size(Size2D::new(1000.0, 1000.0))).unwrap();
info!("Projected {:?}", rp); // one of the points ends up in the negative hemisphere
assert_eq!(m.inverse_project(&rp.min), None); // inverse iflet Some(ri) = m.inverse_rect_footprint(&rp) { // inverse footprint should be larger, since it doesn't know the original Z
assert!(ri.contains_box(&r), "Inverse {:?}", ri);
}
}
fn validate_convert(xref: &LayoutTransform) { let so = ScaleOffset::from_transform(xref).unwrap(); let xf = so.to_transform();
assert!(xref.approx_eq(&xf));
}
#[test] fn negative_scale_map_unmap() { let xref = LayoutTransform::scale(1.0, -1.0, 1.0)
.pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0)); let so = ScaleOffset::from_transform(&xref).unwrap(); let local_rect = LayoutRect {
min: LayoutPoint::new(50.0, -100.0),
max: LayoutPoint::new(250.0, 300.0),
};
let mapped_rect = so.map_rect::<LayoutPixel, DevicePixel>(&local_rect); let xf_rect = project_rect(
&xref,
&local_rect,
&LayoutRect::max_rect(),
).unwrap();
fn validate_accumulate(x0: &LayoutTransform, x1: &LayoutTransform) { let x = x1.then(&x0);
let s0 = ScaleOffset::from_transform(x0).unwrap(); let s1 = ScaleOffset::from_transform(x1).unwrap();
let s = s0.pre_transform(&s1).to_transform();
assert!(x.approx_eq(&s), "{:?}\n{:?}", x, s);
}
#[test] fn scale_offset_accumulate() { let x0 = LayoutTransform::translation(130.0, 200.0, 0.0); let x1 = LayoutTransform::scale(7.0, 3.0, 1.0);
validate_accumulate(&x0, &x1);
}
#[test] fn scale_offset_invalid_scale() { let s0 = ScaleOffset::new(0.0, 1.0, 10.0, 20.0); let i0 = s0.inverse();
assert_eq!(i0, ScaleOffset::new(0.0, 0.0, 0.0, 0.0));
let s1 = ScaleOffset::new(1.0, 0.0, 10.0, 20.0); let i1 = s1.inverse();
assert_eq!(i1, ScaleOffset::new(0.0, 0.0, 0.0, 0.0));
}
#[test] fn polygon_clip_is_left_of_point() { // Define points of a line through (1, -3) and (-2, 6) to test against. // If the triplet consisting of these two points and the test point // form a counter-clockwise triangle, then the test point is on the // left. The easiest way to visualize this is with an "ascending" // line from low-Y to high-Y. let p0_x = 1.0; let p0_y = -3.0; let p1_x = -2.0; let p1_y = 6.0;
// Test some points to the left of the line.
assert!(is_left_of_line(-9.0, 0.0, p0_x, p0_y, p1_x, p1_y) > 0.0);
assert!(is_left_of_line(-1.0, 1.0, p0_x, p0_y, p1_x, p1_y) > 0.0);
assert!(is_left_of_line(1.0, -4.0, p0_x, p0_y, p1_x, p1_y) > 0.0);
// Test some points on the line.
assert!(is_left_of_line(-3.0, 9.0, p0_x, p0_y, p1_x, p1_y) == 0.0);
assert!(is_left_of_line(0.0, 0.0, p0_x, p0_y, p1_x, p1_y) == 0.0);
assert!(is_left_of_line(100.0, -300.0, p0_x, p0_y, p1_x, p1_y) == 0.0);
// Test some points to the right of the line.
assert!(is_left_of_line(0.0, 1.0, p0_x, p0_y, p1_x, p1_y) < 0.0);
assert!(is_left_of_line(-4.0, 13.0, p0_x, p0_y, p1_x, p1_y) < 0.0);
assert!(is_left_of_line(5.0, -12.0, p0_x, p0_y, p1_x, p1_y) < 0.0);
}
#[test] fn polygon_clip_contains_point() { // We define the points of a self-overlapping polygon, which we will // use to create polygons with different windings and fill rules. let p0 = LayoutPoint::new(4.0, 4.0); let p1 = LayoutPoint::new(6.0, 4.0); let p2 = LayoutPoint::new(4.0, 7.0); let p3 = LayoutPoint::new(2.0, 1.0); let p4 = LayoutPoint::new(8.0, 1.0); let p5 = LayoutPoint::new(6.0, 7.0);
// We define a rect that provides a bounding clip area of // the polygon. let rect = LayoutRect::from_size(LayoutSize::new(10.0, 10.0));
// And we'll test three points of interest. let p_inside_once = LayoutPoint::new(5.0, 3.0); let p_inside_twice = LayoutPoint::new(5.0, 5.0); let p_outside = LayoutPoint::new(9.0, 9.0);
// We should get the same results for both clockwise and // counter-clockwise polygons. // For nonzero polygons, the inside twice point is considered inside. for poly_nonzero in vec![poly_clockwise_nonzero, poly_counter_clockwise_nonzero].iter() {
assert_eq!(polygon_contains_point(&p_inside_once, &rect, &poly_nonzero), true);
assert_eq!(polygon_contains_point(&p_inside_twice, &rect, &poly_nonzero), true);
assert_eq!(polygon_contains_point(&p_outside, &rect, &poly_nonzero), false);
} // For evenodd polygons, the inside twice point is considered outside. for poly_evenodd in vec![poly_clockwise_evenodd, poly_counter_clockwise_evenodd].iter() {
assert_eq!(polygon_contains_point(&p_inside_once, &rect, &poly_evenodd), true);
assert_eq!(polygon_contains_point(&p_inside_twice, &rect, &poly_evenodd), false);
assert_eq!(polygon_contains_point(&p_outside, &rect, &poly_evenodd), false);
}
}
// Ensures that mapping or unmapping an input rect with negative size returns a rect // with size 0, and the origin transformed as expected. #[test] fn map_unmap_negative_size() { let scale_offset = ScaleOffset::new(2.0, 2.0, 1.0, 1.0); let rect = Box2D::new(Point2D::new(5.0, 5.0), Point2D::new(0.0, 0.0)); let mapped_rect: Box2D<f32> = scale_offset.map_rect(&rect);
assert_eq!(mapped_rect, Box2D::new(Point2D::new(11.0, 11.0), Point2D::new(11.0, 11.0)));
// Ensures that mapping or unmapping two adjoining input rects returns two rects that // are still adjoining. #[test] fn map_unmap_adjoining_rects() { let so = ScaleOffset::new(0.3, 0.3, 0.0, 0.0); let p1 = Point2D::new(15.0, 15.0); let p2 = Point2D::new(45.0, 45.0); let p3 = Point2D::new(75.0, 75.0);
let rect_1 = Box2D::new(p1, p2); let rect_2 = Box2D::new(p2, p3);
let mapped_rect_1: Box2D<f32> = so.map_rect(&rect_1); let mapped_rect_2: Box2D<f32> = so.map_rect(&rect_2);
assert_eq!(mapped_rect_1.max, mapped_rect_2.min);
let unmapped_rect_1: Box2D<f32> = so.unmap_rect(&rect_1); let unmapped_rect_2: Box2D<f32> = so.unmap_rect(&rect_2);
assert_eq!(unmapped_rect_1.max, unmapped_rect_2.min);
}
}
impl<U> MaxRect for Rect<f32, U> { fn max_rect() -> Self { // Having an unlimited bounding box is fine up until we try // to cast it to `i32`, where we get `-2147483648` for any // values larger than or equal to 2^31. // // Note: clamping to i32::MIN and i32::MAX is not a solution, // with explanation left as an exercise for the reader. const MAX_COORD: f32 = 1.0e9;
impl<U> MaxRect for Box2D<f32, U> { fn max_rect() -> Self { // Having an unlimited bounding box is fine up until we try // to cast it to `i32`, where we get `-2147483648` for any // values larger than or equal to 2^31. // // Note: clamping to i32::MIN and i32::MAX is not a solution, // with explanation left as an exercise for the reader. const MAX_COORD: f32 = 1.0e9;
/// An enum that tries to avoid expensive transformation matrix calculations /// when possible when dealing with non-perspective axis-aligned transformations. #[derive(Debug, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubenum FastTransform<Src, Dst> { /// A simple offset, which can be used without doing any matrix math.
Offset(Vector2D<f32, Src>),
/// A 2D transformation with an inverse.
Transform {
transform: Transform3D<f32, Src, Dst>,
inverse: Option<Transform3D<f32, Dst, Src>>,
is_2d: bool,
},
}
#[inline(always)] pubfn project_point2d(&self, point: Point2D<f32, Src>) -> Option<Point2D<f32, Dst>> { match* self {
FastTransform::Offset(..) => self.transform_point2d(point),
FastTransform::Transform{ref transform, ..} => { // Find a value for z that will transform to 0.
// The transformed value of z is computed as: // z' = point.x * self.m13 + point.y * self.m23 + z * self.m33 + self.m43
// Solving for z when z' = 0 gives us: let z = -(point.x * transform.m13 + point.y * transform.m23 + transform.m43) / transform.m33;
// Note: we only do the full frustum collision when the polygon approaches the camera plane. // Otherwise, it will be clamped to the screen bounds anyway. if homogens.iter().any(|h| h.w <= 0.0 || h.w.is_nan()) { letmut clipper = Clipper::new(); let polygon = Polygon::from_rect(rect.to_rect().cast().cast_unit(), 1);
let planes = match Clipper::<usize>::frustum_planes(
&transform.cast_unit().cast(),
Some(bounds.to_rect().cast_unit().to_f64()),
) {
Ok(planes) => planes,
Err(..) => return None,
};
for plane in planes {
clipper.add(plane);
}
let results = clipper.clip(polygon); if results.is_empty() { return None
}
Some(Box2D::from_points(results
.into_iter() // filter out parts behind the view plane
.flat_map(|poly| &poly.points)
.map(|p| { letmut homo = transform.transform_point2d_homogeneous(p.to_2d().to_f32().cast_unit());
homo.w = homo.w.max(0.00000001); // avoid infinite values
homo.to_point2d().unwrap()
})
))
} else { // we just checked for all the points to be in positive hemisphere, so `unwrap` is valid
Some(Box2D::from_points(&[
homogens[0].to_point2d().unwrap(),
homogens[1].to_point2d().unwrap(),
homogens[2].to_point2d().unwrap(),
homogens[3].to_point2d().unwrap(),
]))
}
}
/// Run the first callback over all elements in the array. If the callback returns true, /// the element is removed from the array and moved to a second callback. /// /// This is a simple implementation waiting for Vec::drain_filter to be stable. /// When that happens, code like: /// /// let filter = |op| { /// match *op { /// Enum::Foo | Enum::Bar => true, /// Enum::Baz => false, /// } /// }; /// drain_filter( /// &mut ops, /// filter, /// |op| { /// match op { /// Enum::Foo => { foo(); } /// Enum::Bar => { bar(); } /// Enum::Baz => { unreachable!(); } /// } /// }, /// ); /// /// Can be rewritten as: /// /// let filter = |op| { /// match *op { /// Enum::Foo | Enum::Bar => true, /// Enum::Baz => false, /// } /// }; /// for op in ops.drain_filter(filter) { /// match op { /// Enum::Foo => { foo(); } /// Enum::Bar => { bar(); } /// Enum::Baz => { unreachable!(); } /// } /// } /// /// See https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter pubfn drain_filter<T, Filter, Action>(
vec: &mut Vec<T>, mut filter: Filter, mut action: Action,
) where
Filter: FnMut(&mut T) -> bool,
Action: FnMut(T)
{ letmut i = 0; while i != vec.len() { if filter(&mut vec[i]) {
action(vec.remove(i));
} else {
i += 1;
}
}
}
impl Recycler { /// Maximum extra capacity that a recycled vector is allowed to have. If the actual capacity /// is larger, we re-allocate the vector storage with lower capacity. const MAX_EXTRA_CAPACITY_PERCENT: usize = 200; /// Minimum extra capacity to keep when re-allocating the vector storage. const MIN_EXTRA_CAPACITY_PERCENT: usize = 20; /// Minimum sensible vector length to consider for re-allocation. const MIN_VECTOR_LENGTH: usize = 16;
/// Clear a vector for re-use, while retaining the backing memory buffer. May shrink the buffer /// if it's currently much larger than was actually used. pubfn recycle_vec<T>(&mutself, vec: &mut Vec<T>) { let extra_capacity = (vec.capacity() - vec.len()) * 100 / vec.len().max(Self::MIN_VECTOR_LENGTH);
if extra_capacity > Self::MAX_EXTRA_CAPACITY_PERCENT { // Reduce capacity of the buffer if it is a lot larger than it needs to be. This prevents // a frame with exceptionally large allocations to cause subsequent frames to retain // more memory than they need. //TODO: use `shrink_to` when it's stable
*vec = Vec::with_capacity(vec.len() + vec.len() * Self::MIN_EXTRA_CAPACITY_PERCENT / 100); self.num_allocations += 1;
} else {
vec.clear();
}
}
}
/// Record the size of a data structure to preallocate a similar size /// at the next frame and avoid growing it too many time. #[derive(Copy, Clone, Debug)] pubstruct Preallocator {
size: usize,
}
/// Record the size of a vector to preallocate it the next frame. pubfn record_vec<T>(&mutself, vec: &[T]) { let len = vec.len(); if len > self.size { self.size = len;
} else { self.size = (self.size + len) / 2;
}
}
/// The size that we'll preallocate the vector with. pubfn preallocation_size(&self) -> usize { // Round up to multiple of 16 to avoid small tiny // variations causing reallocations.
(self.size + 15) & !15
}
/// Preallocate vector storage. /// /// The preallocated amount depends on the length recorded in the last /// record_vec call. pubfn preallocate_vec<T>(&self, vec: &mutVec<T>) { let len = vec.len(); let cap = self.preallocation_size(); if len < cap {
vec.reserve(cap - len);
}
}
/// Preallocate vector storage. /// /// The preallocated amount depends on the length recorded in the last /// record_vec call. pubfn preallocate_framevec<T>(&self, vec: &mut FrameVec<T>) { let len = vec.len(); let cap = self.preallocation_size(); if len < cap {
vec.reserve(cap - len);
}
}
}
/// Computes the scale factors of this matrix; that is, /// the amounts each basis vector is scaled by. /// /// This code comes from gecko gfx/2d/Matrix.h with the following /// modifications: /// /// * Removed `xMajor` parameter. /// * All arithmetics is done with double precision. pubfn scale_factors<Src, Dst>(
mat: &Transform3D<f32, Src, Dst>
) -> (f32, f32) { let m11 = mat.m11 as f64; let m12 = mat.m12 as f64; // Determinant is just of the 2D component. let det = m11 * mat.m22 as f64 - m12 * mat.m21 as f64; if det == 0.0 { return (0.0, 0.0);
}
// ignore mirroring let det = det.abs();
let major = (m11 * m11 + m12 * m12).sqrt(); let minor = if major != 0.0 { det / major } else { 0.0 };
/// Clamp scaling factor to a power of two. /// /// This code comes from gecko gfx/thebes/gfxUtils.cpp with the following /// modification: /// /// * logs are taken in base 2 instead of base e. pubfn clamp_to_scale_factor(val: f32, round_down: bool) -> f32 { // Arbitary scale factor limitation. We can increase this // for better scaling performance at the cost of worse // quality. const SCALE_RESOLUTION: f32 = 2.0;
// Negative scaling is just a flip and irrelevant to // our resolution calculation. let val = val.abs();
let (val, inverse) = if val < 1.0 {
(1.0 / val, true)
} else {
(val, false)
};
let power = val.log2() / SCALE_RESOLUTION.log2();
// If power is within 1e-5 of an integer, round to nearest to // prevent floating point errors, otherwise round up to the // next integer value. let power = if (power - power.round()).abs() < 1e-5 {
power.round()
} elseif inverse != round_down { // Use floor when we are either inverted or rounding down, but // not both.
power.floor()
} else { // Otherwise, ceil when we are not inverted and not rounding // down, or we are inverted and rounding down.
power.ceil()
};
let scale = SCALE_RESOLUTION.powf(power);
if inverse { 1.0 / scale
} else {
scale
}
}
/// Rounds a value up to the nearest multiple of mul pubfn round_up_to_multiple(val: usize, mul: NonZeroUsize) -> usize { match val % mul.get() { 0 => val,
rem => val - rem + mul.get(),
}
}
/// This is inspired by the `weak-table` crate. /// It holds a Vec of weak pointers that are garbage collected as the Vec pubstruct WeakTable {
inner: Vec<std::sync::Weak<Vec<u8>>>
}
// We want to make sure that we change capacity() // even if remove_expired() removes some entries // so that we don't repeatedly hit remove_expired() ifself.inner.len() * 3 < self.inner.capacity() { // We use a different multiple for shrinking then // expanding so that we we don't accidentally // oscilate. self.inner.shrink_to_fit();
} else { // Otherwise double our size self.inner.reserve(self.inner.len())
}
} self.inner.push(x);
}
#[test] fn weak_table() { letmut tbl = WeakTable::new(); letmut things = Vec::new(); let target_count = 50; for _ in0..target_count {
things.push(Arc::new(vec![4]));
} for i in &things {
tbl.insert(Arc::downgrade(i))
}
assert_eq!(tbl.inner.len(), target_count);
drop(things);
assert_eq!(tbl.iter().count(), 0);
// make sure that we shrink the table if it gets too big // by adding a bunch of dead items for _ in0..target_count*2 {
tbl.insert(Arc::downgrade(&Arc::new(vec![5])))
}
assert!(tbl.inner.capacity() <= 4);
}
#[test] fn scale_offset_pre_post() { let a = ScaleOffset::new(1.0, 2.0, 3.0, 4.0); let b = ScaleOffset::new(5.0, 6.0, 7.0, 8.0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.