blob: b95d998aae7d9471d7f2cba3be3790428b309f20 [file] [log] [blame]
// 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);
}
}