From 30f1788192fb86b593c2dbd6525137db58e7e859 Mon Sep 17 00:00:00 2001 From: Atlas Dostal Date: Fri, 30 Sep 2022 21:46:08 -0700 Subject: [PATCH] Add Vec::sign_bits (#345) * Fix negation and signum to consistently handle negative zero across platforms * Add sign_bits and tests * rustfmt * Try to fix coresimd * rename sign_bits to is_negative_bitmask * rustfmt --- codegen/templates/vec.rs.tera | 43 +++++++++++++++++++++++++++++++++++ src/f32/coresimd/vec3a.rs | 9 ++++++++ src/f32/coresimd/vec4.rs | 9 ++++++++ src/f32/scalar/vec3a.rs | 11 +++++++++ src/f32/scalar/vec4.rs | 12 ++++++++++ src/f32/sse2/vec3a.rs | 9 ++++++++ src/f32/sse2/vec4.rs | 9 ++++++++ src/f32/vec2.rs | 9 ++++++++ src/f32/vec3.rs | 11 +++++++++ src/f32/wasm32/vec3a.rs | 9 ++++++++ src/f32/wasm32/vec4.rs | 9 ++++++++ src/f64/dvec2.rs | 9 ++++++++ src/f64/dvec3.rs | 11 +++++++++ src/f64/dvec4.rs | 12 ++++++++++ src/i32/ivec2.rs | 9 ++++++++ src/i32/ivec3.rs | 11 +++++++++ src/i32/ivec4.rs | 12 ++++++++++ tests/vec2.rs | 11 +++++++++ tests/vec3.rs | 12 ++++++++++ tests/vec4.rs | 27 ++++++++++++++++++++++ 20 files changed, 254 insertions(+) diff --git a/codegen/templates/vec.rs.tera b/codegen/templates/vec.rs.tera index 18506be3..425262eb 100644 --- a/codegen/templates/vec.rs.tera +++ b/codegen/templates/vec.rs.tera @@ -825,6 +825,49 @@ impl {{ self_t }} { } {% endif %} } + + /// Returns a bitmask with the lowest {{ dim }} bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + {% if is_scalar and is_float %} + {% for c in components %} + {% if loop.first %} + (self.{{ c }}.is_sign_negative() as u32) | + {% else %} + (self.{{ c }}.is_sign_negative() as u32) << {{ loop.index0 }} {% if not loop.last %} | {% endif %} + {% endif %} + {% endfor %} + {% elif is_scalar %} + {% for c in components %} + {% if loop.first %} + (self.{{ c }}.is_negative() as u32) | + {% else %} + (self.{{ c }}.is_negative() as u32) << {{ loop.index0 }} {% if not loop.last %} | {% endif %} + {% endif %} + {% endfor %} + {% elif is_sse2 %} + {% if dim == 3 %} + unsafe { (_mm_movemask_ps(self.0) as u32) & 0x7 } + {% elif dim == 4 %} + unsafe { _mm_movemask_ps(self.0) as u32 } + {% endif %} + {% elif is_wasm32 %} + {% if dim == 3 %} + (u32x4_bitmask(self.0) & 0x7) as u32 + {% elif dim == 4 %} + u32x4_bitmask(self.0) as u32 + {% endif %} + {% elif is_coresimd %} + {% if dim == 3 %} + (self.0.is_sign_negative().to_bitmask() & 0x7) as u32 + {% elif dim == 4 %} + self.0.is_sign_negative().to_bitmask() as u32 + {% endif %} + {% endif %} + } {% endif %} {% if is_float %} diff --git a/src/f32/coresimd/vec3a.rs b/src/f32/coresimd/vec3a.rs index 966217e3..71f5c646 100644 --- a/src/f32/coresimd/vec3a.rs +++ b/src/f32/coresimd/vec3a.rs @@ -292,6 +292,15 @@ impl Vec3A { Self(self.0.signum()) } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.0.is_sign_negative().to_bitmask() & 0x7) as u32 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/coresimd/vec4.rs b/src/f32/coresimd/vec4.rs index 38b2d4fe..fa92f936 100644 --- a/src/f32/coresimd/vec4.rs +++ b/src/f32/coresimd/vec4.rs @@ -268,6 +268,15 @@ impl Vec4 { Self(self.0.signum()) } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + self.0.is_sign_negative().to_bitmask() as u32 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/scalar/vec3a.rs b/src/f32/scalar/vec3a.rs index 00c1e8fe..26b82b66 100644 --- a/src/f32/scalar/vec3a.rs +++ b/src/f32/scalar/vec3a.rs @@ -312,6 +312,17 @@ impl Vec3A { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) + | (self.y.is_sign_negative() as u32) << 1 + | (self.z.is_sign_negative() as u32) << 2 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/scalar/vec4.rs b/src/f32/scalar/vec4.rs index db0a60a9..6abcccd7 100644 --- a/src/f32/scalar/vec4.rs +++ b/src/f32/scalar/vec4.rs @@ -339,6 +339,18 @@ impl Vec4 { } } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) + | (self.y.is_sign_negative() as u32) << 1 + | (self.z.is_sign_negative() as u32) << 2 + | (self.w.is_sign_negative() as u32) << 3 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/sse2/vec3a.rs b/src/f32/sse2/vec3a.rs index ace0933f..56b8c8d6 100644 --- a/src/f32/sse2/vec3a.rs +++ b/src/f32/sse2/vec3a.rs @@ -319,6 +319,15 @@ impl Vec3A { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + unsafe { (_mm_movemask_ps(self.0) as u32) & 0x7 } + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/sse2/vec4.rs b/src/f32/sse2/vec4.rs index 906f78a6..f72e0277 100644 --- a/src/f32/sse2/vec4.rs +++ b/src/f32/sse2/vec4.rs @@ -294,6 +294,15 @@ impl Vec4 { } } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + unsafe { _mm_movemask_ps(self.0) as u32 } + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs index ab9ec464..13181e19 100644 --- a/src/f32/vec2.rs +++ b/src/f32/vec2.rs @@ -264,6 +264,15 @@ impl Vec2 { } } + /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) | (self.y.is_sign_negative() as u32) << 1 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs index 3f745c00..56062638 100644 --- a/src/f32/vec3.rs +++ b/src/f32/vec3.rs @@ -306,6 +306,17 @@ impl Vec3 { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) + | (self.y.is_sign_negative() as u32) << 1 + | (self.z.is_sign_negative() as u32) << 2 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/wasm32/vec3a.rs b/src/f32/wasm32/vec3a.rs index 4ee5d7ec..08a6269f 100644 --- a/src/f32/wasm32/vec3a.rs +++ b/src/f32/wasm32/vec3a.rs @@ -300,6 +300,15 @@ impl Vec3A { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (u32x4_bitmask(self.0) & 0x7) as u32 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f32/wasm32/vec4.rs b/src/f32/wasm32/vec4.rs index 1fc475bf..de16f666 100644 --- a/src/f32/wasm32/vec4.rs +++ b/src/f32/wasm32/vec4.rs @@ -282,6 +282,15 @@ impl Vec4 { } } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + u32x4_bitmask(self.0) as u32 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f64/dvec2.rs b/src/f64/dvec2.rs index 3fdfc448..42039e98 100644 --- a/src/f64/dvec2.rs +++ b/src/f64/dvec2.rs @@ -264,6 +264,15 @@ impl DVec2 { } } + /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) | (self.y.is_sign_negative() as u32) << 1 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f64/dvec3.rs b/src/f64/dvec3.rs index d7bc1a2e..0260d42f 100644 --- a/src/f64/dvec3.rs +++ b/src/f64/dvec3.rs @@ -306,6 +306,17 @@ impl DVec3 { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) + | (self.y.is_sign_negative() as u32) << 1 + | (self.z.is_sign_negative() as u32) << 2 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/f64/dvec4.rs b/src/f64/dvec4.rs index 21a60cef..50d49b45 100644 --- a/src/f64/dvec4.rs +++ b/src/f64/dvec4.rs @@ -331,6 +331,18 @@ impl DVec4 { } } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_sign_negative() as u32) + | (self.y.is_sign_negative() as u32) << 1 + | (self.z.is_sign_negative() as u32) << 2 + | (self.w.is_sign_negative() as u32) << 3 + } + /// Returns `true` if, and only if, all elements are finite. If any element is either /// `NaN`, positive or negative infinity, this will return `false`. #[inline] diff --git a/src/i32/ivec2.rs b/src/i32/ivec2.rs index fc218965..599f4f59 100644 --- a/src/i32/ivec2.rs +++ b/src/i32/ivec2.rs @@ -258,6 +258,15 @@ impl IVec2 { } } + /// Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_negative() as u32) | (self.y.is_negative() as u32) << 1 + } + /// Returns a vector that is equal to `self` rotated by 90 degrees. #[inline] pub fn perp(self) -> Self { diff --git a/src/i32/ivec3.rs b/src/i32/ivec3.rs index 1d141083..7a87a304 100644 --- a/src/i32/ivec3.rs +++ b/src/i32/ivec3.rs @@ -300,6 +300,17 @@ impl IVec3 { } } + /// Returns a bitmask with the lowest 3 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_negative() as u32) + | (self.y.is_negative() as u32) << 1 + | (self.z.is_negative() as u32) << 2 + } + /// Casts all elements of `self` to `f32`. #[inline] pub fn as_vec3(&self) -> crate::Vec3 { diff --git a/src/i32/ivec4.rs b/src/i32/ivec4.rs index 449f1b9b..157cf05c 100644 --- a/src/i32/ivec4.rs +++ b/src/i32/ivec4.rs @@ -325,6 +325,18 @@ impl IVec4 { } } + /// Returns a bitmask with the lowest 4 bits set to the sign bits from the elements of `self`. + /// + /// A negative element results in a `1` bit and a positive element in a `0` bit. Element `x` goes + /// into the first lowest bit, element `y` into the second, etc. + #[inline] + pub fn is_negative_bitmask(self) -> u32 { + (self.x.is_negative() as u32) + | (self.y.is_negative() as u32) << 1 + | (self.z.is_negative() as u32) << 2 + | (self.w.is_negative() as u32) << 3 + } + /// Casts all elements of `self` to `f32`. #[inline] pub fn as_vec4(&self) -> crate::Vec4 { diff --git a/tests/vec2.rs b/tests/vec2.rs index 8a4163cb..3ca9acc5 100644 --- a/tests/vec2.rs +++ b/tests/vec2.rs @@ -606,6 +606,17 @@ macro_rules! impl_vec2_float_tests { assert!($vec2::splat(NAN).signum().is_nan_mask().all()); }); + glam_test!(test_is_negative_bitmask, { + assert_eq!($vec2::ZERO.is_negative_bitmask(), 0b00); + assert_eq!((-$vec2::ZERO).is_negative_bitmask(), 0b11); + assert_eq!($vec2::ONE.is_negative_bitmask(), 0b00); + assert_eq!((-$vec2::ONE).is_negative_bitmask(), 0b11); + assert_eq!($vec2::new(-0.1, 0.2).is_negative_bitmask(), 0b01); + assert_eq!($vec2::new(0.8, 0.3).is_negative_bitmask(), 0b00); + assert_eq!($vec2::new(0.3, -0.4).is_negative_bitmask(), 0b10); + assert_eq!($vec2::new(-0.2, -0.6).is_negative_bitmask(), 0b11); + }); + glam_test!(test_abs, { assert_eq!($vec2::ZERO.abs(), $vec2::ZERO); assert_eq!($vec2::ONE.abs(), $vec2::ONE); diff --git a/tests/vec3.rs b/tests/vec3.rs index 095264b1..92af042e 100644 --- a/tests/vec3.rs +++ b/tests/vec3.rs @@ -668,6 +668,18 @@ macro_rules! impl_vec3_float_tests { assert!($vec3::splat(NAN).signum().is_nan_mask().all()); }); + glam_test!(test_is_negative_bitmask, { + assert_eq!($vec3::ZERO.is_negative_bitmask(), 0b000); + assert_eq!((-$vec3::ZERO).is_negative_bitmask(), 0b111); + assert_eq!($vec3::ONE.is_negative_bitmask(), 0b000); + assert_eq!((-$vec3::ONE).is_negative_bitmask(), 0b111); + assert_eq!($vec3::new(-0.1, 0.2, 0.3).is_negative_bitmask(), 0b001); + assert_eq!($vec3::new(0.8, 0.3, 0.1).is_negative_bitmask(), 0b000); + assert_eq!($vec3::new(0.1, 0.5, -0.3).is_negative_bitmask(), 0b100); + assert_eq!($vec3::new(0.3, -0.4, 0.1).is_negative_bitmask(), 0b010); + assert_eq!($vec3::new(-0.2, 0.6, -0.5).is_negative_bitmask(), 0b101); + }); + glam_test!(test_abs, { assert_eq!($vec3::ZERO.abs(), $vec3::ZERO); assert_eq!($vec3::ONE.abs(), $vec3::ONE); diff --git a/tests/vec4.rs b/tests/vec4.rs index 2646727b..9d063712 100644 --- a/tests/vec4.rs +++ b/tests/vec4.rs @@ -750,6 +750,33 @@ macro_rules! impl_vec4_float_tests { assert!($vec4::splat(NAN).signum().is_nan_mask().all()); }); + glam_test!(test_is_negative_bitmask, { + assert_eq!($vec4::ZERO.is_negative_bitmask(), 0b0000); + assert_eq!((-$vec4::ZERO).is_negative_bitmask(), 0b1111); + assert_eq!($vec4::ONE.is_negative_bitmask(), 0b0000); + assert_eq!((-$vec4::ONE).is_negative_bitmask(), 0b1111); + assert_eq!( + $vec4::new(-0.1, 0.2, 0.3, -0.4).is_negative_bitmask(), + 0b1001 + ); + assert_eq!( + $vec4::new(0.8, 0.3, 0.1, -0.0).is_negative_bitmask(), + 0b1000 + ); + assert_eq!( + $vec4::new(0.1, 0.5, -0.3, 0.7).is_negative_bitmask(), + 0b0100 + ); + assert_eq!( + $vec4::new(0.3, -0.4, 0.1, 0.6).is_negative_bitmask(), + 0b0010 + ); + assert_eq!( + $vec4::new(0.2, -0.6, 0.5, -0.3).is_negative_bitmask(), + 0b1010 + ); + }); + glam_test!(test_abs, { assert_eq!($vec4::ZERO.abs(), $vec4::ZERO); assert_eq!($vec4::ONE.abs(), $vec4::ONE);