diff options
| -rw-r--r-- | racer-tracer/Cargo.toml | 3 | ||||
| -rw-r--r-- | racer-tracer/src/camera.rs | 18 | ||||
| -rw-r--r-- | racer-tracer/src/error.rs | 4 | ||||
| -rw-r--r-- | racer-tracer/src/image.rs | 79 | ||||
| -rw-r--r-- | racer-tracer/src/main.rs | 247 | ||||
| -rw-r--r-- | racer-tracer/src/render.rs | 177 |
6 files changed, 388 insertions, 140 deletions
diff --git a/racer-tracer/Cargo.toml b/racer-tracer/Cargo.toml index 241e1d8..9bd6b6a 100644 --- a/racer-tracer/Cargo.toml +++ b/racer-tracer/Cargo.toml @@ -10,3 +10,6 @@ thiserror = "1" futures = "0.3" rand = "0.8.5" rayon = "1.6.1" +structopt = "0.3" +num_cpus = "1.15.0" +synchronoise = "1.0.1" diff --git a/racer-tracer/src/camera.rs b/racer-tracer/src/camera.rs index 2c7be12..45d2919 100644 --- a/racer-tracer/src/camera.rs +++ b/racer-tracer/src/camera.rs @@ -33,9 +33,25 @@ impl Camera { } pub fn get_ray(&self, u: f64, v: f64) -> Ray { + let upper_left_corner = self.origin + self.vertical / 2.0 + - self.horizontal / 2.0 + - Vec3::new(0.0, 0.0, self.focal_length); + Ray::new( self.origin, - self.upper_left_corner + u * self.horizontal - v * self.vertical - self.origin, + upper_left_corner + u * self.horizontal - v * self.vertical - self.origin, ) } + + // TODO: Add support for rotation + + // TODO: Use forward facing vector + pub fn go_forward(&mut self, go: f64) { + self.origin += Vec3::new(0.0, 0.0, go); + } + + // TODO: Use right facing vector + pub fn go_right(&mut self, go: f64) { + self.origin += Vec3::new(go, 0.0, 0.0); + } } diff --git a/racer-tracer/src/error.rs b/racer-tracer/src/error.rs index ac78c91..9254715 100644 --- a/racer-tracer/src/error.rs +++ b/racer-tracer/src/error.rs @@ -10,6 +10,9 @@ pub enum TracerError { #[error("Failed to update window: {0}")] FailedToUpdateWindow(String), + + #[error("Resolution is not power of two.")] + ResolutionIsNotPowerOfTwo(), } impl From<TracerError> for i32 { @@ -21,6 +24,7 @@ impl From<TracerError> for i32 { } => exit_code, TracerError::FailedToCreateWindow(_) => 2, TracerError::FailedToUpdateWindow(_) => 3, + TracerError::ResolutionIsNotPowerOfTwo() => 4, } } } diff --git a/racer-tracer/src/image.rs b/racer-tracer/src/image.rs index 48e81ee..502a15a 100644 --- a/racer-tracer/src/image.rs +++ b/racer-tracer/src/image.rs @@ -15,3 +15,82 @@ impl Image { } } } + +// TODO: SubImage and Image can probably be the same struct +impl From<&Image> for SubImage { + fn from(image: &Image) -> Self { + SubImage { + x: 0, + y: 0, + width: image.width, + height: image.height, + samples: image.samples_per_pixel, + screen_width: image.width, + screen_height: image.height, + } + } +} + +pub struct SubImage { + pub x: usize, + pub y: usize, + pub screen_width: usize, + pub screen_height: usize, + pub width: usize, + pub height: usize, + pub samples: usize, +} + +pub trait QuadSplit { + fn quad_split(&self) -> [SubImage; 4]; +} + +impl QuadSplit for SubImage { + fn quad_split(&self) -> [SubImage; 4] { + let half_w = self.width / 2; + let half_h = self.height / 2; + + [ + // Top Left + SubImage { + x: self.x, + y: self.y, + width: half_w, + height: half_h, + samples: self.samples, + screen_width: self.screen_width, + screen_height: self.screen_height, + }, + // Top Right + SubImage { + x: self.x + half_w, + y: self.y, + width: half_w, + height: half_h, + samples: self.samples, + screen_width: self.screen_width, + screen_height: self.screen_height, + }, + // Bottom Left + SubImage { + x: self.x, + y: self.y + half_h, + width: half_w, + height: half_h, + samples: self.samples, + screen_width: self.screen_width, + screen_height: self.screen_height, + }, + // Bottom Right + SubImage { + x: self.x + half_w, + y: self.y + half_h, + width: half_w, + height: half_h, + samples: self.samples, + screen_width: self.screen_width, + screen_height: self.screen_height, + }, + ] + } +} diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs index ef2cb4a..d95717d 100644 --- a/racer-tracer/src/main.rs +++ b/racer-tracer/src/main.rs @@ -5,12 +5,12 @@ mod geometry; mod image; mod material; mod ray; +mod render; mod scene; mod util; mod vec3; use std::{ - borrow::Borrow, sync::{Arc, Mutex, RwLock}, time::{Duration, Instant}, vec::Vec, @@ -18,131 +18,22 @@ use std::{ use material::{lambertian::Lambertian, metal::Metal, Material}; use minifb::{Key, Window, WindowOptions}; -use rayon::prelude::*; -use vec3::Color; +use synchronoise::SignalEvent; use crate::{ - camera::Camera, error::TracerError, geometry::sphere::Sphere, geometry::Hittable, image::Image, - ray::Ray, scene::Scene, util::random_double, vec3::Vec3, + camera::Camera, + error::TracerError, + geometry::sphere::Sphere, + geometry::Hittable, + image::SubImage, + render::render, + scene::Scene, + vec3::{Color, Vec3}, }; -fn ray_color(scene: &dyn Hittable, ray: &Ray, depth: usize) -> Vec3 { - if depth == 0 { - return Vec3::default(); - } - - if let Some(rec) = scene.hit(ray, 0.001, std::f64::INFINITY) { - if let Some((scattered, attenuation)) = rec.material.scatter(ray, &rec) { - return attenuation * ray_color(scene, &scattered, depth - 1); - } - return Color::default(); - //let target = rec.point + random_in_hemisphere(&rec.normal); - //let target = rec.point + rec.normal + random_unit_vector(); - //return 0.5 * ray_color(scene, &Ray::new(rec.point, target - rec.point), depth - 1); - //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) -} - -fn flush( - row: usize, - width: usize, - start: usize, - count: usize, - samples: usize, - source: &[Vec3], - dest: &RwLock<Vec<u32>>, -) { - let mut buf = dest - .write() - .expect("Failed to get write guard when flushing data."); - - for i in 0..count { - buf[row * width + start + i] = source[start + i].scale_sqrt(samples).as_color(); - } -} - -fn raytrace( - buffer: &RwLock<Vec<u32>>, - scene: &dyn Hittable, - camera: &Camera, - image: &Image, - row: usize, - max_depth: usize, -) { - let mut now = Instant::now(); - let flush_delay = Duration::from_millis(50 + ((random_double() * 2000.0) as u64)); - let mut flush_start: usize = 0; - let mut colors: Vec<Vec3> = vec![Vec3::default(); image.width as usize]; - - for i in 0..colors.len() { - 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].add(ray_color(scene, &camera.get_ray(u, v), max_depth)); - } - - // Update the screen buffer every now and again. - if now.elapsed() > flush_delay { - now = Instant::now(); - flush( - row, - image.width, - flush_start, - i - flush_start, - image.samples_per_pixel, - colors.as_slice(), - buffer, - ); - flush_start = i; - } - } - - // Flush last part of the buffer. - flush( - row, - image.width, - flush_start, - colors.len() - flush_start, - image.samples_per_pixel, - colors.as_slice(), - 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>, - max_depth: usize, -) { - let scene: &(dyn Hittable) = 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)| { - raytrace(buf, scene, camera, image, *row, max_depth); - }); -} - type SharedMaterial = Arc<Box<dyn Material>>; + +// TODO: Read from yml fn create_scene() -> Scene { let mut scene = Scene::new(); let material_ground: SharedMaterial = @@ -182,30 +73,75 @@ fn run( screen_width: usize, samples: usize, max_depth: usize, + recurse_depth: 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> = Box::new(create_scene()); + let image = image::Image::new(aspect_ratio, screen_width, samples); + let camera = Arc::new(RwLock::new(Camera::new(&image, 2.0, 1.0))); + let scene: Arc<Box<dyn Hittable>> = Arc::new(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(()))); + let sub_image: SubImage = (&image).into(); + let move_camera = Arc::clone(&camera); + + let render_image = Arc::new(SignalEvent::manual(false)); + let window_render_image = Arc::clone(&render_image); + + let cancel_render = Arc::new(SignalEvent::manual(false)); + let window_cancel_render = cancel_render.clone(); + + let exit = Arc::new(SignalEvent::manual(false)); + let window_exit = Arc::clone(&exit); rayon::scope(|s| { s.spawn(|_| { - let render_time = Instant::now(); - render( - Arc::clone(&screen_buffer), - camera, - Arc::clone(&image), - scene, - max_depth, - ); - - println!( - "It took {} seconds to render the image.", - Instant::now().duration_since(render_time).as_secs() - ); + // TODO: Make configurable + let preview_scale = 4; + let preview_samples = 2; + let preview_max_depth = 4; + let preview_recurse_depth = 4; + + loop { + if exit.wait_timeout(Duration::from_secs(0)) { + return; + } + + if render_image.wait_timeout(Duration::from_secs(0)) && render_image.status() { + let render_time = Instant::now(); + let cancel_render_event = Arc::clone(&cancel_render); + render( + Arc::clone(&screen_buffer), + Arc::clone(&camera), + &sub_image, + Arc::clone(&scene), + samples, + 1, + max_depth, + recurse_depth, + Some(cancel_render_event), + ); + + println!( + "It took {} seconds to render the image.", + Instant::now().duration_since(render_time).as_millis() + ); + } else { + // Render preview + render( + Arc::clone(&screen_buffer), + Arc::clone(&camera), + &sub_image, + Arc::clone(&scene), + preview_samples, + preview_scale, + preview_max_depth, + // TODO: Could create a function to create the optimal value + preview_recurse_depth, //recursive thread depth + None, + ); + } + } }); s.spawn(|_| { let result = Window::new( @@ -220,9 +156,37 @@ fn run( window }) .and_then(|mut window| { + let mut t = Instant::now(); while window.is_open() && !window.is_key_down(Key::Escape) { + let dt = t.elapsed().as_micros() as f64 / 1000000.0; + t = Instant::now(); // Sleep a bit to not hog the lock on the buffer all the time. - std::thread::sleep(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_millis(10)); + + if window.is_key_released(Key::R) { + if window_render_image.status() { + window_cancel_render.signal(); + window_render_image.reset(); + } else { + window_render_image.signal(); + window_cancel_render.reset(); + } + } + + { + let mut cam = move_camera.write().expect("TODO"); + if window.is_key_down(Key::W) { + cam.go_forward(-dt); + } else if window.is_key_down(Key::S) { + cam.go_forward(dt); + } + + if window.is_key_down(Key::A) { + cam.go_right(-dt); + } else if window.is_key_down(Key::D) { + cam.go_right(dt); + } + } screen_buffer .read() @@ -233,6 +197,7 @@ fn run( .map_err(|e| TracerError::FailedToUpdateWindow(e.to_string())) })? } + window_exit.signal(); Ok(()) }); @@ -248,7 +213,11 @@ fn run( } fn main() { - if let Err(e) = run(16.0 / 9.0, 1200, 1000, 50) { + // TODO: Read configuration and args + let samples = 1000; // Samples per pixel + let max_depth = 50; // Max ray trace depth + let recurse_depth = 4; // How many times the screen with split itself into sub images each time splitting it into 4 new smaller ones. + if let Err(e) = run(16.0 / 9.0, 1280, samples, max_depth, recurse_depth) { eprintln!("{}", e); std::process::exit(e.into()) } diff --git a/racer-tracer/src/render.rs b/racer-tracer/src/render.rs new file mode 100644 index 0000000..9d029e3 --- /dev/null +++ b/racer-tracer/src/render.rs @@ -0,0 +1,177 @@ +use std::{ + borrow::Borrow, + sync::{Arc, RwLock}, + time::Duration, +}; + +use rayon::prelude::*; +use synchronoise::SignalEvent; + +use crate::{ + camera::Camera, + geometry::Hittable, + image::{QuadSplit, SubImage}, + ray::Ray, + util::random_double, + vec3::{Color, Vec3}, +}; + +fn ray_color(scene: &dyn Hittable, ray: &Ray, depth: usize) -> Vec3 { + if depth == 0 { + return Vec3::default(); + } + + if let Some(rec) = scene.hit(ray, 0.001, std::f64::INFINITY) { + if let Some((scattered, attenuation)) = rec.material.scatter(ray, &rec) { + return attenuation * ray_color(scene, &scattered, depth - 1); + } + return Color::default(); + //let target = rec.point + random_in_hemisphere(&rec.normal); + //let target = rec.point + rec.normal + random_unit_vector(); + //return 0.5 * ray_color(scene, &Ray::new(rec.point, target - rec.point), depth - 1); + //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) +} + +pub fn raytrace( + buffer: &RwLock<Vec<u32>>, + cancel_event: Option<Arc<SignalEvent>>, + scene: &dyn Hittable, + camera: &Camera, + image: &SubImage, + samples: usize, + scale: usize, + max_depth: usize, +) { + let mut scaled_width = image.width / scale; + let mut scaled_height = image.height / scale; + // In the case where we get an odd one out we patch the widht and + // height with the esception of the edges of the screen. Without + // this everything has to be power of 2 which isn't a crazy + // asumption. + // + // Biggest problem is that the width and height we get here is + // depending on resolution and how many times the image is split + // up between threads. + if scaled_width * scale != image.width + && (image.x + scaled_width * scale + 1 < image.screen_width) + { + scaled_width += 1; + } + + if scaled_width * scale != image.height + && (image.y + scaled_height * scale + 1 < image.screen_height) + { + scaled_height += 1; + } + + let scaled_screen_width = image.screen_width / scale; + let scaled_screen_height = image.screen_height / scale; + let mut colors: Vec<Vec3> = vec![Vec3::default(); scaled_height * scaled_width as usize]; + for row in 0..scaled_height { + for column in 0..scaled_width { + let u: f64 = ((image.x / scale + column) as f64 + random_double()) + / (scaled_screen_width - 1) as f64; + for _ in 0..samples { + let v: f64 = ((image.y / scale + row) as f64 + random_double()) + / (scaled_screen_height - 1) as f64; + colors[row * scaled_width + column].add(ray_color( + scene, + &camera.get_ray(u, v), + max_depth, + )); + } + } + + if do_cancel(&cancel_event) { + return; + } + } + + if do_cancel(&cancel_event) { + return; + } + + let mut buf = buffer + .write() + .expect("Failed to get write guard when flushing data."); + + let offset = image.y * image.screen_width + image.x; + for half_row in 0..scaled_height { + for half_col in 0..scaled_width { + let color = colors[half_row * scaled_width + half_col] + .scale_sqrt(samples) + .as_color(); + + let row = half_row * scale; + let col = half_col * scale; + + for scale_x in 0..scale { + for scale_y in 0..scale { + buf[offset + (row + scale_x) * image.screen_width + col + scale_y] = color; + } + } + } + } +} + +fn do_cancel(cancel_event: &Option<Arc<SignalEvent>>) -> bool { + match cancel_event { + Some(event) => event.wait_timeout(Duration::from_secs(0)), + None => false, + } +} + +pub fn render( + buffer: Arc<RwLock<Vec<u32>>>, + camera: Arc<RwLock<Camera>>, + image: &SubImage, + scene: Arc<Box<dyn Hittable>>, + samples: usize, + scale: usize, + max_depth: usize, + split_depth: usize, + cancel_event: Option<Arc<SignalEvent>>, +) { + if do_cancel(&cancel_event) { + return; + } + + if split_depth == 0 { + let scene: &(dyn Hittable) = (*scene).borrow(); + let camera = { camera.read().expect("TODO").clone() }; + raytrace( + &buffer, + cancel_event, + scene, + &camera, + image, + samples, + scale, + max_depth, + ); + } else { + // Split into more quads + let quads = image.quad_split(); + quads.into_par_iter().for_each(|image| { + render( + Arc::clone(&buffer), + Arc::clone(&camera), + &image, + Arc::clone(&scene), + samples, + scale, + max_depth, + split_depth - 1, + cancel_event.clone(), + ); + }); + } +} |
