diff --git a/rtiow/src/renderer.rs b/rtiow/src/renderer.rs index 66f18c5..d3e6af3 100644 --- a/rtiow/src/renderer.rs +++ b/rtiow/src/renderer.rs @@ -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, -) -> 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, y_range: Range, 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::(0., 1.) + x as f32) / scene.width as f32; let v = (rng.gen_range::(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 }, + Pixel { + x: usize, + y: usize, + pixel: Vec3, + rs: RenderStats, + }, + Line { + y: usize, + pixels: Vec, + 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, Vec) = (0..width) + .map(|x| render_pixel(scene, x, y)) + .collect::>() + .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, ¤t_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(), ¤t_stat, time_diff, pixel_total) ); output::write_images(output_dir)