I'm working on a ray tracer in Rust as a way to learn the language, and the single-threaded version works fine. I want to speed it up by multithreading it, and multithreading a raytracer in C/C++ is relatively easy because most of the data shared is read-only (the only problems occur when writing the pixel data). But I'm having a lot more trouble with Rust and its ownership rules.
I have a trait Hittable: Send + Sync for the different types of things (spheres, meshes) that can get hit in the world, and I left the implementation for Send and Sync blank because I don't actually need either of them. And then I have a vec of the world objects of type Vec<Box<dyn Hittable>>. For the actual multithreading, I'm trying something like this:
let pixels_mutex: Arc<Mutex<Vec<Vec<(f64, f64, f64, u32)>>>> = Arc::new(Mutex::new(pixels));
let vec_arc: Arc<Vec<Box<dyn Hittable>>> = Arc::new(vec);
let mut thread_vec: Vec<thread::JoinHandle<()>> = Vec::new();
for _ in 0..NUM_THREADS {
let camera_clone = camera.clone();
thread_vec.push(thread::spawn(move || {
for r in 0..RAYS_PER_THREAD {
if r % THREAD_UPDATE == 0 {
println!("Thread drawing ray {} of {} ({:.2}%)", r, RAYS_PER_THREAD, (r as f64 * 100.) / (RAYS_PER_THREAD as f64));
}
let u: f64 = util::rand();
let v: f64 = util::rand();
let ray = camera_clone.get_ray(u, v);
let res = geometry::thread_safe_cast_ray(&ray, &vec_arc, MAX_DEPTH);
let i = (u * IMAGE_WIDTH as f64).floor() as usize;
let j = (v * IMAGE_HEIGHT as f64).floor() as usize;
util::thread_safe_increment_color(&pixels_mutex, j, i, &res);
}
}));
}
for handle in thread_vec {
handle.join().unwrap();
}
I have the thread_safe_increment_color implemented and that seems fine, but I'm holding off on doing thread_safe_cast_ray until I get this loop working. The problem I'm running into with this code is that each thread tries to move vec_arc into its closure, which violates the ownership rule. I tried making a clone of vec_arc like I did with camera, but the compiler wouldn't let me, which I think is because my Hittable trait doesn't require the Copy or Clone trait. And my structs that implement Hittable can't simply derive(Copy, Clone) because they contain a Box<dyn Material>, where Material is another trait that represents the material of the object.
I thought this would be way easier since I know most of the data (other than pixels_mutex) is read-only. How can I share vec_arc (and for that matter, pixels_mutex) between the threads I'm creating?