Implemented bucketed rendering.
This commit is contained in:
parent
4493afd85b
commit
5ec1f534cf
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
35
src/image.rs
35
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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use std::result::Result;
|
||||
|
||||
use nom;
|
||||
use nom::IResult;
|
||||
|
||||
use super::DataTree;
|
||||
|
|
171
src/renderer.rs
171
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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user