diff options
| author | Sakarias Johansson <sakarias.johansson@goodbyekansas.com> | 2023-01-10 17:54:39 +0100 |
|---|---|---|
| committer | Sakarias Johansson <sakarias.johansson@goodbyekansas.com> | 2023-01-11 19:36:38 +0100 |
| commit | bbecf67545c2bd6822b0680673aa850c5ddef9f3 (patch) | |
| tree | 741b2fc03797a8fedcfe67e34687b4443fcf41f7 | |
| parent | 899f81eed6c221dce22333ad03704b12d7634a54 (diff) | |
| download | racer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.tar.gz racer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.tar.xz racer-tracer-bbecf67545c2bd6822b0680673aa850c5ddef9f3.zip | |
🔨 Refactors & Use rayon
- All data shared between threads are now Arcs since the data is
immutable.
- Remove tokio
- Rustified main
| -rw-r--r-- | racer-tracer/Cargo.toml | 2 | ||||
| -rw-r--r-- | racer-tracer/src/error.rs | 2 | ||||
| -rw-r--r-- | racer-tracer/src/geometry.rs | 4 | ||||
| -rw-r--r-- | racer-tracer/src/geometry/sphere.rs | 14 | ||||
| -rw-r--r-- | racer-tracer/src/image.rs | 1 | ||||
| -rw-r--r-- | racer-tracer/src/main.rs | 177 | ||||
| -rw-r--r-- | racer-tracer/src/scene.rs | 24 |
7 files changed, 106 insertions, 118 deletions
diff --git a/racer-tracer/Cargo.toml b/racer-tracer/Cargo.toml index 2083dcf..241e1d8 100644 --- a/racer-tracer/Cargo.toml +++ b/racer-tracer/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] minifb = "0.23" -tokio = { version = "1.14.0", features = ["macros", "rt", "rt-multi-thread"] } thiserror = "1" futures = "0.3" rand = "0.8.5" +rayon = "1.6.1" diff --git a/racer-tracer/src/error.rs b/racer-tracer/src/error.rs index f581b08..ac78c91 100644 --- a/racer-tracer/src/error.rs +++ b/racer-tracer/src/error.rs @@ -1,6 +1,6 @@ use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Clone, Error, Debug)] pub enum TracerError { #[error("Unknown error: {message}")] Unknown { message: String, exit_code: i32 }, diff --git a/racer-tracer/src/geometry.rs b/racer-tracer/src/geometry.rs index aa1c18c..8d86353 100644 --- a/racer-tracer/src/geometry.rs +++ b/racer-tracer/src/geometry.rs @@ -47,8 +47,4 @@ impl Default for HitRecord { pub trait Hittable { //pub trait Hittable { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>; - - // Manual ugly clone since forcing on Clone would make it a super trait. - // Clone requires Sized and super traits cannot be Sized. - fn clone_box(&self) -> Box<dyn Hittable>; } diff --git a/racer-tracer/src/geometry/sphere.rs b/racer-tracer/src/geometry/sphere.rs index 6a0ab9c..e974c44 100644 --- a/racer-tracer/src/geometry/sphere.rs +++ b/racer-tracer/src/geometry/sphere.rs @@ -20,16 +20,6 @@ impl Sphere { } } -impl Clone for Sphere { - fn clone(&self) -> Self { - Self { - pos: self.pos, - radius: self.radius, - material: self.material, - } - } -} - impl Hittable for Sphere { fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> { let oc = ray.origin() - self.pos; @@ -61,8 +51,4 @@ impl Hittable for Sphere { hit_record.set_face_normal(ray, outward_normal); Some(hit_record) } - - fn clone_box(&self) -> Box<dyn Hittable> { - Box::new(self.clone()) - } } diff --git a/racer-tracer/src/image.rs b/racer-tracer/src/image.rs index 2df11dc..48e81ee 100644 --- a/racer-tracer/src/image.rs +++ b/racer-tracer/src/image.rs @@ -1,4 +1,3 @@ -#[derive(Clone)] pub struct Image { pub aspect_ratio: f64, pub width: usize, diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs index 9d45762..c9ed169 100644 --- a/racer-tracer/src/main.rs +++ b/racer-tracer/src/main.rs @@ -8,13 +8,18 @@ mod scene; mod util; mod vec3; -use std::vec::Vec; +use std::{ + borrow::Borrow, + sync::{Arc, Mutex, RwLock}, + vec::Vec, +}; -use futures::{select, stream::FuturesUnordered, stream::StreamExt}; use geometry::Hittable; use minifb::{Key, Window, WindowOptions}; +use rayon::prelude::*; use crate::camera::Camera; +use crate::error::TracerError; use crate::geometry::sphere::Sphere; use crate::image::Image; use crate::ray::Ray; @@ -22,41 +27,66 @@ use crate::scene::Scene; use crate::util::random_double; use crate::vec3::Vec3; -fn ray_color(scene: &Scene, ray: &Ray) -> Vec3 { +fn ray_color(scene: &dyn Hittable, ray: &Ray) -> Vec3 { if let Some(hit_record) = scene.hit(ray, 0.0, std::f64::INFINITY) { //return hit_record.color; return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0)); } + // TODO: make sky part of scene. // Sky let unit_direction = ray.direction().unit_vector(); let t = 0.5 * (unit_direction.y() + 1.0); (1.0 - t) * Vec3::new(1.0, 1.0, 1.0) + t * Vec3::new(0.5, 0.7, 1.0) } -// TODO: Rustify -async fn raytrace( - scene: Scene, - camera: Camera, - image: Image, - row: usize, -) -> Result<(usize, Vec<u32>), error::TracerError> { - let mut buffer: Vec<u32> = vec![0; image.width as usize]; +fn raytrace(scene: &dyn Hittable, camera: &Camera, image: &Image, row: usize) -> Vec<u32> { let mut colors: Vec<Vec3> = vec![Vec3::default(); image.width as usize]; - - for i in 0..buffer.len() { + colors.iter_mut().enumerate().for_each(|(i, col)| { for _ in 0..image.samples_per_pixel { let u: f64 = (i as f64 + random_double()) / (image.width - 1) as f64; let v: f64 = (row as f64 + random_double()) / (image.height - 1) as f64; - colors[i] += ray_color(&scene, &camera.get_ray(u, v)); + *col += ray_color(scene, &camera.get_ray(u, v)); } - } + }); + // TODO: Could do rolling average + let mut buffer: Vec<u32> = vec![0; image.width as usize]; for i in 0..image.width { buffer[i] = (colors[i] / image.samples_per_pixel as f64).as_color(); } - Ok((row, buffer)) + buffer +} + +type Data = (Arc<RwLock<Vec<u32>>>, Arc<Camera>, Arc<Image>, usize); + +fn render( + buffer: Arc<RwLock<Vec<u32>>>, + camera: Arc<Camera>, + image: Arc<Image>, + scene: Box<dyn Hittable + std::marker::Sync>, +) { + let scene: &(dyn Hittable + Sync) = scene.borrow(); + + let v: Vec<Data> = (0..image.height) + .map(|row| { + ( + Arc::clone(&buffer), + Arc::clone(&camera), + Arc::clone(&image), + row, + ) + }) + .collect(); + + v.par_iter().for_each(|(buf, camera, image, row)| { + let start = row * image.width; + let end = start + image.width; + let col_buf = raytrace(scene.borrow(), camera, image, *row); + let mut buf = buf.write().expect("Failed to get screen buffer lock."); // TODO: No except + buf[start..end].copy_from_slice(col_buf.as_slice()); + }); } fn create_scene() -> Scene { @@ -72,70 +102,67 @@ fn create_scene() -> Scene { scene } -async fn run( - rows_per_update: u32, - aspect_ratio: f64, - screen_width: usize, - samples: usize, -) -> Result<(), error::TracerError> { - let image = image::Image::new(aspect_ratio, screen_width, samples); - let camera = camera::Camera::new(&image, 2.0, 1.0); - let scene = create_scene(); - - let mut screen_buffer: Vec<u32> = vec![0; image.width * image.height]; - let mut window = Window::new( - "racer-tracer", - image.width, - image.height, - WindowOptions::default(), - ) - .expect("Unable to create window"); - window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); - - let mut futs = FuturesUnordered::new(); - // One future per row is a bit high. - // Could do something less spammy. - for h in 0..image.height { - // TODO: Either clone all or lock em all. - futs.push(raytrace(scene.clone(), camera.clone(), image.clone(), h)); - } - - // TODO: use rayon - // Since it's cooperative multitasking this is not really helpful at the moment. - // You will get pretty much get the same result without the tokio asyncness. - // using rayon with threads is a different matter. - let mut complete = false; - while window.is_open() && !window.is_key_down(Key::Escape) { - if !complete { - for _ in 0..rows_per_update { - select! { - res = futs.select_next_some() => { - let row_buffer = res.expect("Expected to get data"); - let start = row_buffer.0 * image.width; - let end = start + image.width; - screen_buffer[start..end].copy_from_slice(row_buffer.1.as_slice()); - }, - complete => { - if !complete { - println!("Completed!"); - } - complete = true; - }, +fn run(aspect_ratio: f64, screen_width: usize, samples: usize) -> Result<(), TracerError> { + let image = Arc::new(image::Image::new(aspect_ratio, screen_width, samples)); + let camera = Arc::new(camera::Camera::new(&image, 2.0, 1.0)); + let scene: Box<dyn Hittable + Sync + Send> = Box::new(create_scene()); + let screen_buffer: Arc<RwLock<Vec<u32>>> = + Arc::new(RwLock::new(vec![0; image.width * image.height])); + + let window_res: Arc<Mutex<Result<(), TracerError>>> = Arc::new(Mutex::new(Ok(()))); + + rayon::scope(|s| { + s.spawn(|_| { + render( + Arc::clone(&screen_buffer), + camera, + Arc::clone(&image), + scene, + ) + }); + s.spawn(|_| { + let result = Window::new( + "racer-tracer", + image.width, + image.height, + WindowOptions::default(), + ) + .map_err(|e| TracerError::FailedToCreateWindow(e.to_string())) + .map(|mut window| { + window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); + window + }) + .and_then(|mut window| { + // TODO: Only re-render window then buffer is changed + while window.is_open() && !window.is_key_down(Key::Escape) { + // Sleep a bit to not hog the lock on the buffer all the time. + std::thread::sleep(std::time::Duration::from_secs(1)); + + screen_buffer + .read() + .map_err(|e| TracerError::FailedToUpdateWindow(e.to_string())) + .and_then(|buf| { + window + .update_with_buffer(&buf, image.width, image.height) + .map_err(|e| TracerError::FailedToUpdateWindow(e.to_string())) + })? } - } - } + Ok(()) + }); - window - .update_with_buffer(&screen_buffer, image.width, image.height) - .map_err(|e| error::TracerError::FailedToUpdateWindow(e.to_string()))?; - } + if result.is_err() { + let mut a = window_res.lock().expect("Failed to get result lock."); + *a = result; + } + }); + }); - Ok(()) + let res = (window_res.lock().expect("Failed to get result lock.")).clone(); + res } -#[tokio::main] -async fn main() { - if let Err(e) = run(100, 16.0 / 9.0, 1200, 100).await { +fn main() { + if let Err(e) = run(16.0 / 9.0, 1200, 10) { eprintln!("{}", e); std::process::exit(e.into()) } diff --git a/racer-tracer/src/scene.rs b/racer-tracer/src/scene.rs index 2d530af..672b39b 100644 --- a/racer-tracer/src/scene.rs +++ b/racer-tracer/src/scene.rs @@ -1,7 +1,7 @@ use crate::geometry::Hittable; pub struct Scene { - objects: Vec<Box<dyn Hittable>>, + objects: Vec<Box<dyn Hittable + Sync + Send>>, } impl Scene { @@ -11,27 +11,11 @@ impl Scene { } } - pub fn add(&mut self, hittable: Box<dyn Hittable>) { + pub fn add(&mut self, hittable: Box<dyn Hittable + Sync + Send>) { self.objects.push(hittable); } } -// TODO: What to do? -// Cloning everything is nice since then every task can do whatever they like. -// Cloning everything is bad becuse you copy everything which takes time. -// Could also put locks on the Scene but then it becomes this global object that everyone -// wants to access at the same time. -// Will do some laborations later and decide on a solution. -impl Clone for Scene { - fn clone(&self) -> Self { - let mut objects = Vec::with_capacity(self.objects.capacity()); - for i in self.objects.iter() { - objects.push(i.clone_box()); - } - Self { objects } - } -} - impl Hittable for Scene { fn hit( &self, @@ -51,8 +35,4 @@ impl Hittable for Scene { rec } - - fn clone_box(&self) -> Box<dyn Hittable> { - Box::new(self.clone()) - } } |
