| // Copyright 2022 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| use std::ops::Mul; |
| |
| use bytemuck::{Pod, Zeroable}; |
| use peniko::kurbo; |
| |
| /// Affine transformation matrix. |
| #[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)] |
| #[repr(C)] |
| pub struct Transform { |
| /// 2x2 matrix. |
| pub matrix: [f32; 4], |
| /// Translation. |
| pub translation: [f32; 2], |
| } |
| |
| impl Transform { |
| /// Identity transform. |
| pub const IDENTITY: Self = Self { |
| matrix: [1.0, 0.0, 0.0, 1.0], |
| translation: [0.0; 2], |
| }; |
| |
| /// Creates a transform from a kurbo affine matrix. |
| pub fn from_kurbo(transform: &kurbo::Affine) -> Self { |
| let c = transform.as_coeffs().map(|x| x as f32); |
| Self { |
| matrix: [c[0], c[1], c[2], c[3]], |
| translation: [c[4], c[5]], |
| } |
| } |
| |
| /// Converts the transform to a kurbo affine matrix. |
| pub fn to_kurbo(&self) -> kurbo::Affine { |
| kurbo::Affine::new( |
| [ |
| self.matrix[0], |
| self.matrix[1], |
| self.matrix[2], |
| self.matrix[3], |
| self.translation[0], |
| self.translation[1], |
| ] |
| .map(|x| x as f64), |
| ) |
| } |
| } |
| |
| impl Mul for Transform { |
| type Output = Self; |
| |
| #[inline] |
| fn mul(self, other: Self) -> Self { |
| Self { |
| matrix: [ |
| self.matrix[0] * other.matrix[0] + self.matrix[2] * other.matrix[1], |
| self.matrix[1] * other.matrix[0] + self.matrix[3] * other.matrix[1], |
| self.matrix[0] * other.matrix[2] + self.matrix[2] * other.matrix[3], |
| self.matrix[1] * other.matrix[2] + self.matrix[3] * other.matrix[3], |
| ], |
| translation: [ |
| self.matrix[0] * other.translation[0] |
| + self.matrix[2] * other.translation[1] |
| + self.translation[0], |
| self.matrix[1] * other.translation[0] |
| + self.matrix[3] * other.translation[1] |
| + self.translation[1], |
| ], |
| } |
| } |
| } |
| |
| pub fn point_to_f32(point: kurbo::Point) -> [f32; 2] { |
| [point.x as f32, point.y as f32] |
| } |
| |
| /// Converts an `f32` to IEEE-754 binary16 format represented as the bits of a `u16`. |
| /// |
| /// This implementation was adapted from Fabian Giesen's `float_to_half_fast3`() |
| /// function which can be found at <https://gist.github.com/rygorous/2156668#file-gistfile1-cpp-L285> |
| /// |
| /// TODO: We should consider adopting <https://crates.io/crates/half> as a dependency since it nicely |
| /// wraps native ARM and x86 instructions for floating-point conversion. |
| pub(crate) fn f32_to_f16(val: f32) -> u16 { |
| const INF_32: u32 = 255 << 23; |
| const INF_16: u32 = 31 << 23; |
| const MAGIC: u32 = 15 << 23; |
| const SIGN_MASK: u32 = 0x8000_0000_u32; |
| const ROUND_MASK: u32 = !0xFFF_u32; |
| |
| let u = val.to_bits(); |
| let sign = u & SIGN_MASK; |
| let u = u ^ sign; |
| |
| // NOTE all the integer compares in this function can be safely |
| // compiled into signed compares since all operands are below |
| // 0x80000000. Important if you want fast straight SSE2 code |
| // (since there's no unsigned PCMPGTD). |
| |
| // Inf or NaN (all exponent bits set) |
| let output: u16 = if u >= INF_32 { |
| // NaN -> qNaN and Inf->Inf |
| if u > INF_32 { 0x7E00 } else { 0x7C00 } |
| } else { |
| // (De)normalized number or zero |
| let mut u = u & ROUND_MASK; |
| u = (f32::from_bits(u) * f32::from_bits(MAGIC)).to_bits(); |
| u = u.overflowing_sub(ROUND_MASK).0; |
| |
| // Clamp to signed infinity if exponent overflowed |
| if u > INF_16 { |
| u = INF_16; |
| } |
| (u >> 13) as u16 // Take the bits! |
| }; |
| output | (sign >> 16) as u16 |
| } |
| |
| /// Converts a 16-bit precision IEEE-754 binary16 float to a `f32`. |
| /// |
| /// This implementation was adapted from Fabian Giesen's `half_to_float()` |
| /// function which can be found at <https://gist.github.com/rygorous/2156668#file-gistfile1-cpp-L574> |
| pub fn f16_to_f32(bits: u16) -> f32 { |
| let bits = bits as u32; |
| const MAGIC: u32 = 113 << 23; |
| const SHIFTED_EXP: u32 = 0x7c00 << 13; // exponent mask after shift |
| |
| let mut o = (bits & 0x7fff) << 13; // exponent/mantissa bits |
| let exp = SHIFTED_EXP & o; // just the exponent |
| o += (127 - 15) << 23; // exponent adjust |
| |
| // handle exponent special cases |
| if exp == SHIFTED_EXP { |
| // Inf/NaN? |
| o += (128 - 16) << 23; // extra exp adjust |
| } else if exp == 0 { |
| // Zero/Denormal? |
| o += 1 << 23; // extra exp adjust |
| o = (f32::from_bits(o) - f32::from_bits(MAGIC)).to_bits(); // normalize |
| } |
| |
| f32::from_bits(o | ((bits & 0x8000) << 16)) // sign bit |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{f16_to_f32, f32_to_f16}; |
| |
| #[test] |
| fn test_f32_to_f16_simple() { |
| let input: f32 = std::f32::consts::PI; |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0x4248_u16, output); // 3.141 |
| } |
| |
| #[test] |
| fn test_f32_to_f16_nan_overflow() { |
| // A signaling NaN with unset high bits but a low bit that could get accidentally masked |
| // should get converted to a quiet NaN and not infinity. |
| let input: f32 = f32::from_bits(0x7F800001_u32); |
| assert!(input.is_nan()); |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0x7E00, output); |
| } |
| |
| #[test] |
| fn test_f32_to_f16_inf() { |
| let input: f32 = f32::from_bits(0x7F800000_u32); |
| assert!(input.is_infinite()); |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0x7C00, output); |
| } |
| |
| #[test] |
| fn test_f32_to_f16_exponent_rebias() { |
| let input: f32 = 0.00003051758; |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0x0200, output); // 0.00003052 |
| } |
| |
| #[test] |
| fn test_f32_to_f16_exponent_overflow() { |
| let input: f32 = 1.701412e38; |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0x7C00, output); // +inf |
| } |
| |
| #[test] |
| fn test_f32_to_f16_exponent_overflow_neg_inf() { |
| let input: f32 = -1.701412e38; |
| let output: u16 = f32_to_f16(input); |
| assert_eq!(0xFC00, output); // -inf |
| } |
| |
| #[test] |
| fn test_f16_to_f32_simple() { |
| let input: u16 = 0x4248_u16; |
| let output: f32 = f16_to_f32(input); |
| assert_eq!(3.140625, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_inf() { |
| let input: u16 = 0x7C00; |
| let output = f16_to_f32(input); |
| assert!(output.is_infinite()); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_neg_inf() { |
| let input: u16 = 0xFC00; |
| let output = f16_to_f32(input); |
| assert!(output.is_infinite()); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_inf_roundtrip() { |
| let input: u16 = 0x7C00; |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_neg_inf_roundtrip() { |
| let input: u16 = 0xFC00; |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_nan() { |
| let input: u16 = 0x7C01; |
| let output = f16_to_f32(input); |
| assert!(output.is_nan()); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_nan_roundtrip() { |
| let input: u16 = 0x7C01; |
| // Roundtrip 3 times and land on a f32 to check that NaN'ness is preserved. |
| let output = f16_to_f32(f32_to_f16(f16_to_f32(input))); |
| assert!(output.is_nan()); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_large_pos_roundtrip() { |
| let input: u16 = 0x7BFF; // 65504.0 |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_large_neg_roundtrip() { |
| let input: u16 = 0xFBFF; // -65504.0 |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_small_pos_roundtrip() { |
| let input: u16 = 0x0001; // 5.97e-8 |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_small_neg_roundtrip() { |
| let input: u16 = 0x8001; // -5.97e-8 |
| let output = f32_to_f16(f16_to_f32(input)); |
| assert_eq!(input, output); |
| } |
| |
| #[test] |
| fn test_f16_to_f32_roundtrip() { |
| const EPS: f32 = 0.001; |
| let input: f32 = std::f32::consts::PI; |
| assert!((input - f16_to_f32(f32_to_f16(input))).abs() < EPS); |
| } |
| } |