From 0eff7fc694e0f565a3af87367fe3ffa5894356d0 Mon Sep 17 00:00:00 2001 From: STP Date: Sun, 3 Dec 2023 22:12:46 -0500 Subject: [PATCH] propper multithreading --- rhai/scene.rhai | 58 +++++++++--------- rhai/space.rhai | 32 ++++++++++ src/bvh.rs | 13 ++--- src/gui.rs | 30 ++++++---- src/main.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++- src/node.rs | 20 ++++++- src/primitive.rs | 116 +++++++++++++----------------------- src/ray.rs | 55 +++++++++-------- src/scene.rs | 2 +- src/state.rs | 117 +++++++++++++++++++++---------------- 10 files changed, 391 insertions(+), 201 deletions(-) create mode 100644 rhai/space.rhai diff --git a/rhai/scene.rhai b/rhai/scene.rhai index 11cdbac..5f63b46 100644 --- a/rhai/scene.rhai +++ b/rhai/scene.rhai @@ -3,41 +3,43 @@ let scene = Scene(); let distance = 10.0; let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); scene.addCamera("+Z Cam", camera); -let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); -scene.addCamera("+Y Cam", camera); -let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); -scene.addCamera("+X Cam", camera); -let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); -scene.addCamera("-Z Cam", camera); -let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); -scene.addCamera("-Y Cam", camera); -let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); -scene.addCamera("-X Cam", camera); +// let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); +// scene.addCamera("+Y Cam", camera); +// let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); +// scene.addCamera("+X Cam", camera); +// let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); +// scene.addCamera("-Z Cam", camera); +// let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); +// scene.addCamera("-Y Cam", camera); +// let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); +// scene.addCamera("-X Cam", camera); let material = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 10.0); -scene.addMaterial("bluegreen", material); +scene.addMaterial("mat1", material); let material2 = Material(V(0.2,0.9,0.8), V(0.3, 0.8, 0.8), 25.0); -scene.addMaterial("bluegreen2", material); +scene.addMaterial("mat2", material); let light = Light(P(0.0,7.0,0.0), V(0.0,0.0,1.0), V(0.1, 0.01, 0.001)); -light.active(false); +light.active(true); scene.addLight("blue", light); -let light = Light( P(2.0,7.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); -light.active(false); -scene.addLight("green", light); +// let light = Light( P(2.0,7.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); +// light.active(false); +// scene.addLight("green", light); -let light = Light( P(2.0,7.0,2.0), V(1.0,0.5,0.5), V(0.0, 0.00, 0.001)); -scene.addLight("red", light); +// let light = Light( P(2.0,7.0,2.0), V(1.0,0.5,0.5), V(0.0, 0.00, 0.001)); +// light.active(false); +// scene.addLight("red", light); -let light = Ambient(V(0.5,0.5,0.5)); -scene.addLight("ambient", light); +// let light = Ambient(V(0.5,0.5,0.5)); +// light.active(false); +// scene.addLight("ambient", light); -let sphere = Sphere(P(0.0,-10.0,0.0), 10.0 ); -let sphere_node = Node( sphere, material); -scene.addNode("sphere",sphere_node); +//let sphere = Sphere(P(0.0,-10.0,0.0), 10.0 ); +//let sphere_node = Node( sphere, material); +//scene.addNode("sphere",sphere_node); //let mesh = Mesh("obj/cow.obj" ); //let mesh_node = Node(mesh); @@ -48,11 +50,11 @@ scene.addNode("sphere",sphere_node); // child.translate(V(1.0,1.0,1.0)); //scene.addNode(child); -let cube = CubeUnit(); -let cube_node = Node( cube, material2); -cube_node.rotate(0.1,0.1,45.0); -cube_node.translate(0.0,1.0,0.0); -scene.addNode("cube", cube_node); +let sphere2= SphereUnit(); +let sphere2_node = Node( sphere2, material2); +// sphere2_node.rotate(0.1,0.1,0.0); +// sphere2_node.translate(0.0,1.0,0.0); +scene.addNode("sphere", sphere2_node); //let gnonom = Gnonom(); //let gnonom_node = Node(gnonom, material); diff --git a/rhai/space.rhai b/rhai/space.rhai new file mode 100644 index 0000000..10d666b --- /dev/null +++ b/rhai/space.rhai @@ -0,0 +1,32 @@ +let scene = Scene(); + +let falloff = V(0.0,0.0,0.0); + +//CAMERAS +let camera = Camera( P(100.0,100.0,100.0), P(500.0,500.0,500.0), V(0.0,1.0,0.0)); +scene.addCamera("Main Camera", camera); + +//Light for the sun +let light = Light(P(800.0, 800.0, 250.0), V(1.0, 1.0, 0.929), falloff); +scene.addLight("Sun", light); + +//Ball for the sun +let material = Material(V(1.0, 1.0, 0.9), V(0.9, 0.9, 0.9), 10.0); +scene.addMaterial("material_sun", material); +let sphere = Sphere(P(800.0, 800.0, 200.0), 50.0); +let sphere_node = Node(sphere, material); + scene.addNode("sphere", sphere_node); + +//Ball for the planet +let material = Material(V(0.2,0.8,0.2), V(0.2, 0.8, 0.8), 10.0); +scene.addMaterial("material_planet", material); +let sphere = Sphere(P(500.0, 500.0, 500.0), 50.0); +let sphere_node = Node(sphere, material); + scene.addNode("sphere", sphere_node); + + +// let sphere = Steiner( material); +// let sphere_node = Node(sphere); +// scene.addNode("sphere", sphere_node); + +scene \ No newline at end of file diff --git a/src/bvh.rs b/src/bvh.rs index 7a07170..4f287e1 100644 --- a/src/bvh.rs +++ b/src/bvh.rs @@ -1,5 +1,5 @@ use crate::{node::Node, ray::*, EPSILON}; -use nalgebra::{distance, point, Matrix4, Point3, Vector3}; +use nalgebra::{distance, Matrix4, Point3, Vector3}; use std::collections::HashMap; use std::fmt; @@ -386,24 +386,21 @@ impl BVH { // Traverse the BVH, 0 will be needed to start at root node pub fn traverse(&self, ray: &Ray, idx: usize) -> Option<(&Node, Intersection)> { let bvh_node = &self.bvh_nodes[idx]; - if !bvh_node.aabb.intersect_ray(ray) { + if !bvh_node.aabb.intersect_ray(&ray) { // No intersection with BVH in world coordinates return None; } - if bvh_node.prim_count > 0 { + if bvh_node.prim_count != 0 { // Leaf node intersection let node_idx = bvh_node.first_prim; let node = &self.nodes[node_idx]; if !node.active { return None; } - let ray = ray.transform(&node.inv_model); //Transform ray to model coords - if let Some(intersect) = node.primitive.intersect_ray(&ray) { + if let Some(intersect) = node.intersect_ray(&ray) { if intersect.distance < EPSILON { return None; } else { - // Convert intersect back to world coords - let intersect = intersect.transform(&node.model, &node.inv_model); return Some((node, intersect)); } } @@ -438,7 +435,7 @@ impl BVH { let mut l_count = 0; let mut r_count = 0; for i in 0..node.prim_count { - let aabb = self.nodes[node.first_prim + i].primitive.get_aabb(); + let aabb = self.nodes[node.first_prim + i].get_world_aabb(); if aabb.trf[axis] < pos { l_count += 1; l_aabb.grow_mut(&aabb.trf); diff --git a/src/gui.rs b/src/gui.rs index e3c8ccf..39f55d7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -18,18 +18,20 @@ const BUFFER_PROPORTION_MIN: f32 = 0.1; const BUFFER_PROPORTION_MAX: f32 = 1.0; //RAY CONSTANTS +const MIN_THREADS: u32 = 1; +const MAX_THREADS: u32 = 12; const RAYS_MIN: u32 = 100; const RAYS_MAX: u32 = 10000; -const MIN_DEPTH: u8 = 5; -const MAX_DEPTH: u8 = 100; -const MIN_SAMPLES: u32 = 5; -const MAX_SAMPLES: u32 = 100; +const MIN_DEPTH: u8 = 1; +const MAX_DEPTH: u8 = 10; +const MIN_SAMPLES: u32 = 1; +const MAX_SAMPLES: u32 = 10; const MIN_RANDOM: f64 = 100.0; const MAX_RANDOM: f64 = 1000.0; //DIFFUSE CONSTANTS -const MIN_DIFFUSE_RAYS: u8 = 5; -const MAX_DIFFUSE_RAYS: u8 = 100; +const MIN_DIFFUSE_RAYS: u8 = 1; +const MAX_DIFFUSE_RAYS: u8 = 10; const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0; const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0; @@ -205,12 +207,18 @@ impl Gui { //Raytracing options ------------------------------------------- if CollapsingHeader::new("Raytracer").build(ui) { + ui.slider( + "Threads", + MIN_THREADS, + MAX_THREADS, + &mut self.raytracing_option.threads, + ); // Numbers of rays to render per pass ui.slider( "Rays Per Pass", RAYS_MIN, RAYS_MAX, - &mut self.raytracing_option.pixels_per_pass, + &mut self.raytracing_option.pixels_per_thread, ); // Proportion of the window the buffer occupies ui.slider( @@ -521,9 +529,9 @@ pub fn init_engine() -> Engine { engine .register_type::() .register_fn("Mesh", Mesh::from_file); - // engine - // .register_type::() - // .register_fn("Rectange", Rectangle::new) - // .register_fn("RectangleUnit", Rectangle::unit); + engine + .register_type::() + .register_fn("Rectange", RectangleXY::new) + .register_fn("RectangleUnit", RectangleXY::unit); engine } diff --git a/src/main.rs b/src/main.rs index c5f59a2..4590869 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,23 @@ use crate::state::run; +use bvh::BVH; +use camera::Camera; use error_iter::ErrorIter; const EPSILON: f64 = 1e-8; const INFINITY: f64 = 1e10; +use gui::{init_engine, Gui}; use log::error; +use nalgebra::Vector3; +use rand::random; +use ray::Ray; +use scene::Scene; +use state::RaytracingOption; use std::env; use std::error::Error; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; mod bvh; mod camera; @@ -21,11 +32,143 @@ mod state; fn main() { env_logger::init(); - env::set_var("RUST_BACKTRACE", "1"); - if let Err(e) = run() { - println!("Error at runtime: {}", e); + let args: Vec = env::args().collect(); + + if args.len() == 6 { + let width: usize = args[1].parse().unwrap(); + let height: usize = args[2].parse().unwrap(); + let fovy = args[3].parse::().unwrap(); + let filename = &args[4]; + let savefile = &args[5]; + headless( + width, + height, + fovy, + filename.to_string(), + savefile.to_string(), + ); + } else { + if let Err(e) = run() { + println!("Error at runtime: {}", e); + }; + } +} + +fn headless(width: usize, height: usize, fovy: f64, filename: String, savefile: String) { + let options = Arc::new(RaytracingOption { + threads: 12, + ray_samples: 1, + ray_randomness: 100.0, + clear_color: [0x22, 0x00, 0x11, 0x55], + pixel_clear: [0x55, 0x00, 0x22, 0x55], + pixels_per_thread: 200, + buffer_proportion: 1.0, + buffer_fov: 110.0, + ray_depth: 5, + diffuse_rays: 3, + diffuse_coefficient: 0.8, + bvh_active: false, + }); + //Read script from file + let script = match std::fs::read_to_string(&filename) { + Ok(in_script) => in_script, + Err(e) => { + println!("{}", e); + return; + } }; + //Evaluate scene in file + let engine = init_engine(); + let scene: Arc = match engine.eval(&script) { + Ok(in_scene) => Arc::new(in_scene), + Err(e) => { + println!("{e}"); + return; + } + }; + //Set the camera + let mut camera = Camera::unit(); + for (_, in_camera) in &scene.cameras { + camera = in_camera.clone(); + } + //Cast the rays + let rays = Arc::new(Ray::cast_rays( + &camera.eye, + &camera.target, + &camera.up, + fovy, + width as u32, + height as u32, + )); + //Enable bounding volume heirarchy + let bvh; + match options.bvh_active { + true => bvh = Arc::new(Some(BVH::build(&scene.nodes))), + false => bvh = Arc::new(None), + } + //Create our frame and indexer + let size = width * height; + let frame_mutex = Arc::new(Mutex::new(vec![0; size * 4])); + //Multithreading + let mut handles = vec![]; + + for index in 0..size { + for _ in 0..options.threads { + //Get random index from queue + //Create a nre thread for this pixel + let handle = thread::spawn({ + let rays = rays.clone(); + let scene = scene.clone(); + let options = options.clone(); + let bvh = bvh.clone(); + let rays = rays.clone(); + let frame_mutex = frame_mutex.clone(); + move || { + //Shade colour for selected ray + let mut colour: Vector3 = Vector3::zeros(); + //Get the ray we want to make + let shot_ray = &rays[index]; + //Send out ray_samples rays + for _ in 0..options.ray_samples { + let point = shot_ray.a; + let dir = shot_ray.b; + //Generate a random ray + let rx = (random::() - 0.5) / options.ray_randomness; + let ry = (random::() - 0.5) / options.ray_randomness; + let rz = (random::() - 0.5) / options.ray_randomness; + let nx = dir.x + rx; + let ny = dir.y + ry; + let nz = dir.z + rz; + let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz)); + + if let Some(ray_colour) = rand_ray.shade_ray(&scene, 0, &options, &bvh) { + colour += ray_colour; + } + } + colour = (colour / options.ray_samples as f32) * 255.0; + let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; + { + let frame = &mut frame_mutex.lock().unwrap(); + frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); + } + } + }); + handles.push(handle); + } + for handle in handles.drain(..) { + handle.join().unwrap(); + } + } + use std::path::Path; + image::save_buffer( + Path::new(&savefile), + &frame_mutex.lock().unwrap(), + width as u32, + height as u32, + image::ColorType::Rgba8, + ) + .unwrap(); } fn log_error(method_name: &str, err: E) { diff --git a/src/node.rs b/src/node.rs index 4f5ec80..8e28a4b 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,4 +1,9 @@ -use crate::{bvh::AABB, material::Material, primitive::*}; +use crate::{ + bvh::AABB, + material::Material, + primitive::{self, *}, + ray::{Intersection, Ray}, +}; use nalgebra::{Matrix4, Vector3}; use std::sync::Arc; @@ -98,4 +103,17 @@ impl Node { self.inv_model = self.model.try_inverse().unwrap(); self.aabb.transform_mut(&self.model); } + // Intersection of a ray, will convert to model coords and check + pub fn intersect_ray(&self, ray: &Ray) -> Option { + let ray = ray.transform(&self.inv_model); //Transform from world coordinates + if let Some(mut intersect) = self.primitive.intersect_ray(&ray) { + intersect.transform_mut(&self.model, &self.inv_model); //Transform to world coords + return Some(intersect); + } + return None; + } + //Gets the bounding box in world coords + pub fn get_world_aabb(&self) -> AABB { + return self.aabb.clone(); + } } diff --git a/src/primitive.rs b/src/primitive.rs index 059945a..b4569a8 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -373,85 +373,51 @@ impl Primitive for Cone { } // RECTANGLE ----------------------------------------------------------------- -// #[derive(Clone)] -// pub struct Rectangle { -// position: Point3, -// normal: Vector3, -// width_direction: Vector3, -// width: f64, -// height: f64, -// } +// Normal is (0.0, 0.0, 1.0) always facing towards camera at positive z axis +#[derive(Clone)] +pub struct RectangleXY { + bl: Point3, + tr: Point3, +} -// impl Rectangle { -// pub fn new( -// position: Point3, -// normal: Vector3, -// width_direction: Vector3, -// width: f64, -// height: f64, -// ) -> Arc { -// let normal = normal.normalize(); -// let width_direction = width_direction.normalize(); -// let height_direction = width_direction.cross(&normal); -// Arc::new(Rectangle { -// position, -// normal: normal.normalize(), -// width_direction: width_direction.normalize(), -// width, -// height, -// }) -// } -// pub fn unit() -> Arc { -// Rectangle::new( -// Point3::new(0.0, 0.0, 0.0), -// Vector3::new(0.0, 1.0, 0.0), -// Vector3::new(1.0, 0.0, 0.0), -// 2.0, -// 2.0, -// ) -// } -// } +impl RectangleXY { + pub fn new(bl: Point3, tr: Point3) -> Arc { + Arc::new(RectangleXY { bl, tr }) + } + pub fn unit() -> Arc { + RectangleXY::new(Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, 1.0, 0.0)) + } +} -// impl Primitive for Rectangle { -// fn intersect_ray(&self, ray: &Ray) -> Option { -// let constant = self.position.coords.dot(&self.normal); -// let denominator = ray.b.dot(&self.normal); -// let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; +impl Primitive for RectangleXY { + fn intersect_ray(&self, ray: &Ray) -> Option { + let z = self.bl.z; + let az = ray.a.z; + let bz = ray.b.z; + let t = (z - az) / bz; + if t > INFINITY { + return None; + } + let intersect = ray.at_t(t); + let (ix, iy) = (intersect.x, intersect.y); -// if t > INFINITY { -// return None; -// } + if (ix < self.bl.x) || (ix > self.tr.x) || (iy < self.bl.y) || (iy > self.tr.y) { + return None; + } -// let intersect = ray.at_t(t); -// let height_direction = self.width_direction.cross(&self.normal); -// let (w2, h2) = (self.width / 2.0, self.height / 2.0); -// let r1 = w2 * self.width_direction; -// let r2 = h2 * height_direction; -// let pi = intersect - self.position; -// let pi_dot_r1 = pi.dot(&r1); -// let pi_dot_r2 = pi.dot(&r2); + Some(Intersection { + point: intersect, + normal: Vector3::new(0.0, 0.0, 1.0), + distance: t, + }) + } -// if pi_dot_r1 >= -w2 && pi_dot_r1 <= w2 && pi_dot_r2 >= -h2 && pi_dot_r2 <= h2 { -// return Some(Intersection { -// point: intersect, -// normal: self.normal, -// distance: t, -// }); -// } -// None -// } - -// fn get_bounding_box(&self) -> AABB { -// let position = self.position; -// let width = self.width; -// let height = self.height; -// let width_direction = self.width_direction; -// let bln = position - width / 2.0 * width_direction - height / 2.0 * height_direction; -// let trf = position + width / 2.0 * width_direction + height / 2.0 * height_direction; -// AABB::new(bln, trf); -// todo!() -// } -// } + fn get_aabb(&self) -> AABB { + let bl = self.bl + Vector3::new(0.0, 0.0, -0.1); + let tr = self.tr + Vector3::new(0.0, 0.0, 0.1); + AABB::new(bl, tr) + } +} // Cube ----------------------------------------------------------------- #[derive(Clone)] @@ -623,7 +589,7 @@ pub struct Mesh { impl Mesh { pub fn new(triangles: Vec) -> Arc { // Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles - let bounding_box = Mesh::compute_bounding_box(&triangles); + let _bounding_box = Mesh::compute_bounding_box(&triangles); Arc::new(Mesh { triangles }) } diff --git a/src/ray.rs b/src/ray.rs index 2adcaab..ffe5f7c 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -18,14 +18,9 @@ pub struct Intersection { } //Intersection point including point and normal impl Intersection { - pub fn transform(&self, trans: &Matrix4, inv_trans: &Matrix4) -> Intersection { - let point = trans.transform_point(&self.point); - let normal = inv_trans.transpose().transform_vector(&self.normal); - Intersection { - point, - normal, - distance: self.distance, - } + pub fn transform_mut(&mut self, trans: &Matrix4, inv_trans: &Matrix4) { + self.point = trans.transform_point(&self.point); + self.normal = inv_trans.transpose().transform_vector(&self.normal); } } @@ -62,15 +57,20 @@ impl Ray { b: trans.transform_vector(&self.b), } } + //Transform mutably + pub fn transform_mut(&mut self, trans: &Matrix4) { + self.a = trans.transform_point(&self.a); + self.b = trans.transform_vector(&self.b); + } //This function will determine if the ray hits an object in the scene //This is not optimised as it does not include bounding boxes - pub fn hit_scene(&self, scene: &Scene) -> bool { + pub fn hit_scene(ray: &Ray, scene: &Scene) -> bool { for (_, node) in &scene.nodes { if !node.active { continue; } // Transform ray into local model cordinates - let ray = self.transform(&node.inv_model); + let ray = ray.transform(&node.inv_model); if node.primitive.intersect_ray(&ray).is_some() { return true; } @@ -79,26 +79,31 @@ impl Ray { } //This function find the closest intersection point of a ray with an object in the scene //Also not optimised, as it does not include bounding boxes - pub fn closest_intersect<'a>(&'a self, scene: &'a Scene) -> Option<(&Node, Intersection)> { + pub fn closest_intersect<'a>( + ray: &'a Ray, + scene: &'a Scene, + ) -> Option<(&'a Node, Intersection)> { let mut closest_distance = f64::MAX; let mut closest_intersect: Option<(&Node, Intersection)> = None; + let ray_a = ray.a; for (_, node) in &scene.nodes { + //position of ray in world coords if !node.active { continue; } - if node.aabb.intersect_ray(&self) { - // Transform ray into local model cordinates - let ray = self.transform(&node.inv_model); + if node.aabb.intersect_ray(&ray) { + // Transform ray into model cordinates + let ray = ray.transform(&node.inv_model); // Check primitive intersection - if let Some(intersect) = node.primitive.intersect_ray(&ray) { - // Dont intersect with itself + if let Some(mut intersect) = node.primitive.intersect_ray(&ray) { + // Dont intersect with own primitive if intersect.distance < EPSILON { continue; } // Check for closest distance by converting to world coords - let intersect = intersect.transform(&node.model, &node.inv_model); - let distance = distance(&ray.a, &intersect.point); + intersect.transform_mut(&node.model, &node.inv_model); + let distance = distance(&ray_a, &intersect.point); if distance < closest_distance { closest_distance = distance; closest_intersect = Some((node, intersect)); @@ -124,7 +129,7 @@ impl Ray { //We have a bvh so use bvh traversal Some(bvh) => { //Intersect the scene with the bvh - if let Some((node, intersect)) = bvh.traverse(&self, 0) { + if let Some((node, intersect)) = bvh.traverse(self, 0) { return Some(Ray::phong_shade_point( &scene, &self, &node, &intersect, depth, options, sbvh, )); @@ -134,7 +139,7 @@ impl Ray { //We dont have a bvh so use generic algorithm None => { //No BVH given so intersect normally - match self.closest_intersect(scene) { + match Ray::closest_intersect(self, scene) { Some((node, intersect)) => { Some(Ray::phong_shade_point( &scene, &self, &node, &intersect, depth, options, sbvh, @@ -180,10 +185,10 @@ impl Ray { let to_light = to_light.normalize(); //Niave Shadows - let to_light_ray = Ray::new(point, to_light); - if to_light_ray.light_blocked(scene, node, bvh) { - continue; - } + // let to_light_ray = Ray::new(point, to_light); + // if to_light_ray.light_blocked(scene, node, bvh) { + // continue; + // } let n_dot_l = normal.dot(&to_light).max(0.0) as f32; @@ -235,7 +240,7 @@ impl Ray { if !node.active { continue; } - match bvh.traverse(&self, 0) { + match bvh.traverse(self, 0) { Some(_) => return true, None => continue, } diff --git a/src/scene.rs b/src/scene.rs index af35fc2..9ff0a41 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,6 +1,6 @@ use crate::{camera::Camera, light::Light, material::*, node::*}; use std::collections::HashMap; -use std::rc::Rc; + // pub struct MultiThreadScene { // pub nodes: Rc>, diff --git a/src/state.rs b/src/state.rs index 4feb55d..9e2dc5b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,7 +14,6 @@ use rand::{random, thread_rng}; use std::error::Error; use std::sync::Arc; -use std::sync::Mutex; use anyhow::Result; use pixels::{Pixels, SurfaceTexture}; @@ -36,7 +35,7 @@ pub struct RaytracingOption { pub ray_randomness: f64, pub clear_color: [u8; 4], pub pixel_clear: [u8; 4], - pub pixels_per_pass: u32, + pub pixels_per_thread: u32, pub buffer_proportion: f32, pub buffer_fov: f64, pub ray_depth: u8, @@ -48,16 +47,16 @@ impl RaytracingOption { pub fn default() -> RaytracingOption { RaytracingOption { threads: 12, - ray_samples: 10, + ray_samples: 1, ray_randomness: 100.0, clear_color: [0x22, 0x00, 0x11, 0x55], pixel_clear: [0x55, 0x00, 0x22, 0x55], - pixels_per_pass: 200, + pixels_per_thread: 200, buffer_proportion: 1.0, buffer_fov: 110.0, ray_depth: 5, - diffuse_rays: 5, - diffuse_coefficient: 0.5, + diffuse_rays: 3, + diffuse_coefficient: 0.8, bvh_active: false, } } @@ -72,11 +71,11 @@ pub struct State { buffer_width: u32, buffer_height: u32, - pixels: Arc>, + pixels: Pixels, gui: Gui, rays: Arc>, - ray_queue: Arc>>, + ray_queue: Vec, raytracing_options: Arc, } @@ -84,7 +83,7 @@ impl State { pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self { let scene = Arc::new(Scene::empty()); let window_size = window.inner_size(); - let pixels = Arc::new(Mutex::new(pixels)); + let pixels = pixels; let camera = Camera::unit(); let rays = Arc::new(Vec::new()); @@ -98,7 +97,7 @@ impl State { pixels, gui, rays, - ray_queue: Arc::new(Mutex::new(Vec::new())), + ray_queue: Vec::new(), raytracing_options: Arc::new(RaytracingOption::default()), } } @@ -133,8 +132,7 @@ impl State { self.reset_queue(); } GuiEvent::SaveImage(filename) => { - let pixels = &self.pixels.as_ref().lock().unwrap(); - let frame = pixels.frame(); + let frame = self.pixels.frame(); image::save_buffer( Path::new(&filename), frame, @@ -171,16 +169,15 @@ impl State { )); // Resize buffer and surface - let pixels = &mut self.pixels.as_ref().lock().unwrap(); - pixels.resize_surface(size.width, size.height)?; - pixels.resize_buffer(self.buffer_width, self.buffer_height)?; + self.pixels.resize_surface(size.width, size.height)?; + self.pixels + .resize_buffer(self.buffer_width, self.buffer_height)?; Ok(()) } fn resize(&mut self, size: &PhysicalSize) -> Result<(), Box> { - let pixels = &mut self.pixels.as_ref().lock().unwrap(); - pixels.resize_surface(size.width, size.height)?; + self.pixels.resize_surface(size.width, size.height)?; Ok(()) } @@ -199,29 +196,38 @@ impl State { let randomness = self.raytracing_options.ray_randomness; let samples = self.raytracing_options.ray_samples; let samples_f32 = samples as f32; + + let num_threads = self.raytracing_options.threads; + let pixels_per_thread = self.raytracing_options.pixels_per_thread; + let mut handles = vec![]; - for _ in 0..self.raytracing_options.pixels_per_pass { - for _ in 0..self.raytracing_options.threads { - //Get random index from queue - let queue = &mut self.ray_queue.clone(); - let index = match queue.lock().unwrap().pop() { - Some(index) => index, + for _ in 0..num_threads { + //Get necessary variables to render + let rays = self.rays.clone(); + let scene = self.scene.clone(); + let options = self.raytracing_options.clone(); + let bvh = self.bvh.clone(); + + //Get the workload for a thread + let mut load = vec![]; + for _ in 0..pixels_per_thread { + match self.ray_queue.pop() { + Some(index) => load.push(index), None => break, - }; - //Create a nre thread for this pixel - let handle = thread::spawn({ - let rays = self.rays.clone(); - let scene = self.scene.clone(); - let options = self.raytracing_options.clone(); - let bvh = self.bvh.clone(); - let rays = rays.clone(); - let pixels_mutex = self.pixels.clone(); - move || { - //Shade colour for selected ray + } + } + //The finished queue of the thread + let mut finished = vec![]; + + //Create a new thread for these pixels + let handle = thread::spawn({ + move || { + for index in &load { + //Shade colour for selected index let mut colour: Vector3 = Vector3::zeros(); + let ray = &rays[*index]; for _ in 0..samples { - let ray = &rays[index]; let point = ray.a; let dir = ray.b; let rx = (random::() - 0.5) / randomness; @@ -240,23 +246,37 @@ impl State { } colour = (colour / samples_f32) * 255.0; let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; - let pixels = &mut pixels_mutex.lock().unwrap(); - let frame = pixels.frame_mut(); - frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); + finished.push(rgba); } - }); - handles.push(handle); - } - for handle in handles.drain(..) { - handle.join().unwrap(); - } + return (load, finished); + } + }); + handles.push(handle); + } + + let mut all_results = vec![]; + + for handle in handles.drain(..) { + let (load, finished) = handle + .join() + .map_err(|e| format!("Thread panicked: {:?}", e))?; + let thread_results: Vec<_> = load.into_iter().zip(finished.into_iter()).collect(); + all_results.extend(thread_results); + } + + //Now we have two vectors will all the indicies and rgba values, we can upload them to the bufer + + let frame = self.pixels.frame_mut(); + for result in all_results { + let index = result.0; + let rgba = result.1; + frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); } Ok(()) } fn clear_buffer(&mut self) -> Result<(), Box> { - let pixels = &mut self.pixels.as_ref().lock().unwrap(); - let frame = pixels.frame_mut(); + let frame = self.pixels.frame_mut(); for pixel in frame.chunks_exact_mut(4) { pixel.copy_from_slice(&self.raytracing_options.pixel_clear); } @@ -271,7 +291,7 @@ impl State { let size = self.buffer_height as usize * self.buffer_width as usize; let mut ray_queue: Vec = (0..size).collect(); ray_queue.shuffle(&mut thread_rng()); - self.ray_queue = Arc::new(Mutex::new(ray_queue)); + self.ray_queue = ray_queue; } fn render(&mut self) -> Result<(), Box> { @@ -289,8 +309,7 @@ impl State { .prepare(&self.window) .expect("gui.prepare() failed"); // Try to render pixels - let pixels = &mut self.pixels.as_ref().lock().unwrap(); - if let Err(e) = pixels.render_with(|encoder, render_target, context| { + if let Err(e) = self.pixels.render_with(|encoder, render_target, context| { context.scaling_renderer.render(encoder, render_target); // Render pixels self.gui .render(&self.window, encoder, render_target, context)?;