Implemented bucketed rendering.

This commit is contained in:
Nathan Vegdahl 2016-06-05 01:27:55 -07:00
parent 4493afd85b
commit 5ec1f534cf
8 changed files with 154 additions and 66 deletions

6
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@
use std::result::Result;
use nom;
use nom::IResult;
use super::DataTree;

View File

@ -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 = &image;
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;

View File

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

View File

@ -1,3 +1,3 @@
//- Basic scene parsing with triangle meshes.
- Implement bucketed rendering.
//- Implement bucketed rendering.
- Unit tests for scene parsing.