use super::guard::ffi_guard;
use super::registry::{get_compiled, get_node};
use super::types::*;
use crate::animation::AnimationParams;
use crate::compiled::{eval_compiled, CompiledSdf};
use std::slice;
#[no_mangle]
pub extern "C" fn alice_sdf_eval(node: SdfHandle, x: f32, y: f32, z: f32) -> f32 {
ffi_guard(f32::MAX, || {
let sdf_node = match get_node(node) {
Some(n) => n,
None => return f32::MAX,
};
crate::eval(&sdf_node, glam::Vec3::new(x, y, z))
})
}
#[no_mangle]
pub extern "C" fn alice_sdf_eval_compiled(compiled: CompiledHandle, x: f32, y: f32, z: f32) -> f32 {
ffi_guard(f32::MAX, || {
let comp = match get_compiled(compiled) {
Some(c) => c,
None => return f32::MAX,
};
eval_compiled(&comp, glam::Vec3::new(x, y, z))
})
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_batch(
node: SdfHandle,
points: *const f32,
distances: *mut f32,
count: u32,
) -> BatchResult {
ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| {
if points.is_null() || distances.is_null() {
return BatchResult {
count: 0,
result: SdfResult::NullPointer,
};
}
let sdf_node = match get_node(node) {
Some(n) => n,
None => {
return BatchResult {
count: 0,
result: SdfResult::InvalidHandle,
}
}
};
let count_usize = count as usize;
let points_slice = slice::from_raw_parts(points, count_usize * 3);
let distances_slice = slice::from_raw_parts_mut(distances, count_usize);
let compiled = CompiledSdf::compile(&sdf_node);
use rayon::prelude::*;
if count_usize >= 256 {
distances_slice
.par_iter_mut()
.enumerate()
.for_each(|(i, d)| {
let p = glam::Vec3::new(
points_slice[i * 3],
points_slice[i * 3 + 1],
points_slice[i * 3 + 2],
);
*d = eval_compiled(&compiled, p);
});
} else {
for i in 0..count_usize {
let p = glam::Vec3::new(
points_slice[i * 3],
points_slice[i * 3 + 1],
points_slice[i * 3 + 2],
);
distances_slice[i] = eval_compiled(&compiled, p);
}
}
BatchResult {
count,
result: SdfResult::Ok,
}
},
)
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_compiled_batch(
compiled: CompiledHandle,
points: *const f32,
distances: *mut f32,
count: u32,
) -> BatchResult {
ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| {
if points.is_null() || distances.is_null() {
return BatchResult {
count: 0,
result: SdfResult::NullPointer,
};
}
let comp = match get_compiled(compiled) {
Some(c) => c,
None => {
return BatchResult {
count: 0,
result: SdfResult::InvalidHandle,
}
}
};
let count_usize = count as usize;
let points_slice = slice::from_raw_parts(points, count_usize * 3);
let distances_slice = slice::from_raw_parts_mut(distances, count_usize);
use rayon::prelude::*;
if count_usize >= 256 {
distances_slice
.par_iter_mut()
.enumerate()
.for_each(|(i, d)| {
let p = glam::Vec3::new(
points_slice[i * 3],
points_slice[i * 3 + 1],
points_slice[i * 3 + 2],
);
*d = eval_compiled(&comp, p);
});
} else {
for i in 0..count_usize {
let p = glam::Vec3::new(
points_slice[i * 3],
points_slice[i * 3 + 1],
points_slice[i * 3 + 2],
);
distances_slice[i] = eval_compiled(&comp, p);
}
}
BatchResult {
count,
result: SdfResult::Ok,
}
},
)
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_soa(
compiled: CompiledHandle,
x: *const f32,
y: *const f32,
z: *const f32,
distances: *mut f32,
count: u32,
) -> BatchResult {
ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| {
if x.is_null() || y.is_null() || z.is_null() || distances.is_null() {
return BatchResult {
count: 0,
result: SdfResult::NullPointer,
};
}
let comp = match get_compiled(compiled) {
Some(c) => c,
None => {
return BatchResult {
count: 0,
result: SdfResult::InvalidHandle,
}
}
};
let count_usize = count as usize;
let x_slice = slice::from_raw_parts(x, count_usize);
let y_slice = slice::from_raw_parts(y, count_usize);
let z_slice = slice::from_raw_parts(z, count_usize);
let dist_slice = slice::from_raw_parts_mut(distances, count_usize);
use rayon::prelude::*;
const PARALLEL_THRESHOLD: usize = 1024;
if count_usize >= PARALLEL_THRESHOLD {
const CHUNK_SIZE: usize = 4096;
dist_slice
.par_chunks_mut(CHUNK_SIZE)
.enumerate()
.for_each(|(chunk_idx, chunk)| {
let start = chunk_idx * CHUNK_SIZE;
let chunk_len = chunk.len();
let simd_iters = chunk_len / 8;
for s in 0..simd_iters {
let base = start + s * 8;
let local_base = s * 8;
let vx = wide::f32x8::from([
x_slice[base],
x_slice[base + 1],
x_slice[base + 2],
x_slice[base + 3],
x_slice[base + 4],
x_slice[base + 5],
x_slice[base + 6],
x_slice[base + 7],
]);
let vy = wide::f32x8::from([
y_slice[base],
y_slice[base + 1],
y_slice[base + 2],
y_slice[base + 3],
y_slice[base + 4],
y_slice[base + 5],
y_slice[base + 6],
y_slice[base + 7],
]);
let vz = wide::f32x8::from([
z_slice[base],
z_slice[base + 1],
z_slice[base + 2],
z_slice[base + 3],
z_slice[base + 4],
z_slice[base + 5],
z_slice[base + 6],
z_slice[base + 7],
]);
let p = crate::compiled::Vec3x8 {
x: vx,
y: vy,
z: vz,
};
let distances = crate::compiled::eval_compiled_simd(&comp, p);
let arr: [f32; 8] = distances.into();
chunk[local_base..local_base + 8].copy_from_slice(&arr);
}
let remainder_start = simd_iters * 8;
for (offset, slot) in
chunk[remainder_start..chunk_len].iter_mut().enumerate()
{
let i = start + remainder_start + offset;
let p = glam::Vec3::new(x_slice[i], y_slice[i], z_slice[i]);
*slot = eval_compiled(&comp, p);
}
});
} else {
crate::compiled::eval_compiled_batch_soa_raw(
&comp,
x,
y,
z,
distances,
count_usize,
);
}
BatchResult {
count,
result: SdfResult::Ok,
}
},
)
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_gradient_soa(
compiled: CompiledHandle,
x: *const f32,
y: *const f32,
z: *const f32,
nx: *mut f32,
ny: *mut f32,
nz: *mut f32,
dist: *mut f32,
count: u32,
) -> BatchResult {
ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| {
if x.is_null()
|| y.is_null()
|| z.is_null()
|| nx.is_null()
|| ny.is_null()
|| nz.is_null()
{
return BatchResult {
count: 0,
result: SdfResult::NullPointer,
};
}
let comp = match get_compiled(compiled) {
Some(c) => c,
None => {
return BatchResult {
count: 0,
result: SdfResult::InvalidHandle,
}
}
};
let count_usize = count as usize;
let x_slice = slice::from_raw_parts(x, count_usize);
let y_slice = slice::from_raw_parts(y, count_usize);
let z_slice = slice::from_raw_parts(z, count_usize);
let nx_slice = slice::from_raw_parts_mut(nx, count_usize);
let ny_slice = slice::from_raw_parts_mut(ny, count_usize);
let nz_slice = slice::from_raw_parts_mut(nz, count_usize);
let mut dist_slice = if dist.is_null() {
None
} else {
Some(slice::from_raw_parts_mut(dist, count_usize))
};
use crate::compiled::{eval_distance_and_gradient_simd, Vec3x8};
use rayon::prelude::*;
use wide::f32x8;
const EPSILON: f32 = 0.001;
const PARALLEL_THRESHOLD: usize = 1024;
const CHUNK_SIZE: usize = 4096;
if count_usize >= PARALLEL_THRESHOLD {
let chunks: Vec<(usize, usize)> = (0..count_usize)
.step_by(CHUNK_SIZE)
.map(|start| (start, (start + CHUNK_SIZE).min(count_usize)))
.collect();
chunks.into_par_iter().for_each(|(start, end)| {
let chunk_len = end - start;
let simd_iters = chunk_len / 8;
for s in 0..simd_iters {
let base = start + s * 8;
let vx = f32x8::from([
x_slice[base],
x_slice[base + 1],
x_slice[base + 2],
x_slice[base + 3],
x_slice[base + 4],
x_slice[base + 5],
x_slice[base + 6],
x_slice[base + 7],
]);
let vy = f32x8::from([
y_slice[base],
y_slice[base + 1],
y_slice[base + 2],
y_slice[base + 3],
y_slice[base + 4],
y_slice[base + 5],
y_slice[base + 6],
y_slice[base + 7],
]);
let vz = f32x8::from([
z_slice[base],
z_slice[base + 1],
z_slice[base + 2],
z_slice[base + 3],
z_slice[base + 4],
z_slice[base + 5],
z_slice[base + 6],
z_slice[base + 7],
]);
let p = Vec3x8 {
x: vx,
y: vy,
z: vz,
};
let (d, gx, gy, gz) = eval_distance_and_gradient_simd(&comp, p, EPSILON);
let d_arr: [f32; 8] = d.to_array();
let gx_arr: [f32; 8] = gx.to_array();
let gy_arr: [f32; 8] = gy.to_array();
let gz_arr: [f32; 8] = gz.to_array();
for i in 0..8 {
let len = (gz_arr[i] * gz_arr[i]
+ (gx_arr[i] * gx_arr[i] + (gy_arr[i] * gy_arr[i])))
.sqrt();
let inv_len = if len > 1e-8 { 1.0 / len } else { 0.0 };
*nx_slice.as_ptr().add(base + i).cast_mut() = gx_arr[i] * inv_len;
*ny_slice.as_ptr().add(base + i).cast_mut() = gy_arr[i] * inv_len;
*nz_slice.as_ptr().add(base + i).cast_mut() = gz_arr[i] * inv_len;
}
if let Some(ref ds) = dist_slice {
for (i, &d) in d_arr.iter().enumerate() {
*ds.as_ptr().add(base + i).cast_mut() = d;
}
}
}
let remainder_start = start + simd_iters * 8;
for i in remainder_start..end {
let p = glam::Vec3::new(x_slice[i], y_slice[i], z_slice[i]);
let d = eval_compiled(&comp, p);
let eps = EPSILON;
let dx = eval_compiled(&comp, p + glam::Vec3::X * eps)
- eval_compiled(&comp, p - glam::Vec3::X * eps);
let dy = eval_compiled(&comp, p + glam::Vec3::Y * eps)
- eval_compiled(&comp, p - glam::Vec3::Y * eps);
let dz = eval_compiled(&comp, p + glam::Vec3::Z * eps)
- eval_compiled(&comp, p - glam::Vec3::Z * eps);
let len = (dz * dz + (dx * dx + (dy * dy))).sqrt();
let inv_len = if len > 1e-8 { 1.0 / len } else { 0.0 };
*nx_slice.as_ptr().add(i).cast_mut() = dx * inv_len;
*ny_slice.as_ptr().add(i).cast_mut() = dy * inv_len;
*nz_slice.as_ptr().add(i).cast_mut() = dz * inv_len;
if let Some(ref ds) = dist_slice {
*ds.as_ptr().add(i).cast_mut() = d;
}
}
});
} else {
for i in 0..count_usize {
let p = glam::Vec3::new(x_slice[i], y_slice[i], z_slice[i]);
let d = eval_compiled(&comp, p);
let eps = EPSILON;
let dx = eval_compiled(&comp, p + glam::Vec3::X * eps)
- eval_compiled(&comp, p - glam::Vec3::X * eps);
let dy = eval_compiled(&comp, p + glam::Vec3::Y * eps)
- eval_compiled(&comp, p - glam::Vec3::Y * eps);
let dz = eval_compiled(&comp, p + glam::Vec3::Z * eps)
- eval_compiled(&comp, p - glam::Vec3::Z * eps);
let len = (dz * dz + (dx * dx + (dy * dy))).sqrt();
let inv_len = if len > 1e-8 { 1.0 / len } else { 0.0 };
nx_slice[i] = dx * inv_len;
ny_slice[i] = dy * inv_len;
nz_slice[i] = dz * inv_len;
if let Some(ref mut ds) = dist_slice {
ds[i] = d;
}
}
}
BatchResult {
count,
result: SdfResult::Ok,
}
},
)
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_animated_compiled(
compiled: CompiledHandle,
params: *const AnimationParams,
x: f32,
y: f32,
z: f32,
) -> f32 {
ffi_guard(f32::MAX, || {
if params.is_null() {
return f32::MAX;
}
let comp = match get_compiled(compiled) {
Some(c) => c,
None => return f32::MAX,
};
let params_ref = &*params;
crate::animation::eval_animated_compiled(&comp, params_ref, glam::Vec3::new(x, y, z))
})
}
#[no_mangle]
pub unsafe extern "C" fn alice_sdf_eval_animated_batch_soa(
compiled: CompiledHandle,
params: *const AnimationParams,
x: *const f32,
y: *const f32,
z: *const f32,
distances: *mut f32,
count: u32,
) -> BatchResult {
ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| {
if params.is_null() || x.is_null() || y.is_null() || z.is_null() || distances.is_null()
{
return BatchResult {
count: 0,
result: SdfResult::NullPointer,
};
}
let comp = match get_compiled(compiled) {
Some(c) => c,
None => {
return BatchResult {
count: 0,
result: SdfResult::InvalidHandle,
}
}
};
let params_ref = &*params;
let count_usize = count as usize;
let x_slice = slice::from_raw_parts(x, count_usize);
let y_slice = slice::from_raw_parts(y, count_usize);
let z_slice = slice::from_raw_parts(z, count_usize);
let dist_slice = slice::from_raw_parts_mut(distances, count_usize);
let has_transform =
params_ref.has_translation() || params_ref.has_rotation() || params_ref.has_scale();
if !has_transform {
crate::compiled::eval_compiled_batch_soa_raw(
&comp,
x,
y,
z,
distances,
count_usize,
);
} else {
let simd_iters = count_usize / 8;
for s in 0..simd_iters {
let base = s * 8;
let mut px = [0.0f32; 8];
let mut py = [0.0f32; 8];
let mut pz = [0.0f32; 8];
let mut scale_corrections = [1.0f32; 8];
for i in 0..8 {
let point = glam::Vec3::new(
x_slice[base + i],
y_slice[base + i],
z_slice[base + i],
);
let (tp, sc) = params_ref.transform_point(point);
px[i] = tp.x;
py[i] = tp.y;
pz[i] = tp.z;
scale_corrections[i] = sc;
}
let vx = wide::f32x8::from(px);
let vy = wide::f32x8::from(py);
let vz = wide::f32x8::from(pz);
let p = crate::compiled::Vec3x8 {
x: vx,
y: vy,
z: vz,
};
let raw_distances = crate::compiled::eval_compiled_simd(&comp, p);
let raw_arr: [f32; 8] = raw_distances.into();
for i in 0..8 {
dist_slice[base + i] = raw_arr[i] * scale_corrections[i];
}
}
let remainder_start = simd_iters * 8;
for i in remainder_start..count_usize {
let point = glam::Vec3::new(x_slice[i], y_slice[i], z_slice[i]);
dist_slice[i] =
crate::animation::eval_animated_compiled(&comp, params_ref, point);
}
}
BatchResult {
count,
result: SdfResult::Ok,
}
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn panic_inside_ffi_returns_sentinel_and_message() {
use super::super::guard::{clear_last_error, take_last_error};
clear_last_error();
let r = ffi_guard(
BatchResult {
count: 0,
result: SdfResult::Unknown,
},
|| -> BatchResult { panic!("internal invariant violated") },
);
assert_eq!(r.result, SdfResult::Unknown);
assert_eq!(r.count, 0);
let msg = take_last_error().expect("panic message recorded");
assert!(msg.contains("internal invariant violated"), "{msg}");
let node = super::super::primitives::alice_sdf_sphere(1.0);
let mesh = super::super::mesh::alice_sdf_generate_mesh(node, 0, 1.0);
if !mesh.is_null() {
super::super::mesh::alice_sdf_free_mesh(mesh);
}
let mut dist = [0.0f32; 1];
let r = unsafe { alice_sdf_eval_batch(node, std::ptr::null(), dist.as_mut_ptr(), 1) };
assert_ne!(r.result, SdfResult::Ok);
super::super::memory::alice_sdf_free(node);
clear_last_error();
}
use crate::ffi::compilation::{alice_sdf_compile, alice_sdf_free_compiled};
use crate::ffi::memory::alice_sdf_free;
use crate::ffi::primitives::alice_sdf_sphere;
#[test]
fn test_compile_and_eval() {
let sphere = alice_sdf_sphere(1.0);
assert!(!sphere.is_null());
let compiled = alice_sdf_compile(sphere);
assert!(!compiled.is_null());
let d = alice_sdf_eval_compiled(compiled, 0.0, 0.0, 0.0);
assert!((d + 1.0).abs() < 0.001);
let d = alice_sdf_eval_compiled(compiled, 1.0, 0.0, 0.0);
assert!(d.abs() < 0.001);
alice_sdf_free_compiled(compiled);
alice_sdf_free(sphere);
}
#[test]
fn test_batch_eval() {
let sphere = alice_sdf_sphere(1.0);
let compiled = alice_sdf_compile(sphere);
let points: [f32; 9] = [
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0, ];
let mut distances: [f32; 3] = [0.0; 3];
unsafe {
let result =
alice_sdf_eval_compiled_batch(compiled, points.as_ptr(), distances.as_mut_ptr(), 3);
assert_eq!(result.result, SdfResult::Ok);
assert_eq!(result.count, 3);
}
assert!((distances[0] + 1.0).abs() < 0.001); assert!(distances[1].abs() < 0.001); assert!((distances[2] - 1.0).abs() < 0.001);
alice_sdf_free_compiled(compiled);
alice_sdf_free(sphere);
}
#[test]
fn test_animated_compiled_eval() {
let sphere = alice_sdf_sphere(1.0);
let compiled = alice_sdf_compile(sphere);
let params = AnimationParams {
scale: 1.0,
..Default::default()
};
unsafe {
let d = alice_sdf_eval_animated_compiled(compiled, &raw const params, 0.0, 0.0, 0.0);
assert!((d + 1.0).abs() < 0.01);
}
let params = AnimationParams {
translate_x: 5.0,
scale: 1.0,
..Default::default()
};
unsafe {
let d = alice_sdf_eval_animated_compiled(compiled, &raw const params, 0.0, 0.0, 0.0);
assert!(d > 0.0);
}
alice_sdf_free_compiled(compiled);
alice_sdf_free(sphere);
}
#[test]
fn test_animated_batch_soa() {
let sphere = alice_sdf_sphere(1.0);
let compiled = alice_sdf_compile(sphere);
let params = AnimationParams {
translate_x: 5.0,
scale: 1.0,
..Default::default()
};
let x: [f32; 4] = [0.0, 5.0, 10.0, 5.0];
let y: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
let z: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
let mut distances: [f32; 4] = [0.0; 4];
unsafe {
let result = alice_sdf_eval_animated_batch_soa(
compiled,
&raw const params,
x.as_ptr(),
y.as_ptr(),
z.as_ptr(),
distances.as_mut_ptr(),
4,
);
assert_eq!(result.result, SdfResult::Ok);
}
assert!(distances[0] > 3.0);
assert!((distances[1] + 1.0).abs() < 0.01);
alice_sdf_free_compiled(compiled);
alice_sdf_free(sphere);
}
#[test]
fn test_soa_eval() {
let sphere = alice_sdf_sphere(1.0);
let compiled = alice_sdf_compile(sphere);
let x: [f32; 4] = [0.0, 1.0, 2.0, 0.5];
let y: [f32; 4] = [0.0, 0.0, 0.0, 0.5];
let z: [f32; 4] = [0.0, 0.0, 0.0, 0.5];
let mut distances: [f32; 4] = [0.0; 4];
unsafe {
let result = alice_sdf_eval_soa(
compiled,
x.as_ptr(),
y.as_ptr(),
z.as_ptr(),
distances.as_mut_ptr(),
4,
);
assert_eq!(result.result, SdfResult::Ok);
}
assert!((distances[0] + 1.0).abs() < 0.001); assert!(distances[1].abs() < 0.001); assert!((distances[2] - 1.0).abs() < 0.001);
alice_sdf_free_compiled(compiled);
alice_sdf_free(sphere);
}
}