propper multithreading

This commit is contained in:
STP
2023-12-03 22:12:46 -05:00
parent d8488f24f7
commit 0eff7fc694
10 changed files with 391 additions and 201 deletions

View File

@@ -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);

32
rhai/space.rhai Normal file
View File

@@ -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

View File

@@ -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);

View File

@@ -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::<Mesh>()
.register_fn("Mesh", Mesh::from_file);
// engine
// .register_type::<Rectangle>()
// .register_fn("Rectange", Rectangle::new)
// .register_fn("RectangleUnit", Rectangle::unit);
engine
.register_type::<RectangleXY>()
.register_fn("Rectange", RectangleXY::new)
.register_fn("RectangleUnit", RectangleXY::unit);
engine
}

View File

@@ -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");
let args: Vec<String> = 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::<f64>().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<Scene> = 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<f32> = 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::<f64>() - 0.5) / options.ray_randomness;
let ry = (random::<f64>() - 0.5) / options.ray_randomness;
let rz = (random::<f64>() - 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<E: Error + 'static>(method_name: &str, err: E) {

View File

@@ -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<Intersection> {
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();
}
}

View File

@@ -373,85 +373,51 @@ impl Primitive for Cone {
}
// RECTANGLE -----------------------------------------------------------------
// #[derive(Clone)]
// pub struct Rectangle {
// position: Point3<f64>,
// normal: Vector3<f64>,
// width_direction: Vector3<f64>,
// 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<f64>,
tr: Point3<f64>,
}
// impl Rectangle {
// pub fn new(
// position: Point3<f64>,
// normal: Vector3<f64>,
// width_direction: Vector3<f64>,
// width: f64,
// height: f64,
// ) -> Arc<dyn Primitive> {
// 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<dyn Primitive> {
// 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<f64>, tr: Point3<f64>) -> Arc<dyn Primitive> {
Arc::new(RectangleXY { bl, tr })
}
pub fn unit() -> Arc<dyn Primitive> {
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<Intersection> {
// 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<Intersection> {
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<Triangle>) -> Arc<dyn Primitive> {
// 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 })
}

View File

@@ -18,14 +18,9 @@ pub struct Intersection {
}
//Intersection point including point and normal
impl Intersection {
pub fn transform(&self, trans: &Matrix4<f64>, inv_trans: &Matrix4<f64>) -> 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<f64>, inv_trans: &Matrix4<f64>) {
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<f64>) {
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,
}

View File

@@ -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<HashMap<String, Node>>,

View File

@@ -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<Mutex<Pixels>>,
pixels: Pixels,
gui: Gui,
rays: Arc<Vec<Ray>>,
ray_queue: Arc<Mutex<Vec<usize>>>,
ray_queue: Vec<usize>,
raytracing_options: Arc<RaytracingOption>,
}
@@ -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<u32>) -> Result<(), Box<dyn Error>> {
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,
None => break,
};
//Create a nre thread for this pixel
let handle = thread::spawn({
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();
let rays = rays.clone();
let pixels_mutex = self.pixels.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,
}
}
//The finished queue of the thread
let mut finished = vec![];
//Create a new thread for these pixels
let handle = thread::spawn({
move || {
//Shade colour for selected ray
for index in &load {
//Shade colour for selected index
let mut colour: Vector3<f32> = 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::<f64>() - 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);
}
return (load, finished);
}
});
handles.push(handle);
}
let mut all_results = vec![];
for handle in handles.drain(..) {
handle.join().unwrap();
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<dyn Error>> {
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<usize> = (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<dyn Error>> {
@@ -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)?;