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::ops::AddAssign;
use std::ops::Range;
use std::path::Path;
use std::path::PathBuf;
use std::str;
use std::sync;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::mpsc::sync_channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::SyncSender;
@ -49,7 +48,6 @@ impl MockPrometheus {
fn inc(&self) {}
}
static RAY_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(feature = "prom"))]
static RAY_COUNTER: MockPrometheus = MockPrometheus {};
@ -240,40 +238,41 @@ fn color(
depth: usize,
global_illumination: bool,
env_map: &Option<EnvMap>,
) -> Vec3 {
RAY_COUNT.fetch_add(1, Ordering::SeqCst);
) -> (Vec3, usize) {
RAY_COUNTER.with_label_values(&[&depth.to_string()]).inc();
if let Some(rec) = world.hit(r, 0.001, std::f32::MAX) {
let (u, v) = rec.uv;
let emitted = rec.material.emitted(u, v, rec.p);
let scatter_response = rec.material.scatter(&r, &rec);
if depth < 50 && scatter_response.reflected {
return emitted
+ scatter_response.attenutation
* color(
scatter_response.scattered,
world,
depth + 1,
global_illumination,
env_map,
);
let (c, rays) = color(
scatter_response.scattered,
world,
depth + 1,
global_illumination,
env_map,
);
return (emitted + scatter_response.attenutation * c, rays + 1);
} else {
return emitted;
return (emitted, 1);
}
}
if global_illumination {
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 => {
let unit_direction = r.direction.unit_vector();
// No hit, choose color from background.
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.
Vec3::new(0., 0., 0.)
(Vec3::new(0., 0., 0.), 1)
}
const MAX_ADAPTIVE_DEPTH: usize = 10;
@ -285,13 +284,13 @@ fn trace_pixel_adaptive(
x_range: Range<f32>,
y_range: Range<f32>,
scene: &Scene,
) -> Vec3 {
) -> (Vec3, usize) {
let w = scene.width as f32;
let h = scene.height as f32;
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 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.world.as_ref(),
0,
@ -300,7 +299,7 @@ fn trace_pixel_adaptive(
);
if depth == 0 {
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
return center;
return (center, rays);
}
// t = top
// 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 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()
.map(|(u, v)| {
color(
@ -327,10 +326,13 @@ fn trace_pixel_adaptive(
&scene.env_map,
)
})
.fold([0., 0., 0.].into(), |a: Vec3, b: Vec3| a + b)
/ 5.;
.fold(
([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 {
let pixel = (trace_pixel_adaptive(
let tl = trace_pixel_adaptive(
depth - 1,
threshold,
x,
@ -338,7 +340,8 @@ fn trace_pixel_adaptive(
x_range.start..x_mid,
y_range.start..y_mid,
scene,
) + trace_pixel_adaptive(
);
let tr = trace_pixel_adaptive(
depth - 1,
threshold,
x,
@ -346,7 +349,8 @@ fn trace_pixel_adaptive(
x_mid..x_range.end,
y_range.start..y_mid,
scene,
) + trace_pixel_adaptive(
);
let bl = trace_pixel_adaptive(
depth - 1,
threshold,
x,
@ -354,7 +358,8 @@ fn trace_pixel_adaptive(
x_range.start..x_mid,
y_mid..y_range.end,
scene,
) + trace_pixel_adaptive(
);
let br = trace_pixel_adaptive(
depth - 1,
threshold,
x,
@ -362,17 +367,19 @@ fn trace_pixel_adaptive(
x_mid..x_range.end,
y_mid..y_range.end,
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 {
if depth == MAX_ADAPTIVE_DEPTH {
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 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;
@ -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 {
Pixel { x: usize, y: usize },
Line { width: usize, y: usize },
}
enum Response {
Pixel { x: usize, y: usize, pixel: Vec3 },
Line { y: usize, pixels: Vec<Vec3> },
Pixel {
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 {
let mut pixel: Vec3 = Default::default();
let pixel = if let Some(threshold) = scene.adaptive_subsampling {
fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
let (pixel, rays) = if let Some(threshold) = scene.adaptive_subsampling {
trace_pixel_adaptive(
MAX_ADAPTIVE_DEPTH,
threshold,
@ -411,15 +464,20 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> Vec3 {
scene,
)
} else {
for _ in 0..scene.subsamples {
pixel = pixel + trace_pixel_random(x, y, scene);
}
pixel / scene.subsamples as f32
let (pixel, rays) = (0..scene.subsamples)
.map(|_| trace_pixel_random(x, y, scene))
.fold(
([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
// 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(
@ -438,16 +496,33 @@ fn render_worker(
Ok(req) => match req {
Request::Line { 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
.send(Response::Line { y, pixels })
.send(Response::Line {
y,
pixels,
rs: RenderStats {
rays,
pixels: width,
},
})
.expect("failed to send pixel response");
}
Request::Pixel { 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
.send(Response::Pixel { x, y, pixel })
.send(Response::Pixel {
x,
y,
pixel,
rs: RenderStats { rays, pixels: 1 },
})
.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;
handles.push(thread::spawn(move || {
let mut last_time = time::Instant::now();
let mut last_pixel_count = PIXEL_COUNT.load(Ordering::SeqCst);
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;
}
}
}));
let mut last_time = time::Instant::now();
let mut last_stat: RenderStats = Default::default();
let mut current_stat: RenderStats = Default::default();
for resp in pixel_resp_rx {
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);
}
Response::Line { y, pixels } => {
Response::Line { y, pixels, rs } => {
current_stat += rs;
for (x, pixel) in pixels.iter().enumerate() {
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 {
thr.join().expect("thread join");
}
let human = human::Formatter::new();
let time_diff = time::Instant::now() - start_time;
let ray_count = RAY_COUNT.load(Ordering::SeqCst);
info!(
"{}pixels {:.2}s {}pixels/s {}rays/s",
human.format(pixel_total as f64),
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())
"Summary: {}",
progress(&Default::default(), &current_stat, time_diff, pixel_total)
);
output::write_images(output_dir)