mkeeter/fidget

Empty mesh for JitShape when translating box

julianschuler opened this issue · 2 comments

Hi again,

I stumbled upon a case where meshing with a VmShape results in a correct mesh while meshing with a JitShape produces an empty one (on an x86 computer).

More specifically, this only seems to be the case when using a (mitered) box primitive and translating it along Z using remap_xyz(), but only for translations putting the box entirely above/below the XY-plane.

Here is a minimum working example reproducing this issue:

use fidget::{
    context::{IntoNode, Node},
    eval::MathShape,
    jit::JitShape,
    mesh::{Octree, Settings},
    vm::VmShape,
    Context, Error,
};

pub struct BoxShape {
    size: f64,
}

impl BoxShape {
    pub fn new(size: f64) -> Self {
        Self { size }
    }
}

impl IntoNode for BoxShape {
    fn into_node(self, context: &mut Context) -> Result<Node, Error> {
        let x = context.x();
        let y = context.y();
        let z = context.z();

        let abs_x = context.abs(x)?;
        let abs_y = context.abs(y)?;
        let abs_z = context.abs(z)?;

        let qx = context.sub(abs_x, self.size / 2.0)?;
        let qy = context.sub(abs_y, self.size / 2.0)?;
        let qz = context.sub(abs_z, self.size / 2.0)?;

        let max_elem = context.max(qx, qy)?;
        context.max(max_elem, qz)
    }
}

fn translate_z(
    context: &mut Context,
    root: impl IntoNode,
    translation: f64,
) -> Result<Node, Error> {
    let root = root.into_node(context)?;

    let x = context.x();
    let y = context.y();
    let z = context.z();
    let translated_z = context.sub(z, translation)?;

    context.remap_xyz(root, [x, y, translated_z])
}

fn main() -> Result<(), Error> {
    let settings = Settings {
        threads: 12,
        min_depth: 6,
        max_depth: 6,
    };

    for i in -5..=5 {
        let translation = i as f64 / 10.0;

        let mut context = Context::new();
        let box_shape = BoxShape::new(0.4);
        let root = translate_z(&mut context, box_shape, translation)?;

        let jit_shape = JitShape::new(&context, root)?;
        let jit_mesh = Octree::build(&jit_shape, settings).walk_dual(settings);
        let vm_shape = VmShape::new(&context, root)?;
        let vm_mesh = Octree::build(&vm_shape, settings).walk_dual(settings);

        println!(
            "translation: {:.1}  \t vertices (jit): {}\t vertices (vm): {}",
            translation,
            jit_mesh.vertices.len(),
            vm_mesh.vertices.len(),
        );
    }

    Ok(())
}

The output is the following (notice the lines with vertices jit: 0):

translation: -0.5  	 vertices (jit): 0	 vertices (vm): 14
translation: -0.4  	 vertices (jit): 0	 vertices (vm): 36
translation: -0.3  	 vertices (jit): 0	 vertices (vm): 21
translation: -0.2  	 vertices (jit): 14	 vertices (vm): 14
translation: -0.1  	 vertices (jit): 14	 vertices (vm): 14
translation: 0.0  	 vertices (jit): 14	 vertices (vm): 14
translation: 0.1  	 vertices (jit): 17	 vertices (vm): 17
translation: 0.2  	 vertices (jit): 14	 vertices (vm): 14
translation: 0.3  	 vertices (jit): 0	 vertices (vm): 21
translation: 0.4  	 vertices (jit): 0	 vertices (vm): 14
translation: 0.5  	 vertices (jit): 0	 vertices (vm): 42

Please let me know if I can help in any way resolving this.

Interesting, thanks for the minimal example!

This looks like a bug in the x86 JIT specifically: running on AArch64, both JIT and VM evaluator give the same result.

It's not a bug in tape generation, because the system works with GenericVmShape::<12>::new(..) (i.e. building a VM-evaluated shape with only 12 registers, to match x86 JitShape).

(later)

I tracked it down to a bug in the x86 interval abs implementation, where we created a zero value by pulling from a high SIMD slot – which wasn't guaranteed to be empty!

Fixed by #50

Thank you for the quick fix!