From 593632a9e30f7628217b3ec9e71a6c868a3a6fa5 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 29 Oct 2023 09:17:32 -0700 Subject: [PATCH] rtiow: move image writer to an instance instead of wrappers around global. --- rtiow/renderer/src/output.rs | 267 ++++++++++++++++----------------- rtiow/renderer/src/renderer.rs | 49 ++++-- 2 files changed, 166 insertions(+), 150 deletions(-) diff --git a/rtiow/renderer/src/output.rs b/rtiow/renderer/src/output.rs index f36316a..3d2f2f3 100644 --- a/rtiow/renderer/src/output.rs +++ b/rtiow/renderer/src/output.rs @@ -24,10 +24,6 @@ pub const ADAPTIVE_DEPTH: &str = "adaptive_depth"; // Grey scale showing rays cast per pixel. pub const RAYS_PER_PIXEL: &str = "rays_per_pixel"; -lazy_static! { - static ref DEBUGGER: Arc> = Arc::new(Mutex::new(Debugger::new())); -} - #[derive(Serialize)] struct ImageMetadata { name: String, @@ -74,43 +70,144 @@ impl Image { } } -struct Debugger { - images: HashMap, +pub struct OutputManager { + images: Arc>>, } -impl Debugger { - fn new() -> Debugger { - Debugger { - images: HashMap::new(), +impl OutputManager { + pub fn new() -> OutputManager { + OutputManager { + images: Arc::new(Mutex::new(HashMap::new())), } } -} -pub fn register_image(name: String, dimensions: (usize, usize), it: ImageType) { - let mut debugger = DEBUGGER.lock().unwrap(); - debugger - .images - .insert(name, (it, Image::new(dimensions.0, dimensions.1))); -} + pub fn register_image(&self, name: String, dimensions: (usize, usize), it: ImageType) { + let mut images = self.images.lock().unwrap(); + images.insert(name, (it, Image::new(dimensions.0, dimensions.1))); + } -pub fn set_pixel(name: &str, x: usize, y: usize, pixel: Vec3) { - let mut debugger = DEBUGGER.lock().unwrap(); - let (_it, img) = debugger - .images - .get_mut(name) - .unwrap_or_else(|| panic!("couldn't find image named '{}'", name)); - let y_inv = img.h - y - 1; - img.put_pixel(x, y_inv, pixel); -} + pub fn set_pixel(&self, name: &str, x: usize, y: usize, pixel: Vec3) { + let mut images = self.images.lock().unwrap(); + let (_it, img) = images + .get_mut(name) + .unwrap_or_else(|| panic!("couldn't find image named '{}'", name)); + let y_inv = img.h - y - 1; + img.put_pixel(x, y_inv, pixel); + } -pub fn set_pixel_grey(name: &str, x: usize, y: usize, grey: f32) { - let mut debugger = DEBUGGER.lock().unwrap(); - let (_it, img) = debugger - .images - .get_mut(name) - .unwrap_or_else(|| panic!("couldn't find image named '{}'", name)); - let y_inv = img.h - y - 1; - img.put_pixel(x, y_inv, [grey, grey, grey].into()); + pub fn set_pixel_grey(&self, name: &str, x: usize, y: usize, grey: f32) { + let mut images = self.images.lock().unwrap(); + let (_it, img) = images + .get_mut(name) + .unwrap_or_else(|| panic!("couldn't find image named '{}'", name)); + let y_inv = img.h - y - 1; + img.put_pixel(x, y_inv, [grey, grey, grey].into()); + } + pub fn write_images>( + &self, + scene: &Scene, + render_time: time::Duration, + output_dir: P, + ) -> std::io::Result<()> { + let output_dir: &Path = output_dir.as_ref(); + let now = Local::now(); + let images = self.images.lock().unwrap(); + // Write out images in consistent order. + let mut names = images.keys().collect::>(); + names.sort(); + let mut image_metadata = Vec::new(); + for name in &names { + let (it, img) = images.get(*name).unwrap(); + let image = format!("{}.png", name); + let binary = format!("{}.json", name); + let ratio = img.w as f32 / img.h as f32; + let size = (img.w, img.h); + let image_path = output_dir.join(&image); + let binary_path = output_dir.join(&binary); + image_metadata.push(ImageMetadata { + name: name.to_string(), + image, + binary, + ratio, + size, + format: *it, + }); + info!("Saving {}", image_path.to_string_lossy()); + match it { + ImageType::RGB01 => { + let mut out_img = image::RgbImage::new(img.w as u32, img.h as u32); + out_img + .enumerate_pixels_mut() + .enumerate() + .for_each(|(i, (_x, _y, p))| { + let pixel = img.pix[i]; + *p = image::Rgb([ + (pixel[0] * 255.).min(255.) as u8, + (pixel[1] * 255.).min(255.) as u8, + (pixel[2] * 255.).min(255.) as u8, + ]) + }); + out_img.save(image_path)?; + } + ImageType::Grey01 => { + let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32); + out_img + .enumerate_pixels_mut() + .enumerate() + .for_each(|(i, (_x, _y, p))| { + let pixel = img.pix[i]; + *p = image::Luma([(pixel[0] * 255.).min(255.) as u8]) + }); + out_img.save(image_path)?; + } + ImageType::GreyNormalized => { + let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32); + + let max_val = img.pix.iter().map(|v| v.x).fold(0., f32::max); + out_img + .enumerate_pixels_mut() + .enumerate() + .for_each(|(i, (_x, _y, p))| { + let pixel = img.pix[i]; + *p = image::Luma([(pixel[0] / max_val * 255.).min(255.) as u8]) + }); + out_img.save(image_path)?; + } + }; + info!("Saving {}", binary_path.to_string_lossy()); + let f = File::create(output_dir.join(binary_path))?; + let f = BufWriter::new(f); + match it { + ImageType::RGB01 => { + serde_json::ser::to_writer( + f, + &img.pix + .iter() + .map(|v| [v.x, v.y, v.z]) + .collect::>(), + )?; + } + ImageType::Grey01 | ImageType::GreyNormalized => { + serde_json::ser::to_writer( + f, + &img.pix.iter().map(|v| v.x).collect::>(), + )?; + } + }; + } + let f = File::create(output_dir.join("data.json"))?; + let f = BufWriter::new(f); + serde_json::ser::to_writer( + f, + &Data { + timestamp: now.timestamp(), + render_time_seconds: render_time.as_secs_f32(), + scene, + image_metadata, + }, + )?; + Ok(()) + } } trait ImageSaver { @@ -118,105 +215,3 @@ trait ImageSaver { where Q: AsRef + Sized; } - -pub fn write_images>( - scene: &Scene, - render_time: time::Duration, - output_dir: P, -) -> std::io::Result<()> { - let output_dir: &Path = output_dir.as_ref(); - let debugger = DEBUGGER.lock().unwrap(); - let now = Local::now(); - // Write out images in consistent order. - let mut names = debugger.images.keys().collect::>(); - names.sort(); - let mut image_metadata = Vec::new(); - for name in &names { - let (it, img) = debugger.images.get(*name).unwrap(); - let image = format!("{}.png", name); - let binary = format!("{}.json", name); - let ratio = img.w as f32 / img.h as f32; - let size = (img.w, img.h); - let image_path = output_dir.join(&image); - let binary_path = output_dir.join(&binary); - image_metadata.push(ImageMetadata { - name: name.to_string(), - image, - binary, - ratio, - size, - format: *it, - }); - info!("Saving {}", image_path.to_string_lossy()); - match it { - ImageType::RGB01 => { - let mut out_img = image::RgbImage::new(img.w as u32, img.h as u32); - out_img - .enumerate_pixels_mut() - .enumerate() - .for_each(|(i, (_x, _y, p))| { - let pixel = img.pix[i]; - *p = image::Rgb([ - (pixel[0] * 255.).min(255.) as u8, - (pixel[1] * 255.).min(255.) as u8, - (pixel[2] * 255.).min(255.) as u8, - ]) - }); - out_img.save(image_path)?; - } - ImageType::Grey01 => { - let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32); - out_img - .enumerate_pixels_mut() - .enumerate() - .for_each(|(i, (_x, _y, p))| { - let pixel = img.pix[i]; - *p = image::Luma([(pixel[0] * 255.).min(255.) as u8]) - }); - out_img.save(image_path)?; - } - ImageType::GreyNormalized => { - let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32); - - let max_val = img.pix.iter().map(|v| v.x).fold(0., f32::max); - out_img - .enumerate_pixels_mut() - .enumerate() - .for_each(|(i, (_x, _y, p))| { - let pixel = img.pix[i]; - *p = image::Luma([(pixel[0] / max_val * 255.).min(255.) as u8]) - }); - out_img.save(image_path)?; - } - }; - info!("Saving {}", binary_path.to_string_lossy()); - let f = File::create(output_dir.join(binary_path))?; - let f = BufWriter::new(f); - match it { - ImageType::RGB01 => { - serde_json::ser::to_writer( - f, - &img.pix - .iter() - .map(|v| [v.x, v.y, v.z]) - .collect::>(), - )?; - } - ImageType::Grey01 | ImageType::GreyNormalized => { - serde_json::ser::to_writer(f, &img.pix.iter().map(|v| v.x).collect::>())?; - } - }; - } - let f = File::create(output_dir.join("data.json"))?; - let f = BufWriter::new(f); - serde_json::ser::to_writer( - f, - &Data { - timestamp: now.timestamp(), - render_time_seconds: render_time.as_secs_f32(), - scene, - image_metadata, - }, - )?; - Ok(()) -} diff --git a/rtiow/renderer/src/renderer.rs b/rtiow/renderer/src/renderer.rs index 535f09d..7d0334f 100644 --- a/rtiow/renderer/src/renderer.rs +++ b/rtiow/renderer/src/renderer.rs @@ -25,6 +25,7 @@ use crate::{ human, material::{Lambertian, Material}, output, + output::OutputManager, ray::Ray, scenes, sphere::Sphere, @@ -109,6 +110,9 @@ pub struct Opt { /// Use acceleration data structure, may be BVH or kd-tree depending on scene. #[structopt(long = "use_accel")] pub use_accel: bool, + /// Host:port of running tev instance. + #[structopt(long = "tev_addr")] + pub tev_addr: Option, /// Output directory #[structopt( @@ -246,6 +250,7 @@ fn trace_pixel_adaptive( x_range: Range, y_range: Range, scene: &Scene, + output: &OutputManager, ) -> (Vec3, usize) { let w = scene.width as f32; let h = scene.height as f32; @@ -260,7 +265,7 @@ fn trace_pixel_adaptive( &scene.env_map, ); 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, rays); } // t = top @@ -302,6 +307,7 @@ fn trace_pixel_adaptive( x_range.start..x_mid, y_range.start..y_mid, scene, + output, ); let tr = trace_pixel_adaptive( depth - 1, @@ -311,6 +317,7 @@ fn trace_pixel_adaptive( x_mid..x_range.end, y_range.start..y_mid, scene, + output, ); let bl = trace_pixel_adaptive( depth - 1, @@ -320,6 +327,7 @@ fn trace_pixel_adaptive( x_range.start..x_mid, y_mid..y_range.end, scene, + output, ); let br = trace_pixel_adaptive( depth - 1, @@ -329,13 +337,14 @@ fn trace_pixel_adaptive( x_mid..x_range.end, y_mid..y_range.end, scene, + output, ); 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()); + output.set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into()); } (corners, rays) } @@ -418,7 +427,7 @@ enum Response { }, } -fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) { +fn render_pixel(scene: &Scene, x: usize, y: usize, output: &OutputManager) -> (Vec3, usize) { let (pixel, rays) = if let Some(threshold) = scene.adaptive_subsampling { trace_pixel_adaptive( MAX_ADAPTIVE_DEPTH, @@ -428,6 +437,7 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) { 0.0..1.0, 0.0..1.0, scene, + output, ) } else { let (pixel, rays) = (0..scene.subsamples) @@ -436,7 +446,7 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) { ([0., 0., 0.].into(), 0), |(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)), ); - output::set_pixel_grey(output::RAYS_PER_PIXEL, x, y, rays as f32); + output.set_pixel_grey(output::RAYS_PER_PIXEL, x, y, rays as f32); (pixel / scene.subsamples as f32, rays) }; // Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or @@ -452,6 +462,7 @@ fn render_worker( scene: &Scene, input_chan: Arc>>, output_chan: &SyncSender, + output: &OutputManager, ) { loop { let job = { input_chan.lock().unwrap().recv() }; @@ -466,7 +477,7 @@ fn render_worker( let batch = false; if batch { let (pixels, rays): (Vec, Vec) = (0..width) - .map(|x| render_pixel(scene, x, y)) + .map(|x| render_pixel(scene, x, y, output)) .collect::>() .into_iter() .unzip(); @@ -483,7 +494,7 @@ fn render_worker( .expect("failed to send pixel response"); } else { (0..width).for_each(|x| { - let (pixel, rays) = render_pixel(scene, x, y); + let (pixel, rays) = render_pixel(scene, x, y, output); output_chan .send(Response::Pixel { x, @@ -497,7 +508,7 @@ fn render_worker( } Request::Pixel { x, y } => { trace!("tid {} x {} y {}", tid, x, y); - let (pixel, rays) = render_pixel(scene, x, y); + let (pixel, rays) = render_pixel(scene, x, y, output); output_chan .send(Response::Pixel { x, @@ -512,6 +523,12 @@ fn render_worker( } } +/* +lazy_static! { + static ref DEBUGGER: Arc> = Arc::new(Mutex::new(OutputManager::new())); +} +*/ + pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::io::Error> { // Default to half the cores to disable hyperthreading. let num_threads = scene.num_threads.unwrap_or_else(|| num_cpus::get() / 2); @@ -528,20 +545,23 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i } else { core_ids }; + let output = output::OutputManager::new(); + let output = Arc::new(output); + info!("Creating {} render threads", core_ids.len()); - output::register_image( + output.register_image( output::MAIN_IMAGE.to_string(), (scene.width, scene.height), output::ImageType::RGB01, ); if scene.adaptive_subsampling.is_some() { - output::register_image( + output.register_image( output::ADAPTIVE_DEPTH.to_string(), (scene.width, scene.height), output::ImageType::RGB01, ); } - output::register_image( + output.register_image( output::RAYS_PER_PIXEL.to_string(), (scene.width, scene.height), output::ImageType::GreyNormalized, @@ -555,9 +575,10 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i let s = sync::Arc::clone(&scene); let pixel_req_rx = pixel_req_rx.clone(); let pixel_resp_tx = pixel_resp_tx.clone(); + let output = sync::Arc::clone(&output); thread::spawn(move || { core_affinity::set_for_current(id); - render_worker(i, &s, pixel_req_rx, &pixel_resp_tx); + render_worker(i, &s, pixel_req_rx, &pixel_resp_tx, &output); }) }) .collect::>(); @@ -596,12 +617,12 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i match resp { 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, rs } => { current_stat += rs; 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); } } } @@ -639,5 +660,5 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i ) ); - output::write_images(&scene, time_diff, output_dir) + output.write_images(&scene, time_diff, output_dir) }