use glam::Vec3;
use wide::f32x8;
pub const SIMD_WIDTH: usize = 8;
pub const SIMD_ALIGNMENT: usize = 32;
#[derive(Debug, Clone)]
pub struct SoAPoints {
pub x: AlignedVec,
pub y: AlignedVec,
pub z: AlignedVec,
len: usize,
}
impl SoAPoints {
pub const fn new() -> Self {
Self {
x: AlignedVec::new(),
y: AlignedVec::new(),
z: AlignedVec::new(),
len: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
let aligned_capacity = align_up(capacity, SIMD_WIDTH);
Self {
x: AlignedVec::with_capacity(aligned_capacity),
y: AlignedVec::with_capacity(aligned_capacity),
z: AlignedVec::with_capacity(aligned_capacity),
len: 0,
}
}
pub fn from_vec3_slice(points: &[Vec3]) -> Self {
let len = points.len();
let aligned_len = align_up(len, SIMD_WIDTH);
let mut soa = Self::with_capacity(aligned_len);
for p in points {
soa.x.push(p.x);
soa.y.push(p.y);
soa.z.push(p.z);
}
let padding = aligned_len - len;
for _ in 0..padding {
soa.x.push(0.0);
soa.y.push(0.0);
soa.z.push(0.0);
}
soa.len = len;
soa
}
#[inline]
pub fn push(&mut self, x: f32, y: f32, z: f32) {
self.x.push(x);
self.y.push(y);
self.z.push(z);
self.len += 1;
}
#[inline]
pub fn push_vec3(&mut self, p: Vec3) {
self.push(p.x, p.y, p.z);
}
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub const fn padded_len(&self) -> usize {
align_up(self.len, SIMD_WIDTH)
}
pub fn ensure_padding(&mut self) {
let padded = self.padded_len();
while self.x.len() < padded {
self.x.push(0.0);
self.y.push(0.0);
self.z.push(0.0);
}
}
#[inline]
pub const fn as_ptrs(&self) -> (*const f32, *const f32, *const f32) {
(self.x.as_ptr(), self.y.as_ptr(), self.z.as_ptr())
}
#[inline]
pub unsafe fn load_simd_unchecked(&self, index: usize) -> (f32x8, f32x8, f32x8) {
let (px, py, pz) = self.as_ptrs();
let x = f32x8::from(std::slice::from_raw_parts(px.add(index), 8));
let y = f32x8::from(std::slice::from_raw_parts(py.add(index), 8));
let z = f32x8::from(std::slice::from_raw_parts(pz.add(index), 8));
(x, y, z)
}
#[inline]
pub fn load_simd(&self, index: usize) -> Option<(f32x8, f32x8, f32x8)> {
if index + SIMD_WIDTH <= self.x.len() {
let x = f32x8::from(&self.x.as_slice()[index..index + 8]);
let y = f32x8::from(&self.y.as_slice()[index..index + 8]);
let z = f32x8::from(&self.z.as_slice()[index..index + 8]);
Some((x, y, z))
} else {
None
}
}
#[inline]
pub fn get(&self, index: usize) -> Option<Vec3> {
if index < self.len {
Some(Vec3::new(self.x[index], self.y[index], self.z[index]))
} else {
None
}
}
pub const fn clear(&mut self) {
self.x.clear();
self.y.clear();
self.z.clear();
self.len = 0;
}
pub fn iter(&self) -> impl Iterator<Item = Vec3> + '_ {
(0..self.len).map(move |i| Vec3::new(self.x[i], self.y[i], self.z[i]))
}
#[inline]
pub const fn as_slices(&self) -> (&[f32], &[f32], &[f32]) {
(self.x.as_slice(), self.y.as_slice(), self.z.as_slice())
}
}
impl Default for SoAPoints {
fn default() -> Self {
Self::new()
}
}
impl FromIterator<Vec3> for SoAPoints {
fn from_iter<T: IntoIterator<Item = Vec3>>(iter: T) -> Self {
let iter = iter.into_iter();
let (lower, upper) = iter.size_hint();
let capacity = upper.unwrap_or(lower);
let mut soa = Self::with_capacity(capacity);
for p in iter {
soa.push_vec3(p);
}
soa.ensure_padding();
soa
}
}
impl<'a> FromIterator<&'a Vec3> for SoAPoints {
fn from_iter<T: IntoIterator<Item = &'a Vec3>>(iter: T) -> Self {
let iter = iter.into_iter();
let (lower, upper) = iter.size_hint();
let capacity = upper.unwrap_or(lower);
let mut soa = Self::with_capacity(capacity);
for p in iter {
soa.push_vec3(*p);
}
soa.ensure_padding();
soa
}
}
pub struct AlignedVec {
ptr: *mut f32,
len: usize,
capacity: usize,
}
unsafe impl Send for AlignedVec {}
unsafe impl Sync for AlignedVec {}
impl AlignedVec {
const ALIGN: usize = 32;
pub const fn new() -> Self {
Self {
ptr: std::ptr::NonNull::dangling().as_ptr(),
len: 0,
capacity: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
if capacity == 0 {
return Self::new();
}
let byte_size = capacity * std::mem::size_of::<f32>();
let layout = std::alloc::Layout::from_size_align(byte_size, Self::ALIGN)
.expect("AlignedVec: invalid layout");
let raw = unsafe { std::alloc::alloc_zeroed(layout) };
if raw.is_null() {
std::alloc::handle_alloc_error(layout);
}
Self {
ptr: raw as *mut f32,
len: 0,
capacity,
}
}
pub fn push(&mut self, value: f32) {
if self.len == self.capacity {
self.grow();
}
unsafe {
self.ptr.add(self.len).write(value);
}
self.len += 1;
}
#[inline]
pub const fn clear(&mut self) {
self.len = 0;
}
#[inline]
pub const fn as_ptr(&self) -> *const f32 {
self.ptr
}
#[inline]
pub const fn as_mut_ptr(&mut self) -> *mut f32 {
self.ptr
}
#[inline]
pub const fn as_slice(&self) -> &[f32] {
if self.len == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
#[inline]
pub const fn as_mut_slice(&mut self) -> &mut [f32] {
if self.len == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
fn grow(&mut self) {
let new_capacity = if self.capacity == 0 {
8
} else {
self.capacity * 2
};
let new_byte_size = new_capacity * std::mem::size_of::<f32>();
let new_layout = std::alloc::Layout::from_size_align(new_byte_size, Self::ALIGN)
.expect("AlignedVec: invalid layout on grow");
let new_raw = unsafe { std::alloc::alloc_zeroed(new_layout) };
if new_raw.is_null() {
std::alloc::handle_alloc_error(new_layout);
}
let new_ptr = new_raw as *mut f32;
if self.capacity > 0 && self.len > 0 {
unsafe {
std::ptr::copy_nonoverlapping(self.ptr, new_ptr, self.len);
}
self.dealloc_current();
}
self.ptr = new_ptr;
self.capacity = new_capacity;
}
fn dealloc_current(&self) {
if self.capacity > 0 {
let byte_size = self.capacity * std::mem::size_of::<f32>();
let layout = std::alloc::Layout::from_size_align(byte_size, Self::ALIGN)
.expect("AlignedVec: invalid layout on dealloc");
unsafe {
std::alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
}
impl Drop for AlignedVec {
fn drop(&mut self) {
self.dealloc_current();
}
}
impl Clone for AlignedVec {
fn clone(&self) -> Self {
let mut new = Self::with_capacity(self.capacity);
if self.len > 0 {
unsafe {
std::ptr::copy_nonoverlapping(self.ptr, new.ptr, self.len);
}
}
new.len = self.len;
new
}
}
impl std::fmt::Debug for AlignedVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AlignedVec")
.field("len", &self.len)
.field("capacity", &self.capacity)
.field("aligned", &(self.ptr as usize % Self::ALIGN == 0))
.finish()
}
}
impl std::ops::Deref for AlignedVec {
type Target = [f32];
#[inline]
fn deref(&self) -> &[f32] {
self.as_slice()
}
}
impl std::ops::DerefMut for AlignedVec {
#[inline]
fn deref_mut(&mut self) -> &mut [f32] {
self.as_mut_slice()
}
}
impl std::ops::Index<usize> for AlignedVec {
type Output = f32;
#[inline]
fn index(&self, index: usize) -> &f32 {
assert!(index < self.len, "AlignedVec index out of bounds");
unsafe { &*self.ptr.add(index) }
}
}
impl std::ops::IndexMut<usize> for AlignedVec {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut f32 {
assert!(index < self.len, "AlignedVec index out of bounds");
unsafe { &mut *self.ptr.add(index) }
}
}
impl Default for AlignedVec {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SoADistances {
pub distances: Vec<f32>,
len: usize,
}
impl SoADistances {
pub fn with_capacity(capacity: usize) -> Self {
let aligned_capacity = align_up(capacity, SIMD_WIDTH);
Self {
distances: vec![0.0; aligned_capacity],
len: capacity,
}
}
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn as_slice(&self) -> &[f32] {
&self.distances[..self.len]
}
pub fn to_vec(&self) -> Vec<f32> {
self.distances[..self.len].to_vec()
}
#[inline]
pub unsafe fn store_simd_unchecked(&mut self, index: usize, values: f32x8) {
let arr: [f32; 8] = values.into();
std::ptr::copy_nonoverlapping(arr.as_ptr(), self.distances.as_mut_ptr().add(index), 8);
}
}
#[inline]
const fn align_up(value: usize, alignment: usize) -> usize {
(value + alignment - 1) & !(alignment - 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_soa_creation() {
let mut soa = SoAPoints::new();
soa.push(1.0, 2.0, 3.0);
soa.push(4.0, 5.0, 6.0);
assert_eq!(soa.len(), 2);
assert_eq!(soa.get(0), Some(Vec3::new(1.0, 2.0, 3.0)));
assert_eq!(soa.get(1), Some(Vec3::new(4.0, 5.0, 6.0)));
}
#[test]
fn test_soa_from_vec3_slice() {
let points = vec![
Vec3::new(1.0, 2.0, 3.0),
Vec3::new(4.0, 5.0, 6.0),
Vec3::new(7.0, 8.0, 9.0),
];
let soa = SoAPoints::from_vec3_slice(&points);
assert_eq!(soa.len(), 3);
assert_eq!(soa.padded_len(), 8);
for (i, p) in points.iter().enumerate() {
assert_eq!(soa.get(i), Some(*p));
}
}
#[test]
fn test_soa_simd_load() {
let points: Vec<Vec3> = (0..8)
.map(|i| Vec3::new(i as f32, (i * 10) as f32, (i * 100) as f32))
.collect();
let soa = SoAPoints::from_vec3_slice(&points);
let (x, y, z) = soa.load_simd(0).unwrap();
let x_arr: [f32; 8] = x.into();
let y_arr: [f32; 8] = y.into();
let z_arr: [f32; 8] = z.into();
for i in 0..8 {
assert_eq!(x_arr[i], i as f32);
assert_eq!(y_arr[i], (i * 10) as f32);
assert_eq!(z_arr[i], (i * 100) as f32);
}
}
#[test]
fn test_soa_iter() {
let points = vec![Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0)];
let soa = SoAPoints::from_vec3_slice(&points);
let collected: Vec<Vec3> = soa.iter().collect();
assert_eq!(collected, points);
}
#[test]
fn test_soa_from_iterator() {
let points = [Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0)];
let soa: SoAPoints = points.iter().collect();
assert_eq!(soa.len(), 2);
assert_eq!(soa.get(0), Some(Vec3::new(1.0, 2.0, 3.0)));
}
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(1, 8), 8);
assert_eq!(align_up(7, 8), 8);
assert_eq!(align_up(8, 8), 8);
assert_eq!(align_up(9, 8), 16);
}
#[test]
fn test_aligned_vec_32byte_alignment() {
let v = AlignedVec::with_capacity(16);
assert!(
v.as_ptr() as usize % 32 == 0,
"AlignedVec pointer must be 32-byte aligned, got {:p}",
v.as_ptr()
);
let mut v2 = AlignedVec::new();
for i in 0..100 {
v2.push(i as f32);
}
assert!(
v2.as_ptr() as usize % 32 == 0,
"AlignedVec pointer must remain 32-byte aligned after grow, got {:p}",
v2.as_ptr()
);
assert_eq!(v2.len(), 100);
assert_eq!(v2[0], 0.0);
assert_eq!(v2[99], 99.0);
}
#[test]
fn test_aligned_vec_push_and_index() {
let mut v = AlignedVec::new();
v.push(1.0);
v.push(2.0);
v.push(3.0);
assert_eq!(v.len(), 3);
assert_eq!(v[0], 1.0);
assert_eq!(v[1], 2.0);
assert_eq!(v[2], 3.0);
assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]);
}
#[test]
fn test_aligned_vec_clone() {
let mut v = AlignedVec::with_capacity(8);
v.push(10.0);
v.push(20.0);
let v2 = v.clone();
assert_eq!(v2.len(), 2);
assert_eq!(v2[0], 10.0);
assert_eq!(v2[1], 20.0);
assert_eq!(v2.as_ptr() as usize % 32, 0);
}
#[test]
fn test_aligned_vec_clear() {
let mut v = AlignedVec::with_capacity(8);
v.push(1.0);
v.push(2.0);
v.clear();
assert_eq!(v.len(), 0);
assert!(v.is_empty());
}
#[test]
fn test_soa_points_alignment() {
let points: Vec<Vec3> = (0..8)
.map(|i| Vec3::new(i as f32, (i * 10) as f32, (i * 100) as f32))
.collect();
let soa = SoAPoints::from_vec3_slice(&points);
assert!(
soa.x.as_ptr() as usize % 32 == 0,
"SoA x must be 32-byte aligned"
);
assert!(
soa.y.as_ptr() as usize % 32 == 0,
"SoA y must be 32-byte aligned"
);
assert!(
soa.z.as_ptr() as usize % 32 == 0,
"SoA z must be 32-byte aligned"
);
}
#[test]
fn test_soa_points_empty() {
let soa = SoAPoints::new();
assert_eq!(soa.len(), 0);
assert!(soa.is_empty());
}
#[test]
fn test_soa_points_single() {
let mut soa = SoAPoints::new();
soa.push(1.0, 2.0, 3.0);
assert_eq!(soa.len(), 1);
assert!(!soa.is_empty());
let p = soa.get(0).expect("expected Some(Vec3)");
assert_eq!(p.x, 1.0);
assert_eq!(p.y, 2.0);
assert_eq!(p.z, 3.0);
}
#[test]
fn test_soa_points_large() {
let n = 1024;
let mut soa = SoAPoints::with_capacity(n);
for i in 0..n {
soa.push(i as f32, (i as f32) * 2.0, (i as f32) * 3.0);
}
assert_eq!(soa.len(), n);
let p = soa.get(n - 1).expect("expected Some(Vec3)");
assert_eq!(p.x, (n - 1) as f32);
assert_eq!(p.y, ((n - 1) as f32) * 2.0);
assert_eq!(p.z, ((n - 1) as f32) * 3.0);
}
#[test]
fn test_soa_distances_empty() {
let soa = SoADistances::with_capacity(0);
assert_eq!(soa.len(), 0);
assert!(soa.is_empty());
}
#[test]
fn test_soa_simd_boundary() {
let n = 9;
let mut soa = SoAPoints::with_capacity(n);
for i in 0..n {
soa.push(i as f32, 0.0, 0.0);
}
assert_eq!(soa.len(), n);
let p = soa.get(8).expect("expected Some(Vec3)");
assert_eq!(p.x, 8.0);
}
}