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"
|
name = "psychopath"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
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)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "docopt"
|
name = "docopt"
|
||||||
version = "0.6.80"
|
version = "0.6.80"
|
||||||
|
|
|
@ -8,4 +8,5 @@ docopt = "0.6"
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
nom = "1.2"
|
nom = "1.2"
|
||||||
scoped_threadpool = "0.1"
|
scoped_threadpool = "0.1"
|
||||||
|
crossbeam = "0.2"
|
||||||
num_cpus = "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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
data: Vec<(u8, u8, u8)>,
|
data: Vec<(f32, f32, f32)>,
|
||||||
res: (usize, usize),
|
res: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub fn new(width: usize, height: usize) -> Image {
|
pub fn new(width: usize, height: usize) -> Image {
|
||||||
Image {
|
Image {
|
||||||
data: vec![(0,0,0); width * height],
|
data: vec![(0.0, 0.0, 0.0); width * height],
|
||||||
res: (width, height),
|
res: (width, height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,14 @@ impl Image {
|
||||||
self.res.1
|
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!(x < self.res.0);
|
||||||
assert!(y < self.res.1);
|
assert!(y < self.res.1);
|
||||||
|
|
||||||
self.data[self.res.0 * y + x]
|
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!(x < self.res.0);
|
||||||
assert!(y < self.res.1);
|
assert!(y < self.res.1);
|
||||||
|
|
||||||
|
@ -52,6 +52,9 @@ impl Image {
|
||||||
for y in 0..self.res.1 {
|
for y in 0..self.res.1 {
|
||||||
for x in 0..self.res.0 {
|
for x in 0..self.res.0 {
|
||||||
let (r, g, b) = self.get(x, y);
|
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, "{} {} {} ", r, g, b));
|
||||||
}
|
}
|
||||||
try!(write!(f, "\n"));
|
try!(write!(f, "\n"));
|
||||||
|
@ -72,6 +75,9 @@ impl Image {
|
||||||
for y in 0..self.res.1 {
|
for y in 0..self.res.1 {
|
||||||
for x in 0..self.res.0 {
|
for x in 0..self.res.0 {
|
||||||
let (r, g, b) = self.get(x, y);
|
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];
|
let d = [r, g, b];
|
||||||
try!(f.write_all(&d));
|
try!(f.write_all(&d));
|
||||||
}
|
}
|
||||||
|
@ -81,3 +87,24 @@ impl Image {
|
||||||
Ok(())
|
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 rustc_serialize;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
extern crate scoped_threadpool;
|
extern crate scoped_threadpool;
|
||||||
|
extern crate crossbeam;
|
||||||
extern crate num_cpus;
|
extern crate num_cpus;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate nom;
|
extern crate nom;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
|
||||||
use nom;
|
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
use super::DataTree;
|
use super::DataTree;
|
||||||
|
|
145
src/renderer.rs
145
src/renderer.rs
|
@ -1,9 +1,12 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::path::Path;
|
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 std::cell::RefCell;
|
||||||
use scoped_threadpool::Pool;
|
use scoped_threadpool::Pool;
|
||||||
|
use crossbeam::sync::MsQueue;
|
||||||
|
|
||||||
use tracer::Tracer;
|
use tracer::Tracer;
|
||||||
use halton;
|
use halton;
|
||||||
|
@ -24,8 +27,15 @@ impl Renderer {
|
||||||
pub fn render(&self, thread_count: u32) {
|
pub fn render(&self, thread_count: u32) {
|
||||||
let mut tpool = Pool::new(thread_count);
|
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
|
// Pre-calculate some useful values related to the image plane
|
||||||
let cmpx = 1.0 / self.resolution.0 as f32;
|
let cmpx = 1.0 / self.resolution.0 as f32;
|
||||||
|
@ -37,93 +47,138 @@ impl Renderer {
|
||||||
let x_extent = max_x - min_x;
|
let x_extent = max_x - min_x;
|
||||||
let y_extent = max_y - min_y;
|
let y_extent = max_y - min_y;
|
||||||
|
|
||||||
|
// Set up job queue
|
||||||
|
let job_queue = MsQueue::new();
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
tpool.scoped(|scope| {
|
tpool.scoped(|scope| {
|
||||||
let (img_width, img_height) = {
|
// Spawn worker tasks
|
||||||
let i = img.lock().unwrap();
|
for _ in 0..thread_count {
|
||||||
let w = i.borrow().width();
|
let jq = &job_queue;
|
||||||
let h = i.borrow().height();
|
let ajq = &all_jobs_queued;
|
||||||
(w, h)
|
let img = ℑ
|
||||||
};
|
|
||||||
for y in 0..img_height {
|
|
||||||
for x in 0..img_width {
|
|
||||||
let img = &img;
|
|
||||||
scope.execute(move || {
|
scope.execute(move || {
|
||||||
let mut rays = Vec::new();
|
let mut rays = Vec::new();
|
||||||
|
let mut pixel_mapping = Vec::new();
|
||||||
let mut tracer = Tracer::from_assembly(&self.scene.root);
|
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
|
// Generate rays
|
||||||
rays.clear();
|
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 {
|
for si in 0..self.spp {
|
||||||
let mut ray = {
|
let mut ray = {
|
||||||
let filter_x =
|
let filter_x =
|
||||||
fast_logit(halton::sample(3, offset + si as u32), 1.5) + 0.5;
|
fast_logit(halton::sample(3, offset + si as u32), 1.5) +
|
||||||
|
0.5;
|
||||||
let filter_y =
|
let filter_y =
|
||||||
fast_logit(halton::sample(4, offset + si as u32), 1.5) + 0.5;
|
fast_logit(halton::sample(4, offset + si as u32), 1.5) +
|
||||||
|
0.5;
|
||||||
let samp_x = (filter_x + x as f32) * cmpx;
|
let samp_x = (filter_x + x as f32) * cmpx;
|
||||||
let samp_y = (filter_y + y as f32) * cmpy;
|
let samp_y = (filter_y + y as f32) * cmpy;
|
||||||
|
|
||||||
self.scene.camera.generate_ray((samp_x - 0.5) * x_extent,
|
self.scene
|
||||||
|
.camera
|
||||||
|
.generate_ray((samp_x - 0.5) * x_extent,
|
||||||
(0.5 - samp_y) * y_extent,
|
(0.5 - samp_y) * y_extent,
|
||||||
halton::sample(0,
|
halton::sample(0, offset + si as u32),
|
||||||
offset + si as u32),
|
halton::sample(1, offset + si as u32),
|
||||||
halton::sample(1,
|
halton::sample(2, offset + si as u32))
|
||||||
offset + si as u32),
|
|
||||||
halton::sample(2,
|
|
||||||
offset + si as u32))
|
|
||||||
};
|
};
|
||||||
ray.id = si as u32;
|
ray.id = rays.len() as u32;
|
||||||
rays.push(ray);
|
rays.push(ray);
|
||||||
|
pixel_mapping.push((x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test rays against scene
|
// Test rays against scene
|
||||||
let isects = tracer.trace(&rays);
|
let isects = tracer.trace(&rays);
|
||||||
|
|
||||||
// Calculate color based on ray hits
|
// Calculate color based on ray hits
|
||||||
let mut r = 0.0;
|
let img = img.lock().unwrap();
|
||||||
let mut g = 0.0;
|
let mut img = img.borrow_mut();
|
||||||
let mut b = 0.0;
|
for (isect, co) in Iterator::zip(isects.iter(), pixel_mapping.iter()) {
|
||||||
for isect in isects.iter() {
|
let mut col = img.get(co.0 as usize, co.1 as usize);
|
||||||
if let &surface::SurfaceIntersection::Hit { t: _,
|
if let &surface::SurfaceIntersection::Hit { t: _,
|
||||||
pos: _,
|
pos: _,
|
||||||
nor: _,
|
nor: _,
|
||||||
space: _,
|
space: _,
|
||||||
uv } = isect {
|
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
|
col.0 += uv.0 / self.spp as f32;
|
||||||
let img = img.lock().unwrap();
|
col.1 += uv.1 / self.spp as f32;
|
||||||
img.borrow_mut().set(x, y, (r as u8, g as u8, b as u8));
|
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
|
// 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));
|
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 {
|
fn hash_u32(n: u32, seed: u32) -> u32 {
|
||||||
let mut hash = n;
|
let mut hash = n;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::slice;
|
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
|
|
||||||
use math::{Matrix4x4, multiply_matrix_slices};
|
use math::{Matrix4x4, multiply_matrix_slices};
|
||||||
use lerp::lerp_slice;
|
use lerp::lerp_slice;
|
||||||
use assembly::{Assembly, Object, Instance, InstanceType};
|
use assembly::{Assembly, Object, InstanceType};
|
||||||
use ray::Ray;
|
use ray::Ray;
|
||||||
use surface::SurfaceIntersection;
|
use surface::SurfaceIntersection;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user