diff --git a/.gitignore b/.gitignore index d569ccf..23b033f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target +.idea *.lock diff --git a/Cargo.toml b/Cargo.toml index 47ae72d..31f4a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,14 @@ blend_info = "0.2.9" byteorder = "1.4.3" crossbeam = "0.8.2" crossbeam-channel = "0.5.6" +crossbeam-utils = "0.8.5" hexf = "0.2.1" image = "0.24.3" impl_ops = "0.1.1" lazy_static = "1.4.0" num = "0.4.0" num_cpus = "1.13.1" +murmurhash64 = "0.3.1" pbr = "1.0.4" pest = "2.3.0" pest_derive = "2.3.0" @@ -36,6 +38,7 @@ structopt = "0.3.26" strum = "0.24.1" strum_macros = "0.24.3" typed-arena = "2.0.1" +tev_client = "0.5.2" [[bin]] name = "rs_pbrt" diff --git a/examples/parse_ass_file.rs b/examples/parse_ass_file.rs index c448eda..6f069a6 100644 --- a/examples/parse_ass_file.rs +++ b/examples/parse_ass_file.rs @@ -1200,7 +1200,7 @@ fn main() -> std::io::Result<()> { if let Some(mut integrator) = some_integrator { let scene = make_scene(&primitives, lights); let num_threads: u8 = num_cpus::get() as u8; - integrator.render(&scene, num_threads); + integrator.render(&scene, num_threads, None); } else { panic!("Unable to create integrator."); } diff --git a/src/bin/parse_blend_file.rs b/src/bin/parse_blend_file.rs index 0ce01f3..8f1715d 100644 --- a/src/bin/parse_blend_file.rs +++ b/src/bin/parse_blend_file.rs @@ -3543,7 +3543,7 @@ fn main() -> std::io::Result<()> { if let Some(mut integrator) = some_integrator { let scene = make_scene(&render_options.primitives, render_options.lights); let num_threads: u8 = num_cpus::get() as u8; - integrator.render(&scene, num_threads); + integrator.render(&scene, num_threads, None); } else { panic!("Unable to create integrator."); } @@ -3570,7 +3570,7 @@ fn main() -> std::io::Result<()> { if let Some(mut integrator) = some_integrator { let scene = make_scene(&render_options.primitives, render_options.lights); let num_threads: u8 = num_cpus::get() as u8; - integrator.render(&scene, num_threads); + integrator.render(&scene, num_threads, None); } else { panic!("Unable to create integrator."); } diff --git a/src/bin/rs_pbrt.rs b/src/bin/rs_pbrt.rs index 95def26..669bbd2 100644 --- a/src/bin/rs_pbrt.rs +++ b/src/bin/rs_pbrt.rs @@ -63,6 +63,9 @@ struct Cli { /// The path to the file to read #[structopt(parse(from_os_str))] path: std::path::PathBuf, + /// The address and port of the display server + #[structopt(long = "--display-server")] + display_server: Option, } // Accelerator @@ -895,6 +898,7 @@ fn main() { let cropx1: f32 = args.cropx1; let cropy0: f32 = args.cropy0; let cropy1: f32 = args.cropy1; + let display_server: Option = args.display_server; let num_cores = num_cpus::get(); let git_describe = option_env!("GIT_DESCRIBE").unwrap_or("unknown"); println!( @@ -910,6 +914,7 @@ fn main() { cropx1, cropy0, cropy1, + display_server ); parse_file( args.path.into_os_string().into_string().unwrap(), diff --git a/src/core/api.rs b/src/core/api.rs index 4d8a2ff..779d97a 100644 --- a/src/core/api.rs +++ b/src/core/api.rs @@ -127,6 +127,7 @@ pub struct ApiState { pushed_transforms: Vec, pushed_active_transform_bits: Vec, param_set: ParamSet, + display_server: Option, } impl Default for ApiState { @@ -163,6 +164,7 @@ impl Default for ApiState { pushed_transforms: Vec::new(), pushed_active_transform_bits: Vec::new(), param_set: ParamSet::default(), + display_server: None, } } } @@ -2346,6 +2348,7 @@ pub fn pbrt_init( cropx1: f32, cropy0: f32, cropy1: f32, + display_server: Option ) -> (ApiState, BsdfState) { let mut api_state: ApiState = ApiState::default(); let bsdf_state: BsdfState = BsdfState::default(); @@ -2361,6 +2364,7 @@ pub fn pbrt_init( y: clamp_t(cropy1.max(cropy0), 0.0, 1.0), }, }; + api_state.display_server = display_server; (api_state, bsdf_state) } @@ -2381,7 +2385,8 @@ pub fn pbrt_cleanup(api_state: &ApiState, integrator_arg: &Option) { if let Some(mut integrator) = some_integrator { let scene = api_state.render_options.make_scene(); let num_threads: u8 = api_state.number_of_threads; - integrator.render(&scene, num_threads); + let display_server = api_state.display_server.clone(); + integrator.render(&scene, num_threads, display_server); } else { panic!("Unable to create integrator."); } diff --git a/src/core/display.rs b/src/core/display.rs new file mode 100644 index 0000000..0aa5752 --- /dev/null +++ b/src/core/display.rs @@ -0,0 +1,376 @@ +use core::str; +use std::cmp::min; +use std::io::Write; +use std::mem::size_of; +use std::net::TcpStream; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex, RwLock}; +use std::thread::{self, ThreadId}; +use std::time; + +use atomic::Ordering; +use crossbeam_utils::thread::Scope; +use murmurhash64::murmur_hash64a; +use tev_client::{PacketCreateImage, PacketUpdateImage, TevClient, TevError}; + +use crate::core::geometry::{Bounds2i, Point2i}; +use crate::core::pbrt::Float; + +const TILE_SIZE: i32 = 128; + +type Pixels<'a, T> = Arc<&'a RwLock>>; +type Function = fn(Bounds2i, Pixels, usize, &mut Vec>); + +struct DisplayItem<'a, T: Send + Sync> { + title: String, + resolution: Point2i, + get_tile_values: Function, + // channel_buffers: Vec, + channel_names: Vec, + vec: Pixels<'a, T>, + opened_image: bool, +} + +impl<'a, T: Send + Sync> DisplayItem<'a, T> { + pub fn new( + base_title: &str, + resolution: Point2i, + channel_names: Vec, + vec: Pixels<'a, T>, + get_tile_values: Function, + ) -> DisplayItem<'a, T> { + let title = format!("{} {:?}", base_title, get_thread_id()); + + + DisplayItem { + title, + resolution, + get_tile_values, + channel_names, + vec, + opened_image: false, + } + } + + pub fn display_with_tev_client(&mut self, client: &mut TevClient) -> bool { + // Open image if not opened already + if !self.opened_image { + if let Ok(_) = self.send_create_image_packet(client) { + self.opened_image = true; + } else { + return false; + } + } + + // Create image buffer + let size = self.channel_names.len(); + let inner_size = (TILE_SIZE * TILE_SIZE) as usize; + let mut display_values: Vec> = Vec::with_capacity(size); + for _ in 0..size { + display_values.push(Vec::with_capacity(inner_size)); + } + + // Bounds for Tile + let bounds = Bounds2i { + p_min: Point2i { x: 0, y: 0 }, + p_max: self.resolution, + }; + + // Retrieve image values for Tile with bounds + (self.get_tile_values)( + bounds, + self.vec.clone(), + self.resolution.x as usize, + &mut display_values, + ); + + // debug_assert!(!display_values.iter().all(|x| x.iter().all(|y| *y == 0.0)), + // "All display values are zero"); + + + let channel0 = display_values[0].iter(); + let channel1 = display_values[1].iter(); + let channel2 = display_values[2].iter(); + let mut data: Vec = vec![]; + data.push(1.0); + for ((x, y), z) in channel0.zip(channel1).zip(channel2) { + data.push(*x); + data.push(*y); + data.push(*z); + } + + let packet = PacketUpdateImage { + image_name: "TestImage", + grab_focus: false, + channel_names: &vec!["R", "G", "B"], + channel_offsets: &[1, 2, 3], + channel_strides: &[3, 3, 3], + x: 0, + y: 0, + width: self.resolution.x as u32, + height: self.resolution.y as u32, + data: &data, + }; + + client.send(packet).expect("TODO: panic message"); + + true + } + + fn send_create_image_packet(&self, client: &mut TevClient) -> std::io::Result<()> { + client.send(PacketCreateImage { + image_name: "TestImage", + grab_focus: false, + width: self.resolution.x as u32, + height: self.resolution.y as u32, + channel_names: &["R", "G", "B"], + }) + } +} + +/*impl ImageChannelBuffer { + fn send_if_changed(&mut self, ipc_channel: &mut TcpStream, tile_index: usize) -> bool { + let hash = murmur_hash64a(&self.buffer[self.channel_values_offset..], 0); + if let Some(&tile_hash) = self.tile_hashes.get(tile_index) { + if tile_hash == hash { + return true; + } + } + + let message_length = self.buffer.len(); + let message_length_bytes = (message_length as i32).to_ne_bytes(); + self.buffer.splice(..4, message_length_bytes); + + let sent = ipc_channel.write(&self.buffer); + if let Err(err) = sent { + dbg!(err); + return false; + } + + self.tile_hashes.insert(tile_index, hash); + true + } +}*/ + +fn get_thread_id() -> ThreadId { + thread::current().id() +} + +pub struct Preview<'a, T: Send + Sync> { + pub exit_thread: Arc, + dynamic_items: Arc>>>, + dynamic_channel: TcpStream, +} + +impl<'a, T: Send + Sync> Preview<'a, T> { + pub fn connect_to_display_server(host: &str) -> Result, TevError> { + let exit_thread = Arc::new(AtomicBool::new(false)); + let dynamic_channel = TcpStream::connect(host)?; + let dynamic_items: Arc>>> = Arc::new(Mutex::new(Vec::new())); + + Ok(Preview { + exit_thread, + dynamic_items, + dynamic_channel, + }) + } + + pub fn disconnect_from_display_server(&mut self) { + dbg!("Disconnecting from Tev"); + self.exit_thread.store(true, Ordering::Relaxed); + } + + pub fn display_dynamic( + mut self, + title: &str, + resolution: Point2i, + channel_names: Vec, + scope: &Scope<'a>, + arc: Arc<&'a RwLock>>, + get_tile_values: Function, + ) { + let cloned_exit_thread = self.exit_thread.clone(); + let cloned_dynamic_items = self.dynamic_items.clone(); + let mut cloned_channel = self.dynamic_channel.try_clone().unwrap(); + scope.spawn(move |_| { + update_dynamic_items( + cloned_exit_thread, + &mut cloned_channel, + cloned_dynamic_items, + ); + }); + + let cloned_items = self.dynamic_items.clone(); + let mut display_items = cloned_items.lock().unwrap(); + display_items.push(DisplayItem::new( + title, + resolution, + channel_names, + arc, + get_tile_values, + )); + } + + #[allow(unused)] + fn display_static( + &mut self, + title: &str, + resolution: Point2i, + vec: Pixels, + channel_names: Vec, + get_tile_values: Function, + ) { + let mut item = DisplayItem::new(title, resolution, channel_names, vec, get_tile_values); + let mut client = TevClient::wrap(self.dynamic_channel.try_clone().unwrap()); + if !item.display_with_tev_client(&mut client) { + println!("Unable to display static content {}", title); + } + } +} + +fn update_dynamic_items( + exit_thread: Arc, + channel: &mut TcpStream, + items: Arc>>>, +) { + let mut client = TevClient::wrap(channel.try_clone().expect("Could not clone TCP Channel")); + while !exit_thread.load(Ordering::Relaxed) { + thread::sleep(time::Duration::from_millis(250)); + + let mut items = items.lock().expect("Could not lock"); + for item in items.iter_mut() { + item.display_with_tev_client(&mut client); + } + } + + let mut items = items.lock().expect("Could not lock"); + for item in items.iter_mut() { + item.display_with_tev_client(&mut client); + } + + items.clear(); +} + +#[cfg(test)] +mod test { + use std::sync::{Arc, Mutex, RwLock}; + use std::sync::atomic::Ordering::Relaxed; + use std::thread; + use std::time; + use std::time::Duration; + + use crossbeam_utils::thread::Scope; + + use crate::core::display::Preview; + use crate::core::film::Pixel; + use crate::core::geometry::{Bounds2i, Point2i}; + use crate::core::pbrt::Float; + + #[ignore] + #[test] + /// Manual test for tev remote + fn display_remote() { + let address = "127.0.0.1:14158"; + + let display = Preview::connect_to_display_server(address); + // Do not fail the test if tev is not running + if display.is_err() { + return; + } + let mut display = display.unwrap(); + let exit_thread = display.exit_thread.clone(); + let resolution = Point2i { x: 200, y: 200 }; + + let mut image: Vec = Vec::with_capacity(resolution.x as usize); + for x in 0..resolution.x { + for y in 0..resolution.y { + + let mut pixel = Pixel::default(); + pixel.xyz = [x as Float / resolution.x as Float, y as Float / resolution.y as Float, 0.0]; + image.push(pixel); + } + } + + let data = &RwLock::new(image); + let arc = Arc::new(data); + + let get_values = move |b: Bounds2i, + arc: Arc<&RwLock>>, + width: usize, + values: &mut Vec>| { + for col in b.p_min.y as usize..b.p_max.y as usize { + for row in b.p_min.x as usize..b.p_max.x as usize { + let v = { + let clone = arc.read().unwrap(); + clone[col * width + row].xyz + }; + + for i in 0..3 { + values[i].push(v[i]); + } + } + } + }; + + // let display = Arc::new(Mutex::new(display)); + crossbeam::scope(|scope| { + display.display_dynamic( + "Test", + resolution, + vec!["R".to_string(), "G".to_string(), "B".to_string()], + scope, + arc.clone(), + get_values, + ); + + thread::sleep(time::Duration::from_millis(1000)); + for cols in 0..resolution.x as usize { + for rows in 0..resolution.y as usize { + let mut arc = arc.write().unwrap(); + arc[cols * resolution.x as usize + rows] = Pixel::default(); + } + } + thread::sleep(time::Duration::from_millis(1000)); + exit_thread.store(true, Relaxed); + }) + .unwrap(); + } + + #[test] + fn mutate_data_while_sharing() { + let num = (0, 0); + + let arc = Arc::new(Mutex::new(num)); + let clone = arc.clone(); + let clone2 = arc.clone(); + + crossbeam::scope(|scope| { + mutate(scope, clone); + for _ in 0..100 { + { + let mut arc = arc.lock().unwrap(); + arc.0 += 1; + } + thread::sleep(Duration::from_millis(3)); + } + }) + .unwrap(); + + let num = clone2.lock().unwrap(); + println!("{:?}", num); + println!("Done."); + } + + fn mutate(scope: &Scope, clone: Arc>) { + scope.spawn(move |_| { + for _ in 0..10 { + { + let mut clone = clone.lock().unwrap(); + println!("{:?}", clone.0); + clone.1 += 1; + } + thread::sleep(Duration::from_millis(2)); + } + }); + } +} diff --git a/src/core/film.rs b/src/core/film.rs index 8f48508..f419996 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -36,8 +36,8 @@ const FILTER_TABLE_WIDTH: usize = 16; #[derive(Debug, Clone)] pub struct Pixel { - xyz: [Float; 3], - filter_weight_sum: Float, + pub(crate) xyz: [Float; 3], + pub(crate) filter_weight_sum: Float, splat_xyz: [Float; 3], // pad: Float, } @@ -168,7 +168,7 @@ pub struct Film { // Film Private Data pub pixels: RwLock>, filter_table: [Float; FILTER_TABLE_WIDTH * FILTER_TABLE_WIDTH], - scale: Float, + pub scale: Float, max_sample_luminance: Float, } diff --git a/src/core/integrator.rs b/src/core/integrator.rs index 705466c..a176361 100644 --- a/src/core/integrator.rs +++ b/src/core/integrator.rs @@ -2,7 +2,8 @@ //! class that implements the **Integrator** interface. // std -use std::sync::Arc; +use std::sync::{Arc, Mutex, RwLock}; +use std::sync::atomic::Ordering; // pbrt use crate::blockqueue::BlockQueue; use crate::core::camera::{Camera, CameraSample}; @@ -25,6 +26,9 @@ use crate::integrators::path::PathIntegrator; use crate::integrators::sppm::SPPMIntegrator; use crate::integrators::volpath::VolPathIntegrator; use crate::integrators::whitted::WhittedIntegrator; +use crate::core::film::Pixel; +use crate::core::display::Preview; +use crate::core::spectrum::xyz_to_rgb; // see integrator.h @@ -36,12 +40,12 @@ pub enum Integrator { } impl Integrator { - pub fn render(&mut self, scene: &Scene, num_threads: u8) { + pub fn render(&mut self, scene: &Scene, num_threads: u8, display_server: Option) { match self { Integrator::BDPT(integrator) => integrator.render(scene, num_threads), Integrator::MLT(integrator) => integrator.render(scene, num_threads), Integrator::SPPM(integrator) => integrator.render(scene, num_threads), - Integrator::Sampler(integrator) => integrator.render(scene, num_threads), + Integrator::Sampler(integrator) => integrator.render(scene, num_threads, display_server), } } } @@ -67,7 +71,7 @@ impl SamplerIntegrator { /// All [SamplerIntegrators](enum.SamplerIntegrator.html) use the /// same render loop, but call an individual /// [li()](enum.SamplerIntegrator.html#method.li) method. - pub fn render(&mut self, scene: &Scene, num_threads: u8) { + pub fn render(&mut self, scene: &Scene, num_threads: u8, display_server: Option) { let film = self.get_camera().get_film(); let sample_bounds: Bounds2i = film.get_sample_bounds(); self.preprocess(scene); @@ -98,6 +102,7 @@ impl SamplerIntegrator { let camera = &self.get_camera(); let film = &film; let pixel_bounds = &self.get_pixel_bounds(); + let address = display_server.unwrap_or("".to_string()); crossbeam::scope(|scope| { let (pixel_tx, pixel_rx) = crossbeam_channel::bounded(num_cores); // spawn worker threads @@ -140,7 +145,7 @@ impl SamplerIntegrator { ray.scale_differentials( 1.0 as Float / (tile_sampler.get_samples_per_pixel() as Float) - .sqrt(), + .sqrt(), ); // TODO: ++nCameraRays; // evaluate radiance along camera ray @@ -207,14 +212,64 @@ impl SamplerIntegrator { } // spawn thread to collect pixels and render image to file scope.spawn(move |_| { - for _ in pbr::PbIter::new(0..bq.len()) { - let film_tile = pixel_rx.recv().unwrap(); - // merge image tile into _Film_ - film.merge_film_tile(&film_tile); - } + crossbeam::scope(|sub_scope| { + // This should not + let display = Preview::connect_to_display_server(&address); + let connected = display.is_ok(); + let exit_thread_clone = if connected { + Some(display.as_ref().unwrap().exit_thread.clone()) + } else { + eprintln!("Could not connect to tev"); + None + }; + + if connected { + let display = display.unwrap(); + let arc = Arc::new(&film.pixels); + + // If we always need this function and never need another one we can move this inside the display + let get_values = move |b: Bounds2i, arc: Arc<&RwLock>>, width: usize, values: &mut Vec>| { + for col in b.p_min.y..b.p_max.y { + for row in b.p_min.x..b.p_max.x { + let v = { + let vec = arc.read().unwrap(); + let mut rgb = [0.0; 3]; + let pixels = &vec[col as usize * width + row as usize]; + xyz_to_rgb(&pixels.xyz, &mut rgb); + + let filter_weight_sum = pixels.filter_weight_sum; + if filter_weight_sum != 0.0 as Float { + let inv_wt: Float = 1.0 as Float / filter_weight_sum; + rgb[0] = (rgb[0] * inv_wt).max(0.0 as Float); + rgb[1] = (rgb[1] * inv_wt).max(0.0 as Float); + rgb[2] = (rgb[2] * inv_wt).max(0.0 as Float); + } + rgb + }; + + for (channel, value) in values.iter_mut().zip(v) { + // Todo: We probably need to scale the values here? + channel.push(value); + } + } + } + }; + + display.display_dynamic("Test", film.full_resolution, + vec!["R".to_string(), "G".to_string(), "B".to_string()], + sub_scope, arc, get_values); + } + for _ in pbr::PbIter::new(0..bq.len()) { + let film_tile = pixel_rx.recv().unwrap(); + // merge image tile into _Film_ + film.merge_film_tile(&film_tile); + } + if let Some(exit) = exit_thread_clone { + exit.store(true, Ordering::Relaxed); + } + }).unwrap_or_default(); }); - }) - .unwrap(); + }).expect("What am I even?"); } film.write_image(1.0 as Float); } diff --git a/src/core/mod.rs b/src/core/mod.rs index bd218a7..77ae441 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -3,6 +3,7 @@ pub mod api; pub mod bssrdf; pub mod camera; +pub mod display; pub mod efloat; pub mod film; pub mod filter;