diff --git a/Cargo.lock b/Cargo.lock index dbb5948..e938e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "psychopath" version = "0.1.0" dependencies = [ + "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -17,6 +18,11 @@ dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "docopt" version = "0.6.80" diff --git a/Cargo.toml b/Cargo.toml index de51fa8..055d67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ docopt = "0.6" rustc-serialize = "0.3" nom = "1.2" scoped_threadpool = "0.1" +crossbeam = "0.2" num_cpus = "0.2" \ No newline at end of file diff --git a/src/image.rs b/src/image.rs index 3c5cb6c..85a2f45 100644 --- a/src/image.rs +++ b/src/image.rs @@ -7,14 +7,14 @@ use std::fs::File; #[derive(Debug, Clone)] pub struct Image { - data: Vec<(u8, u8, u8)>, + data: Vec<(f32, f32, f32)>, res: (usize, usize), } impl Image { pub fn new(width: usize, height: usize) -> Image { Image { - data: vec![(0,0,0); width * height], + data: vec![(0.0, 0.0, 0.0); width * height], res: (width, height), } } @@ -27,14 +27,14 @@ impl Image { self.res.1 } - pub fn get(&self, x: usize, y: usize) -> (u8, u8, u8) { + pub fn get(&self, x: usize, y: usize) -> (f32, f32, f32) { assert!(x < self.res.0); assert!(y < self.res.1); self.data[self.res.0 * y + x] } - pub fn set(&mut self, x: usize, y: usize, value: (u8, u8, u8)) { + pub fn set(&mut self, x: usize, y: usize, value: (f32, f32, f32)) { assert!(x < self.res.0); assert!(y < self.res.1); @@ -52,6 +52,9 @@ impl Image { for y in 0..self.res.1 { for x in 0..self.res.0 { let (r, g, b) = self.get(x, y); + let r = quantize_255(srgb_gamma(r)); + let g = quantize_255(srgb_gamma(g)); + let b = quantize_255(srgb_gamma(b)); try!(write!(f, "{} {} {} ", r, g, b)); } try!(write!(f, "\n")); @@ -72,6 +75,9 @@ impl Image { for y in 0..self.res.1 { for x in 0..self.res.0 { let (r, g, b) = self.get(x, y); + let r = quantize_255(srgb_gamma(r)); + let g = quantize_255(srgb_gamma(g)); + let b = quantize_255(srgb_gamma(b)); let d = [r, g, b]; try!(f.write_all(&d)); } @@ -81,3 +87,24 @@ impl Image { Ok(()) } } + +fn srgb_gamma(n: f32) -> f32 { + if n < 0.0031308 { + n * 12.92 + } else { + (1.055 * n.powf(1.0 / 2.4)) - 0.055 + } +} + +fn srgb_inv_gamma(n: f32) -> f32 { + if n < 0.04045 { + n / 12.92 + } else { + ((n + 0.055) / 1.055).powf(2.4) + } +} + +fn quantize_255(n: f32) -> u8 { + let n = 1.0f32.min(0.0f32.max(n)) * 255.0; + n as u8 +} diff --git a/src/main.rs b/src/main.rs index 3d752d4..809ae7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate rustc_serialize; extern crate docopt; extern crate scoped_threadpool; +extern crate crossbeam; extern crate num_cpus; #[macro_use] extern crate nom; diff --git a/src/parse/psy_mesh_surface.rs b/src/parse/psy_mesh_surface.rs index 1d5cced..f7f5324 100644 --- a/src/parse/psy_mesh_surface.rs +++ b/src/parse/psy_mesh_surface.rs @@ -2,7 +2,6 @@ use std::result::Result; -use nom; use nom::IResult; use super::DataTree; diff --git a/src/renderer.rs b/src/renderer.rs index 2705c9d..e9675d4 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,9 +1,12 @@ #![allow(dead_code)] use std::path::Path; -use std::sync::Mutex; +use std::cmp::min; +use std::iter::Iterator; +use std::sync::{Mutex, RwLock}; use std::cell::RefCell; use scoped_threadpool::Pool; +use crossbeam::sync::MsQueue; use tracer::Tracer; use halton; @@ -24,8 +27,15 @@ impl Renderer { pub fn render(&self, thread_count: u32) { let mut tpool = Pool::new(thread_count); - let img = Mutex::new(RefCell::new(Image::new(self.resolution.0, self.resolution.1))); + let image = Mutex::new(RefCell::new(Image::new(self.resolution.0, self.resolution.1))); + let (img_width, img_height) = { + let i = image.lock().unwrap(); + let w = i.borrow().width(); + let h = i.borrow().height(); + (w, h) + }; + let all_jobs_queued = RwLock::new(false); // Pre-calculate some useful values related to the image plane let cmpx = 1.0 / self.resolution.0 as f32; @@ -37,93 +47,138 @@ impl Renderer { let x_extent = max_x - min_x; let y_extent = max_y - min_y; + // Set up job queue + let job_queue = MsQueue::new(); // Render tpool.scoped(|scope| { - let (img_width, img_height) = { - let i = img.lock().unwrap(); - let w = i.borrow().width(); - let h = i.borrow().height(); - (w, h) - }; - for y in 0..img_height { - for x in 0..img_width { - let img = &img; - scope.execute(move || { - let mut rays = Vec::new(); - let mut tracer = Tracer::from_assembly(&self.scene.root); + // Spawn worker tasks + for _ in 0..thread_count { + let jq = &job_queue; + let ajq = &all_jobs_queued; + let img = ℑ + scope.execute(move || { + let mut rays = Vec::new(); + let mut pixel_mapping = Vec::new(); + let mut tracer = Tracer::from_assembly(&self.scene.root); - let offset = hash_u32(((x as u32) << 16) ^ (y as u32), 0); + loop { + rays.clear(); + pixel_mapping.clear(); + + // Get bucket, or exit if no more jobs left + let bucket: BucketJob; + loop { + if let Some(b) = jq.try_pop() { + bucket = b; + break; + } else { + if *ajq.read().unwrap() == true { + return; + } + } + } // Generate rays - rays.clear(); - for si in 0..self.spp { - let mut ray = { - let filter_x = - fast_logit(halton::sample(3, offset + si as u32), 1.5) + 0.5; - let filter_y = - fast_logit(halton::sample(4, offset + si as u32), 1.5) + 0.5; - let samp_x = (filter_x + x as f32) * cmpx; - let samp_y = (filter_y + y as f32) * cmpy; + for y in bucket.y..(bucket.y + bucket.h) { + for x in bucket.x..(bucket.x + bucket.w) { + let offset = hash_u32(((x as u32) << 16) ^ (y as u32), 0); + for si in 0..self.spp { + let mut ray = { + let filter_x = + fast_logit(halton::sample(3, offset + si as u32), 1.5) + + 0.5; + let filter_y = + fast_logit(halton::sample(4, offset + si as u32), 1.5) + + 0.5; + let samp_x = (filter_x + x as f32) * cmpx; + let samp_y = (filter_y + y as f32) * cmpy; - self.scene.camera.generate_ray((samp_x - 0.5) * x_extent, - (0.5 - samp_y) * y_extent, - halton::sample(0, - offset + si as u32), - halton::sample(1, - offset + si as u32), - halton::sample(2, - offset + si as u32)) - }; - ray.id = si as u32; - rays.push(ray); + self.scene + .camera + .generate_ray((samp_x - 0.5) * x_extent, + (0.5 - samp_y) * y_extent, + halton::sample(0, offset + si as u32), + halton::sample(1, offset + si as u32), + halton::sample(2, offset + si as u32)) + }; + ray.id = rays.len() as u32; + rays.push(ray); + pixel_mapping.push((x, y)) + } + } } // Test rays against scene let isects = tracer.trace(&rays); // Calculate color based on ray hits - let mut r = 0.0; - let mut g = 0.0; - let mut b = 0.0; - for isect in isects.iter() { + let img = img.lock().unwrap(); + let mut img = img.borrow_mut(); + for (isect, co) in Iterator::zip(isects.iter(), pixel_mapping.iter()) { + let mut col = img.get(co.0 as usize, co.1 as usize); if let &surface::SurfaceIntersection::Hit { t: _, pos: _, nor: _, space: _, uv } = isect { - r += uv.0; - g += uv.1; - b += (1.0 - uv.0 - uv.1).max(0.0); - } else { - r += 0.02; - g += 0.02; - b += 0.02; - } - } - r = 255.0 * srgb_gamma(r / self.spp as f32); - g = 255.0 * srgb_gamma(g / self.spp as f32); - b = 255.0 * srgb_gamma(b / self.spp as f32); - // Set pixel color - let img = img.lock().unwrap(); - img.borrow_mut().set(x, y, (r as u8, g as u8, b as u8)); - }); + col.0 += uv.0 / self.spp as f32; + col.1 += uv.1 / self.spp as f32; + col.2 += (1.0 - uv.0 - uv.1).max(0.0) / self.spp as f32; + + } else { + col.0 += 0.02 / self.spp as f32; + col.1 += 0.02 / self.spp as f32; + col.2 += 0.02 / self.spp as f32; + } + img.set(co.0 as usize, co.1 as usize, col); + } + } + }); + } + + // Populate job queue + let bucket_w = 16; + let bucket_h = 16; + for by in 0..((img_height / bucket_h) + 1) { + for bx in 0..((img_width / bucket_w) + 1) { + let x = bx * bucket_w; + let y = by * bucket_h; + let w = min(bucket_w, img_width - x); + let h = min(bucket_h, img_height - y); + if w > 0 && h > 0 { + job_queue.push(BucketJob { + x: x as u32, + y: y as u32, + w: w as u32, + h: h as u32, + }); + } } } - // scope.defer(|| println!("Exiting scope")); - // scope.spawn(|| println!("Running child thread in scope")) + + // Mark done queuing jobs + *all_jobs_queued.write().unwrap() = true; }); // Write rendered image to disk { - let img = &img.lock().unwrap(); + let img = &image.lock().unwrap(); let _ = img.borrow().write_binary_ppm(Path::new(&self.output_file)); } } } +#[derive(Debug)] +struct BucketJob { + x: u32, + y: u32, + w: u32, + h: u32, +} + fn hash_u32(n: u32, seed: u32) -> u32 { let mut hash = n; diff --git a/src/tracer.rs b/src/tracer.rs index ff2b8af..52a158a 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -1,10 +1,9 @@ use std::iter; -use std::slice; use std::cell::UnsafeCell; use math::{Matrix4x4, multiply_matrix_slices}; use lerp::lerp_slice; -use assembly::{Assembly, Object, Instance, InstanceType}; +use assembly::{Assembly, Object, InstanceType}; use ray::Ray; use surface::SurfaceIntersection; diff --git a/todo.txt b/todo.txt index 4907de2..517d2fc 100644 --- a/todo.txt +++ b/todo.txt @@ -1,3 +1,3 @@ //- Basic scene parsing with triangle meshes. -- Implement bucketed rendering. +//- Implement bucketed rendering. - Unit tests for scene parsing. \ No newline at end of file