rtiow: lockless non-global stats keeping.

This commit is contained in:
Bill Thiede 2019-10-21 21:11:15 -07:00
parent 27ca936264
commit 848e9879cb

View File

@ -1,11 +1,10 @@
use std::fmt; use std::fmt;
use std::ops::AddAssign;
use std::ops::Range; use std::ops::Range;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str; use std::str;
use std::sync; use std::sync;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::mpsc::sync_channel; use std::sync::mpsc::sync_channel;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::sync::mpsc::SyncSender; use std::sync::mpsc::SyncSender;
@ -49,7 +48,6 @@ impl MockPrometheus {
fn inc(&self) {} fn inc(&self) {}
} }
static RAY_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(feature = "prom"))] #[cfg(not(feature = "prom"))]
static RAY_COUNTER: MockPrometheus = MockPrometheus {}; static RAY_COUNTER: MockPrometheus = MockPrometheus {};
@ -240,40 +238,41 @@ fn color(
depth: usize, depth: usize,
global_illumination: bool, global_illumination: bool,
env_map: &Option<EnvMap>, env_map: &Option<EnvMap>,
) -> Vec3 { ) -> (Vec3, usize) {
RAY_COUNT.fetch_add(1, Ordering::SeqCst);
RAY_COUNTER.with_label_values(&[&depth.to_string()]).inc(); RAY_COUNTER.with_label_values(&[&depth.to_string()]).inc();
if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) { if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) {
let (u, v) = rec.uv; let (u, v) = rec.uv;
let emitted = rec.material.emitted(u, v, rec.p); let emitted = rec.material.emitted(u, v, rec.p);
let scatter_response = rec.material.scatter(&r, &rec); let scatter_response = rec.material.scatter(&r, &rec);
if depth < 50 && scatter_response.reflected { if depth < 50 && scatter_response.reflected {
return emitted let (c, rays) = color(
+ scatter_response.attenutation scatter_response.scattered,
* color( world,
scatter_response.scattered, depth + 1,
world, global_illumination,
depth + 1, env_map,
global_illumination, );
env_map, return (emitted + scatter_response.attenutation * c, rays + 1);
);
} else { } else {
return emitted; return (emitted, 1);
} }
} }
if global_illumination { if global_illumination {
return match env_map { return match env_map {
Some(env_map) => env_map.color(r.direction.unit_vector()), Some(env_map) => (env_map.color(r.direction.unit_vector()), 1),
None => { None => {
let unit_direction = r.direction.unit_vector(); let unit_direction = r.direction.unit_vector();
// No hit, choose color from background. // No hit, choose color from background.
let t = 0.5 * (unit_direction.y + 1.); let t = 0.5 * (unit_direction.y + 1.);
Vec3::new(1., 1., 1.) * (1. - t) + Vec3::new(0.5, 0.7, 1.) * t (
Vec3::new(1., 1., 1.) * (1. - t) + Vec3::new(0.5, 0.7, 1.) * t,
1,
)
} }
}; };
} }
// No global illumination, so background is black. // No global illumination, so background is black.
Vec3::new(0., 0., 0.) (Vec3::new(0., 0., 0.), 1)
} }
const MAX_ADAPTIVE_DEPTH: usize = 10; const MAX_ADAPTIVE_DEPTH: usize = 10;
@ -285,13 +284,13 @@ fn trace_pixel_adaptive(
x_range: Range<f32>, x_range: Range<f32>,
y_range: Range<f32>, y_range: Range<f32>,
scene: &Scene, scene: &Scene,
) -> Vec3 { ) -> (Vec3, usize) {
let w = scene.width as f32; let w = scene.width as f32;
let h = scene.height as f32; let h = scene.height as f32;
let x_mid = x_range.start + ((x_range.end - x_range.start) / 2.); let x_mid = x_range.start + ((x_range.end - x_range.start) / 2.);
let y_mid = y_range.start + ((y_range.end - y_range.start) / 2.); let y_mid = y_range.start + ((y_range.end - y_range.start) / 2.);
let mc = ((x_mid + x as f32) / w, (y_mid + y as f32) / h); let mc = ((x_mid + x as f32) / w, (y_mid + y as f32) / h);
let center = color( let (center, rays) = color(
scene.camera.get_ray(mc.0, mc.1), scene.camera.get_ray(mc.0, mc.1),
scene.world.as_ref(), scene.world.as_ref(),
0, 0,
@ -300,7 +299,7 @@ fn trace_pixel_adaptive(
); );
if depth == 0 { if depth == 0 {
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into()); output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
return center; return (center, rays);
} }
// t = top // t = top
// m = middle // m = middle
@ -316,7 +315,7 @@ fn trace_pixel_adaptive(
let bl = ((x_range.start + x as f32) / w, (y_range.end + y as f32) / h); let bl = ((x_range.start + x as f32) / w, (y_range.end + y as f32) / h);
let br = ((x_range.end + x as f32) / w, (y_range.end + y as f32) / h); let br = ((x_range.end + x as f32) / w, (y_range.end + y as f32) / h);
let corners = [tl, tr, mc, bl, br] let (corners, rays) = [tl, tr, mc, bl, br]
.iter() .iter()
.map(|(u, v)| { .map(|(u, v)| {
color( color(
@ -327,10 +326,13 @@ fn trace_pixel_adaptive(
&scene.env_map, &scene.env_map,
) )
}) })
.fold([0., 0., 0.].into(), |a: Vec3, b: Vec3| a + b) .fold(
/ 5.; ([0., 0., 0.].into(), 0),
|(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)),
);
let corners = corners / 5.;
if (corners - center).length() > threshold { if (corners - center).length() > threshold {
let pixel = (trace_pixel_adaptive( let tl = trace_pixel_adaptive(
depth - 1, depth - 1,
threshold, threshold,
x, x,
@ -338,7 +340,8 @@ fn trace_pixel_adaptive(
x_range.start..x_mid, x_range.start..x_mid,
y_range.start..y_mid, y_range.start..y_mid,
scene, scene,
) + trace_pixel_adaptive( );
let tr = trace_pixel_adaptive(
depth - 1, depth - 1,
threshold, threshold,
x, x,
@ -346,7 +349,8 @@ fn trace_pixel_adaptive(
x_mid..x_range.end, x_mid..x_range.end,
y_range.start..y_mid, y_range.start..y_mid,
scene, scene,
) + trace_pixel_adaptive( );
let bl = trace_pixel_adaptive(
depth - 1, depth - 1,
threshold, threshold,
x, x,
@ -354,7 +358,8 @@ fn trace_pixel_adaptive(
x_range.start..x_mid, x_range.start..x_mid,
y_mid..y_range.end, y_mid..y_range.end,
scene, scene,
) + trace_pixel_adaptive( );
let br = trace_pixel_adaptive(
depth - 1, depth - 1,
threshold, threshold,
x, x,
@ -362,17 +367,19 @@ fn trace_pixel_adaptive(
x_mid..x_range.end, x_mid..x_range.end,
y_mid..y_range.end, y_mid..y_range.end,
scene, scene,
)) / 4.; );
pixel let pixel = (tl.0 + tr.0 + bl.0 + br.0) / 4.;
let rays = tl.1 + tr.1 + bl.1 + br.1;
(pixel, rays)
} else { } else {
if depth == MAX_ADAPTIVE_DEPTH { if depth == MAX_ADAPTIVE_DEPTH {
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into()); output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into());
} }
corners (corners, rays)
} }
} }
fn trace_pixel_random(x: usize, y: usize, scene: &Scene) -> Vec3 { fn trace_pixel_random(x: usize, y: usize, scene: &Scene) -> (Vec3, usize) {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let u = (rng.gen_range::<f32>(0., 1.) + x as f32) / scene.width as f32; let u = (rng.gen_range::<f32>(0., 1.) + x as f32) / scene.width as f32;
let v = (rng.gen_range::<f32>(0., 1.) + y as f32) / scene.height as f32; let v = (rng.gen_range::<f32>(0., 1.) + y as f32) / scene.height as f32;
@ -386,21 +393,67 @@ fn trace_pixel_random(x: usize, y: usize, scene: &Scene) -> Vec3 {
) )
} }
#[derive(Clone, Copy)]
struct RenderStats {
rays: usize,
pixels: usize,
}
impl AddAssign for RenderStats {
fn add_assign(&mut self, other: Self) {
*self = Self {
rays: self.rays + other.rays,
pixels: self.pixels + other.pixels,
}
}
}
fn progress(
last_stat: &RenderStats,
current_stat: &RenderStats,
time_diff: time::Duration,
pixel_total: usize,
) -> String {
let human = human::Formatter::new();
let pixel_diff = current_stat.pixels - last_stat.pixels;
let ray_diff = current_stat.rays - last_stat.rays;
format!(
"{:7} / {:7}pixels ({:2}%) {:7}pixels/s {:7}rays/s",
human.format(current_stat.pixels as f64),
human.format(pixel_total as f64),
100 * current_stat.pixels / pixel_total,
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
human.format(ray_diff as f64 / time_diff.as_secs_f64())
)
}
impl Default for RenderStats {
fn default() -> Self {
RenderStats { rays: 0, pixels: 0 }
}
}
enum Request { enum Request {
Pixel { x: usize, y: usize }, Pixel { x: usize, y: usize },
Line { width: usize, y: usize }, Line { width: usize, y: usize },
} }
enum Response { enum Response {
Pixel { x: usize, y: usize, pixel: Vec3 }, Pixel {
Line { y: usize, pixels: Vec<Vec3> }, x: usize,
y: usize,
pixel: Vec3,
rs: RenderStats,
},
Line {
y: usize,
pixels: Vec<Vec3>,
rs: RenderStats,
},
} }
static PIXEL_COUNT: AtomicUsize = AtomicUsize::new(0); fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
let (pixel, rays) = if let Some(threshold) = scene.adaptive_subsampling {
fn render_pixel(scene: &Scene, x: usize, y: usize) -> Vec3 {
let mut pixel: Vec3 = Default::default();
let pixel = if let Some(threshold) = scene.adaptive_subsampling {
trace_pixel_adaptive( trace_pixel_adaptive(
MAX_ADAPTIVE_DEPTH, MAX_ADAPTIVE_DEPTH,
threshold, threshold,
@ -411,15 +464,20 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> Vec3 {
scene, scene,
) )
} else { } else {
for _ in 0..scene.subsamples { let (pixel, rays) = (0..scene.subsamples)
pixel = pixel + trace_pixel_random(x, y, scene); .map(|_| trace_pixel_random(x, y, scene))
} .fold(
pixel / scene.subsamples as f32 ([0., 0., 0.].into(), 0),
|(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)),
);
(pixel / scene.subsamples as f32, rays)
}; };
PIXEL_COUNT.fetch_add(1, Ordering::SeqCst);
// Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or // Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or
// sqrt. // sqrt.
Vec3::new(pixel[0].sqrt(), pixel[1].sqrt(), pixel[2].sqrt()) (
Vec3::new(pixel[0].sqrt(), pixel[1].sqrt(), pixel[2].sqrt()),
rays,
)
} }
fn render_worker( fn render_worker(
@ -438,16 +496,33 @@ fn render_worker(
Ok(req) => match req { Ok(req) => match req {
Request::Line { width, y } => { Request::Line { width, y } => {
trace!("tid {} width {} y {}", tid, width, y); trace!("tid {} width {} y {}", tid, width, y);
let pixels = (0..width).map(|x| render_pixel(scene, x, y)).collect(); let (pixels, rays): (Vec<Vec3>, Vec<usize>) = (0..width)
.map(|x| render_pixel(scene, x, y))
.collect::<Vec<(_, _)>>()
.into_iter()
.unzip();
let rays = rays.iter().sum();
output_chan output_chan
.send(Response::Line { y, pixels }) .send(Response::Line {
y,
pixels,
rs: RenderStats {
rays,
pixels: width,
},
})
.expect("failed to send pixel response"); .expect("failed to send pixel response");
} }
Request::Pixel { x, y } => { Request::Pixel { x, y } => {
trace!("tid {} x {} y {}", tid, x, y); trace!("tid {} x {} y {}", tid, x, y);
let pixel = render_pixel(scene, x, y); let (pixel, rays) = render_pixel(scene, x, y);
output_chan output_chan
.send(Response::Pixel { x, y, pixel }) .send(Response::Pixel {
x,
y,
pixel,
rs: RenderStats { rays, pixels: 1 },
})
.expect("failed to send line response"); .expect("failed to send line response");
} }
}, },
@ -524,61 +599,41 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
} }
let pixel_total = scene.width * scene.height; let pixel_total = scene.width * scene.height;
handles.push(thread::spawn(move || { let mut last_time = time::Instant::now();
let mut last_time = time::Instant::now(); let mut last_stat: RenderStats = Default::default();
let mut last_pixel_count = PIXEL_COUNT.load(Ordering::SeqCst); let mut current_stat: RenderStats = Default::default();
let mut last_ray_count = RAY_COUNT.load(Ordering::SeqCst);
let human = human::Formatter::new();
loop {
let sleep_time = time::Duration::from_secs(1);
thread::sleep(sleep_time);
let now = time::Instant::now();
let pixel_count = PIXEL_COUNT.load(Ordering::SeqCst);
let ray_count = RAY_COUNT.load(Ordering::SeqCst);
let time_diff = now - last_time;
let pixel_diff = pixel_count - last_pixel_count;
let ray_diff = ray_count - last_ray_count;
info!(
"{} / {}pixels ({}%) {}pixels/s {}rays/s",
human.format(pixel_count as f64),
human.format(pixel_total as f64),
100 * pixel_count / pixel_total,
human.format(pixel_diff as f64 / time_diff.as_secs_f64()),
human.format(ray_diff as f64 / time_diff.as_secs_f64())
);
last_time = now;
last_pixel_count = pixel_count;
last_ray_count = ray_count;
if pixel_count == pixel_total {
return;
}
}
}));
for resp in pixel_resp_rx { for resp in pixel_resp_rx {
match resp { match resp {
Response::Pixel { x, y, pixel } => { Response::Pixel { x, y, pixel, rs } => {
current_stat += rs;
output::set_pixel(output::MAIN_IMAGE, x, y, pixel); output::set_pixel(output::MAIN_IMAGE, x, y, pixel);
} }
Response::Line { y, pixels } => { Response::Line { y, pixels, rs } => {
current_stat += rs;
for (x, pixel) in pixels.iter().enumerate() { for (x, pixel) in pixels.iter().enumerate() {
output::set_pixel(output::MAIN_IMAGE, x, y, *pixel); output::set_pixel(output::MAIN_IMAGE, x, y, *pixel);
} }
} }
} }
let now = time::Instant::now();
let time_diff = now - last_time;
if time_diff > time::Duration::from_secs(1) {
info!(
"{}",
progress(&last_stat, &current_stat, time_diff, pixel_total)
);
last_stat = current_stat;
last_time = now;
}
} }
for thr in handles { for thr in handles {
thr.join().expect("thread join"); thr.join().expect("thread join");
} }
let human = human::Formatter::new();
let time_diff = time::Instant::now() - start_time; let time_diff = time::Instant::now() - start_time;
let ray_count = RAY_COUNT.load(Ordering::SeqCst);
info!( info!(
"{}pixels {:.2}s {}pixels/s {}rays/s", "Summary: {}",
human.format(pixel_total as f64), progress(&Default::default(), &current_stat, time_diff, pixel_total)
time_diff.as_secs_f64(),
human.format(pixel_total as f64 / time_diff.as_secs_f64()),
human.format(ray_count as f64 / time_diff.as_secs_f64())
); );
output::write_images(output_dir) output::write_images(output_dir)