diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c3c6cfb..10c9a27 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -14,6 +14,8 @@ num-integer = "0.1.46" num-traits = "0.2.19" once_cell = "1.21.3" smallvec = "1.15.1" +cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } [features] -cuda = [] +use_f64 = [] +cuda = ["cuda_std"] diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index 84ee64c..424f346 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -1,37 +1,29 @@ -use crate::camera::CameraTransform; -use crate::core::filter::{Filter, FilterTrait}; +use crate::core::camera::CameraTransform; +use crate::core::filter::Filter; use crate::core::geometry::{ Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Vector2i, Vector3f, }; use crate::core::interaction::SurfaceInteraction; use crate::core::pbrt::Float; -use crate::image::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat}; +use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat}; use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance}; use crate::spectra::data::generate_cie_d; use crate::spectra::{ ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum, - SpectrumProvider, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum, + SpectrumTrait, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum, }; use crate::utils::AtomicFloat; use crate::utils::containers::Array2D; use crate::utils::math::linear_least_squares; use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; -use crate::utils::parameters::ParameterDictionary; use crate::utils::sampling::VarianceEstimator; use crate::utils::transform::AnimatedTransform; -use enum_dispatch::enum_dispatch; -use pbrt::utils::error::FileLoc; -// use rayon::prelude::*; -use std::path::Path; -use std::sync::{Arc, OnceLock, atomic::AtomicUsize, atomic::Ordering}; +use std::sync::Arc; -use once_cell::sync::Lazy; -use std::error::Error; -use std::sync::Mutex; - -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct RGBFilm { pub base: FilmBase, pub max_component_value: Float, @@ -41,31 +33,15 @@ pub struct RGBFilm { pub pixels: Arc>, } -#[derive(Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct RGBPixel { rgb_sum: [AtomicFloat; 3], weight_sum: AtomicFloat, rgb_splat: [AtomicFloat; 3], } -impl Default for RGBPixel { - fn default() -> Self { - Self { - rgb_sum: [ - AtomicFloat::new(0.0), - AtomicFloat::new(0.0), - AtomicFloat::new(0.0), - ], - weight_sum: AtomicFloat::new(0.0), - rgb_splat: [ - AtomicFloat::new(0.0), - AtomicFloat::new(0.0), - AtomicFloat::new(0.0), - ], - } - } -} - +#[cfg(not(target_os = "cuda"))] impl RGBFilm { pub fn new( base: FilmBase, @@ -73,12 +49,13 @@ impl RGBFilm { max_component_value: Float, write_fp16: bool, ) -> Self { + let sensor_ptr = base.sensor; + if sensor_ptr.is_null() { + panic!("Film must have a sensor"); + } + let sensor = unsafe { &*self.sensor }; let filter_integral = base.filter.integral(); - let sensor_matrix = base - .sensor - .as_ref() - .expect("Sensor must exist") - .xyz_from_sensor_rgb; + let sensor_matrix = sensor.xyz_from_sensor_rgb; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix; let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x(); @@ -101,29 +78,116 @@ impl RGBFilm { pixels: Arc::new(pixels_array), } } +} - pub fn create( - params: &ParameterDictionary, - exposure_time: Float, - filter: Filter, - _camera_transform: Option, - loc: &FileLoc, - ) -> Result { - let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); - let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::new(params, filter, Some(sensor), loc); - Ok(RGBFilm::new( - film_base, - &colorspace, - max_component_value, - write_fp16, - )) +impl RGBFilm { + pub fn base(&self) -> &FilmBase { + &self.base + } + + pub fn base_mut(&mut self) -> &mut FilmBase { + &mut self.base + } + + #[cfg(not(target_os = "cuda"))] + pub fn get_sensor(&self) -> Result<&PixelSensor, String> { + if self.sensor.is_null() { + return Err("FilmBase error: PixelSensor pointer is null.".to_string()); + } + Ok(unsafe { &*self.sensor }) + } + + pub fn add_sample( + &self, + p_film: Point2i, + l: SampledSpectrum, + lambda: &SampledWavelengths, + _vi: Option<&VisibleSurface>, + weight: Float, + ) { + let sensor = unsafe { self.get_sensor() }; + let mut rgb = sensor.to_sensor_rgb(l, lambda); + let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); + if m > self.max_component_value { + rgb *= self.max_component_value / m; + } + + let pixel = &self.pixels[p_film]; + for c in 0..3 { + pixel.rgb_sum[c].add((weight * rgb[c]) as f64); + } + pixel.weight_sum.add(weight as f64); + } + + pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { + let sensor = unsafe { self.get_sensor() }; + let mut rgb = sensor.to_sensor_rgb(l, lambda); + let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); + if m > self.max_component_value { + rgb *= self.max_component_value / m; + } + + let p_discrete = p + Vector2f::new(0.5, 0.5); + let radius = self.get_filter().radius(); + + let splat_bounds = Bounds2i::from_points( + (p_discrete - radius).floor(), + (p_discrete + radius).floor() + Vector2i::new(1, 1), + ); + + let splat_intersect = splat_bounds.union(self.pixel_bounds()); + for pi in &splat_intersect { + let pi_f: Point2f = (*pi).into(); + let wt = self + .get_filter() + .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); + if wt != 0. { + let pixel = &self.pixels[*pi]; + for i in 0..3 { + pixel.rgb_splat[i].add((wt * rgb[i]) as f64); + } + } + } + } + + pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { + let pixel = unsafe { &self.pixels.get(p.x(), p.y())[p] }; + let mut rgb = RGB::new( + pixel.rgb_sum[0].load() as Float, + pixel.rgb_sum[1].load() as Float, + pixel.rgb_sum[2].load() as Float, + ); + let weight_sum = pixel.weight_sum.load(); + if weight_sum != 0. { + rgb /= weight_sum as Float + } + + if let Some(splat) = splat_scale { + for c in 0..3 { + let splat_val = pixel.rgb_splat[c].load(); + rgb[c] += splat * splat_val as Float / self.filter_integral; + } + } else { + for c in 0..3 { + let splat_val = pixel.rgb_splat[c].load(); + rgb[c] += splat_val as Float / self.filter_integral; + } + } + self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) + } + + pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { + let sensor = unsafe { self.get_sensor() }; + let mut sensor_rgb = sensor.to_sensor_rgb(l, lambda); + self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) + } + + fn uses_visible_surface(&self) -> bool { + false } } -#[derive(Debug, Default)] +#[derive(Debug, Clone, Copy, Default)] struct GBufferPixel { pub rgb_sum: [AtomicFloat; 3], pub weight_sum: AtomicFloat, @@ -144,7 +208,7 @@ pub struct GBufferFilm { pub base: FilmBase, output_from_render: AnimatedTransform, apply_inverse: bool, - pixels: Arc>, + pixels: Array2D, colorspace: RGBColorSpace, max_component_value: Float, write_fp16: bool, @@ -152,6 +216,7 @@ pub struct GBufferFilm { output_rgbf_from_sensor_rgb: SquareMatrix, } +#[cfg(not(target_os = "cuda"))] impl GBufferFilm { pub fn new( base: &FilmBase, @@ -162,8 +227,12 @@ impl GBufferFilm { write_fp16: bool, ) -> Self { assert!(!base.pixel_bounds.is_empty()); - let output_rgbf_from_sensor_rgb = - colorspace.rgb_from_xyz * base.sensor.as_ref().unwrap().xyz_from_sensor_rgb; + let sensor_ptr = base.sensor; + if sensor_ptr.is_null() { + panic!("Film must have a sensor"); + } + let sensor = unsafe { &*sensor_ptr }; + let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb; let filter_integral = base.filter.integral(); let pixels = Array2D::new(base.pixel_bounds); @@ -171,7 +240,7 @@ impl GBufferFilm { base: base.clone(), output_from_render: output_from_render.clone(), apply_inverse, - pixels: pixels.into(), + pixels, colorspace: colorspace.clone(), max_component_value, write_fp16, @@ -179,54 +248,106 @@ impl GBufferFilm { output_rgbf_from_sensor_rgb, } } +} - pub fn create( - params: &ParameterDictionary, - exposure_time: Float, - filter: Filter, - camera_transform: Option, - loc: &FileLoc, - ) -> Result { - let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); - let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::new(params, filter, Some(sensor), loc); +impl GBufferFilm { + pub fn base(&self) -> &FilmBase { + &self.base + } - if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) { - return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into()); + pub fn base_mut(&mut self) -> &mut FilmBase { + &mut self.base + } + + #[cfg(not(target_os = "cuda"))] + pub fn get_sensor(&self) -> Result<&PixelSensor, String> { + if self.sensor.is_null() { + return Err("FilmBase error: PixelSensor pointer is null.".to_string()); + } + Ok(unsafe { &*self.sensor }) + } + + pub unsafe fn get_sensor(&self) -> &PixelSensor { + #[cfg(not(target_os = "cuda"))] + { + if self.sensor.is_null() { + panic!("FilmBase: PixelSensor pointer is null"); + } } - let coords_system = params.get_one_string("coordinatesystem", "camera"); - let mut apply_inverse = false; - let camera_transform = camera_transform - .ok_or_else(|| "GBufferFilm requires a camera_transform".to_string())?; - let output_from_render = if coords_system == "camera" { - apply_inverse = true; - camera_transform.render_from_camera - } else if coords_system == "world" { - AnimatedTransform::from_transform(&camera_transform.world_from_render) - } else { - return Err(format!( - "{}: unknown coordinate system for GBufferFilm. (Expecting camera - or world", - loc - ) - .into()); - }; + &*self.sensor + } - Ok(GBufferFilm::new( - &film_base, - &output_from_render, - apply_inverse, - colorspace, - max_component_value, - write_fp16, - )) + pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { + let sensor = unsafe { self.get_pixel_sensor() }; + let mut rgb = sensor.to_sensor_rgb(l, lambda); + let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); + if m > self.max_component_value { + rgb *= self.max_component_value / m; + } + + let p_discrete = p + Vector2f::new(0.5, 0.5); + let radius = self.get_filter().radius(); + + let splat_bounds = Bounds2i::from_points( + (p_discrete - radius).floor(), + (p_discrete + radius).floor() + Vector2i::new(1, 1), + ); + + let splat_intersect = splat_bounds.union(self.pixel_bounds()); + for pi in &splat_intersect { + let pi_f: Point2f = (*pi).into(); + let wt = self + .get_filter() + .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); + if wt != 0. { + let pixel = &self.pixels[*pi]; + for i in 0..3 { + pixel.rgb_splat[i].add((wt * rgb[i]) as f64); + } + } + } + } + + pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { + let sensor = unsafe { self.get_pixel_sensor() }; + let sensor_rgb = sensor.to_sensor_rgb(l, lambda); + self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) + } + + pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { + let pixel = unsafe { &self.pixels.get(p.x(), p.y()) }; + let mut rgb = RGB::new( + pixel.rgb_sum[0].load() as Float, + pixel.rgb_sum[1].load() as Float, + pixel.rgb_sum[2].load() as Float, + ); + let weight_sum = pixel.weight_sum.load(); + if weight_sum != 0. { + rgb /= weight_sum as Float + } + + if let Some(splat) = splat_scale { + for c in 0..3 { + let splat_val = pixel.rgb_splat[c].load(); + rgb[c] += splat * splat_val as Float / self.filter_integral; + } + } else { + for c in 0..3 { + let splat_val = pixel.rgb_splat[c].load(); + rgb[c] += splat_val as Float / self.filter_integral; + } + } + self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) + } + + pub fn uses_visible_surface(&self) -> bool { + true } } -#[derive(Default, Debug)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] pub struct SpectralPixel { pub rgb_sum: [AtomicFloat; 3], pub rgb_weigh_sum: AtomicFloat, @@ -241,7 +362,8 @@ pub struct SpectralPixelView<'a> { pub bucket_splats: &'a [AtomicFloat], } -#[derive(Debug)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct SpectralFilm { pub base: FilmBase, pub colorspace: RGBColorSpace, @@ -258,6 +380,7 @@ pub struct SpectralFilm { pub bucket_splats: Vec, } +#[cfg(not(target_os = "cuda"))] impl SpectralFilm { pub fn new( base: &FilmBase, @@ -269,9 +392,12 @@ impl SpectralFilm { write_fp16: bool, ) -> Self { assert!(!base.pixel_bounds.is_empty()); - let sensor = &base.sensor; - let output_rgbf_from_sensor_rgb = - colorspace.rgb_from_xyz * sensor.as_ref().unwrap().xyz_from_sensor_rgb; + let sensor_ptr = base.sensor; + if sensor_ptr.is_null() { + panic!("Film must have a sensor"); + } + let sensor = unsafe { &*sensor_ptr }; + let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb; let n_pixels = base.pixel_bounds.area() as usize; let total_bucket_count = n_pixels * n_buckets; let bucket_sums = vec![0.0; total_bucket_count]; @@ -302,47 +428,23 @@ impl SpectralFilm { output_rgbf_from_sensor_rgb, } } +} - pub fn create( - params: &ParameterDictionary, - exposure_time: Float, - filter: Filter, - _camera_transform: Option, - loc: &FileLoc, - ) -> Result { - let colorspace = params.color_space.as_ref().unwrap(); - let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY); - let write_fp16 = params.get_one_bool("savefp16", true); - let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::new(params, filter, Some(sensor), loc); +impl SpectralFilm { + pub fn base(&self) -> &FilmBase { + &self.base + } - if Path::new(&film_base.filename).extension() != Some("exr".as_ref()) { - return Err(format!("{}: EXR is the only format supported by GBufferFilm", loc).into()); - } + pub fn base_mut(&mut self) -> &mut FilmBase { + &mut self.base + } - let n_buckets = params.get_one_int("nbuckets", 16) as usize; - let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float); - let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float); - if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float { - return Err(format!( - "{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX", - loc - )); - } - - Ok(SpectralFilm::new( - &film_base, - lambda_min, - lambda_max, - n_buckets, - colorspace, - max_component_value, - write_fp16, - )) + fn uses_visible_surface(&self) -> bool { + true } pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView { - let metadata = &self.pixels[p]; + let metadata = unsafe { &self.pixels.get(p.x(), p.y()) }; let start = metadata.bucket_offset; let end = start + self.n_buckets; @@ -355,32 +457,27 @@ impl SpectralFilm { } } -const N_SWATCH_REFLECTANCES: usize = 24; -static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> = Lazy::new(|| { - std::array::from_fn(|i| { - let raw_data = crate::core::cie::SWATCHES_RAW[i]; - let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false); - Spectrum::PiecewiseLinear(pls) - }) -}); - -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct PixelSensor { pub xyz_from_sensor_rgb: SquareMatrix, - r_bar: DenselySampledSpectrum, - g_bar: DenselySampledSpectrum, - b_bar: DenselySampledSpectrum, - imaging_ratio: f32, + pub r_bar: DenselySampledSpectrum, + pub g_bar: DenselySampledSpectrum, + pub b_bar: DenselySampledSpectrum, + pub imaging_ratio: Float, } +#[cfg(not(target_os = "cuda"))] impl PixelSensor { + const N_SWATCH_REFLECTANCES: usize = 24; pub fn new( - r: Arc, - g: Arc, - b: Arc, - output_colorspace: Arc, + r: Spectrum, + g: Spectrum, + b: Spectrum, + output_colorspace: RGBColorSpace, sensor_illum: Option>, imaging_ratio: Float, + swatches: &[Spectrum; 24], ) -> Result> { // As seen in usages of this constructos, sensor_illum can be null // Going with the colorspace's own illuminant, but this might not be the right choice @@ -395,9 +492,11 @@ impl PixelSensor { let b_bar = DenselySampledSpectrum::from_spectrum(&b); let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; + let swatches = Self::get_swatches(); + for i in 0..N_SWATCH_REFLECTANCES { let rgb = Self::project_reflectance::( - &SWATCH_REFLECTANCES[i], + &swatches[i], illum, &Spectrum::DenselySampled(r_bar.clone()), &Spectrum::DenselySampled(g_bar.clone()), @@ -412,7 +511,7 @@ impl PixelSensor { let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone())); let sensor_white_y = illum.inner_product(cie_y()); for i in 0..N_SWATCH_REFLECTANCES { - let s = SWATCH_REFLECTANCES[i].clone(); + let s = swatches[i].clone(); let xyz = Self::project_reflectance::( &s, &output_colorspace.illuminant, @@ -437,7 +536,7 @@ impl PixelSensor { } pub fn new_with_white_balance( - output_colorspace: Arc, + output_colorspace: &RGBColorSpace, sensor_illum: Option>, imaging_ratio: Float, ) -> Self { @@ -463,66 +562,6 @@ impl PixelSensor { } } - pub fn create( - params: &ParameterDictionary, - output_colorspace: Arc, - exposure_time: Float, - loc: &FileLoc, - ) -> Result { - let iso = params.get_one_float("iso", 100.); - let mut white_balance_temp = params.get_one_float("whitebalance", 0.); - let sensor_name = params.get_one_string("sensor", "cie1931"); - if sensor_name != "cie1931" && white_balance_temp == 0. { - white_balance_temp = 6500.; - } - let imaging_ratio = exposure_time * iso / 100.; - - let d_illum = if white_balance_temp == 0. { - generate_cie_d(6500.) - } else { - generate_cie_d(white_balance_temp) - }; - - let sensor_illum: Option> = if white_balance_temp != 0. { - Some(Arc::new(Spectrum::DenselySampled(d_illum))) - } else { - None - }; - - if sensor_name == "cie1931" { - return Ok(PixelSensor::new_with_white_balance( - output_colorspace, - sensor_illum, - imaging_ratio, - )); - } else { - let r_opt = get_named_spectrum(&format!("{}_r", sensor_name)); - let g_opt = get_named_spectrum(&format!("{}_g", sensor_name)); - let b_opt = get_named_spectrum(&format!("{}_b", sensor_name)); - if r_opt.is_none() || g_opt.is_none() || b_opt.is_none() { - return Err(format!( - "{}: unknown sensor type '{}' (missing RGB spectral data)", - loc, sensor_name - ) - .into()); - } - - let r = Arc::new(r_opt.unwrap()); - let g = Arc::new(g_opt.unwrap()); - let b = Arc::new(b_opt.unwrap()); - - return PixelSensor::new( - r, - g, - b, - output_colorspace.clone(), - sensor_illum, - imaging_ratio, - ) - .map_err(|e| e.to_string()); - } - } - pub fn project_reflectance( refl: &Spectrum, illum: &Spectrum, @@ -553,7 +592,9 @@ impl PixelSensor { T::from_triplet(result[0], result[1], result[2]) } +} +impl PixelSensor { pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf()); self.imaging_ratio @@ -565,6 +606,8 @@ impl PixelSensor { } } +#[repr(C)] +#[derive(Default, Copy, Clone)] pub struct VisibleSurface { pub p: Point3f, pub n: Normal3f, @@ -590,213 +633,85 @@ impl VisibleSurface { } } -impl Default for VisibleSurface { - fn default() -> Self { - Self { - p: Point3f::default(), - n: Normal3f::default(), - ns: Normal3f::default(), - uv: Point2f::default(), - time: 0., - dpdx: Vector3f::default(), - dpdy: Vector3f::default(), - albedo: SampledSpectrum::default(), - set: false, - } - } -} - -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Default, Copy, Clone)] pub struct FilmBase { pub full_resolution: Point2i, pub pixel_bounds: Bounds2i, pub filter: Filter, pub diagonal: Float, - pub sensor: Option, - pub filename: String, + pub sensor: *const PixelSensor, } -impl FilmBase { - pub fn new( - params: &ParameterDictionary, - filter: Filter, - sensor: Option, - loc: &FileLoc, - ) -> Self { - let x_res = params.get_one_int("xresolution", 1280); - let y_res = params.get_one_int("yresolution", 720); +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum Film { + RGB(RGBFilm), + GBuffer(GBufferFilm), + Spectral(SpectralFilm), +} - if x_res <= 0 || y_res <= 0 { - eprintln!( - "{}: Film resolution must be > 0. Defaulting to 1280x720.", - loc - ); - } - let full_resolution = Point2i::new(x_res.max(1), y_res.max(1)); - - let crop_data = params.get_float_array("cropwindow"); - let crop = if crop_data.len() == 4 { - Bounds2f::from_points( - Point2f::new(crop_data[0], crop_data[2]), - Point2f::new(crop_data[1], crop_data[3]), - ) - } else { - Bounds2f::from_points(Point2f::zero(), Point2f::new(1.0, 1.0)) - }; - - let p_min = Point2i::new( - (full_resolution.x() as Float * crop.p_min.x()).ceil() as i32, - (full_resolution.y() as Float * crop.p_min.y()).ceil() as i32, - ); - let p_max = Point2i::new( - (full_resolution.x() as Float * crop.p_max.x()).ceil() as i32, - (full_resolution.y() as Float * crop.p_max.y()).ceil() as i32, - ); - - let mut pixel_bounds = Bounds2i::from_points(p_min, p_max); - - if pixel_bounds.is_empty() { - eprintln!("{}: Film crop window results in empty pixel bounds.", loc); - } - - let rad = filter.radius(); - let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32); - pixel_bounds = pixel_bounds.expand(expansion); - - let diagonal_mm = params.get_one_float("diagonal", 35.0); - let filename = params.get_one_string("filename", "pbrt.exr"); - - Self { - full_resolution, - pixel_bounds, - filter, - diagonal: diagonal_mm * 0.001, - sensor, - filename, +impl Film { + fn base(&self) -> &FilmBase { + match self { + Film::RGB(f) => f.base(), + Film::GBuffer(f) => f.base(), + Film::Spectral(f) => f.base(), } } -} -#[enum_dispatch] -pub trait FilmTrait: Sync { - fn base(&self) -> &FilmBase; - fn base_mut(&mut self) -> &mut FilmBase; - fn add_sample( + pub fn base(&mut self) -> &mut FilmBase { + match self { + Film::RGB(f) => f.base_mut(), + Film::GBuffer(f) => f.base_mut(), + Film::Spectral(f) => f.base_mut(), + } + } + + pub fn add_sample( &self, - _p_filme: Point2i, - _l: SampledSpectrum, - _lambda: &SampledWavelengths, - _visible_surface: Option<&VisibleSurface>, - _weight: Float, + p_film: Point2i, + l: SampledSpectrum, + lambda: &SampledWavelengths, + visible_surface: Option<&VisibleSurface>, + weight: Float, ) { - todo!() - } - fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) { - todo!() - } - - fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) { - let image = self.get_image(metadata, splat_scale); - image - .write(self.get_filename(), metadata) - .expect("Something") - } - - fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { - let write_fp16 = true; - let format = if write_fp16 { - PixelFormat::F16 - } else { - PixelFormat::F32 - }; - - let channel_names = &["R", "G", "B"]; - - let pixel_bounds = self.base().pixel_bounds; - let resolution = Point2i::from(pixel_bounds.diagonal()); - - let n_clamped = Arc::new(AtomicUsize::new(0)); - let processed_rows: Vec> = (pixel_bounds.p_min.y()..pixel_bounds.p_max.y()) - .into_par_iter() - .map(|y| { - let n_clamped = Arc::clone(&n_clamped); - let mut row_data = Vec::with_capacity(resolution.x() as usize * 3); - for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { - let p = Point2i::new(x, y); - let mut rgb = self.get_pixel_rgb(p, Some(splat_scale)); - let mut was_clamped = false; - if write_fp16 { - if rgb.r > 65504.0 { - rgb.r = 65504.0; - was_clamped = true; - } - if rgb.g > 65504.0 { - rgb.g = 65504.0; - was_clamped = true; - } - if rgb.b > 65504.0 { - rgb.b = 65504.0; - was_clamped = true; - } - } - if was_clamped { - n_clamped.fetch_add(1, Ordering::SeqCst); - } - row_data.push(rgb.r); - row_data.push(rgb.g); - row_data.push(rgb.b); - } - row_data - }) - .collect(); - - let mut image = Image::new(format, resolution, channel_names, SRGB); - let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); - - for (iy, row_data) in processed_rows.into_iter().enumerate() { - for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() { - let p_offset = Point2i::new(ix as i32, iy as i32); - let values = ImageChannelValues::from(rgb_chunk); - image.set_channels(p_offset, &rgb_desc, &values); - } + match self { + Film::RGB(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), + Film::GBuffer(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), + Film::Spectral(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), } + } - let clamped_count = n_clamped.load(Ordering::SeqCst); - if clamped_count > 0 { - println!( - "{} pixel values clamped to maximum fp16 value.", - clamped_count - ); + pub fn add_splat(&mut self, p: Point2f, v: SampledSpectrum, lambda: &SampledWavelengths) { + match self { + Film::RGB(f) => f.add_splat(p, v, lambda), + Film::GBuffer(f) => f.add_splat(p, v, lambda), + Film::Spectral(f) => f.add_splat(p, v, lambda), } - - // self.base().pixel_bounds = pixel_bounds; - // self.base().full_resolution = resolution; - // self.colorspace = colorspace; - - image } - fn get_pixel_rgb(&self, _p: Point2i, _splat_scale: Option) -> RGB { - todo!() - } - fn reset_pixel(&mut self, _p: Point2i) { - todo!() - } - fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB { - todo!() - } - fn sample_wavelengths(&self, u: Float) -> SampledWavelengths { + pub fn sample_wavelengths(&self, u: Float) -> SampledWavelengths { SampledWavelengths::sample_visible(u) } - fn uses_visible_surface(&self) -> bool; - fn full_resolution(&self) -> Point2i { + pub fn uses_visible_surface(&self) -> bool { + match self { + Film::RGB(f) => f.uses_visible_surface(), + Film::GBuffer(f) => f.uses_visible_surface(), + Film::Spectral(f) => f.uses_visible_surface(), + } + } + + pub fn full_resolution(&self) -> Point2i { self.base().full_resolution } - fn pixel_bounds(&self) -> Bounds2i { + + pub fn pixel_bounds(&self) -> Bounds2i { self.base().pixel_bounds } - fn sample_bounds(&self) -> Bounds2f { + + pub fn sample_bounds(&self) -> Bounds2f { let pixel_bounds = self.pixel_bounds(); let radius = self.get_filter().radius(); Bounds2f::from_points( @@ -805,263 +720,23 @@ pub trait FilmTrait: Sync { ) } - fn diagonal(&self) -> Float { + pub fn diagonal(&self) -> Float { self.base().diagonal } - fn get_filter(&self) -> &Filter { + + pub fn get_filter(&self) -> &Filter { &self.base().filter } - fn get_pixel_sensor(&self) -> Option<&PixelSensor> { - self.base().sensor.as_ref() + + pub fn get_pixel_rgb(&self, _p: Point2i, _splat_scale: Option) -> RGB { + todo!() } - fn get_filename(&self) -> &String { - &self.base().filename - } -} - -#[enum_dispatch(FilmTrait)] -#[derive(Debug)] -pub enum Film { - RGB(RGBFilm), - GBuffer(GBufferFilm), - Spectral(SpectralFilm), -} - -impl Film { - pub fn create( - name: &str, - params: &ParameterDictionary, - exposure_time: Float, - filter: Filter, - camera_transform: Option, - loc: &FileLoc, - ) -> Result { - match name { - "rgb" => { - let film = RGBFilm::create(params, exposure_time, filter, camera_transform, loc)?; - - Ok(Film::RGB(film)) - } - "gbuffer" => { - let film = - GBufferFilm::create(params, exposure_time, filter, camera_transform, loc)?; - - Ok(Film::GBuffer(film)) - } - "spectral" => { - let film = - SpectralFilm::create(params, exposure_time, filter, camera_transform, loc)?; - - Ok(Film::Spectral(film)) - } - _ => Err(format!("Film type '{}' unknown at {}", name, loc)), - } - } -} - -impl FilmTrait for RGBFilm { - fn base(&self) -> &FilmBase { - &self.base - } - - fn base_mut(&mut self) -> &mut FilmBase { - &mut self.base - } - - fn add_sample( - &self, - p_film: Point2i, - l: SampledSpectrum, - lambda: &SampledWavelengths, - _vi: Option<&VisibleSurface>, - weight: Float, - ) { - let mut rgb = self - .get_pixel_sensor() - .expect("Sensor must exist") - .to_sensor_rgb(l, lambda); - let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); - if m > self.max_component_value { - rgb *= self.max_component_value / m; - } - - let pixel = &self.pixels[p_film]; - for c in 0..3 { - pixel.rgb_sum[c].add((weight * rgb[c]) as f64); - } - pixel.weight_sum.add(weight as f64); - } - - fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { - let mut rgb = self - .get_pixel_sensor() - .expect("Sensor must exist") - .to_sensor_rgb(l, lambda); - let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); - if m > self.max_component_value { - rgb *= self.max_component_value / m; - } - - let p_discrete = p + Vector2f::new(0.5, 0.5); - let radius = self.get_filter().radius(); - - let splat_bounds = Bounds2i::from_points( - (p_discrete - radius).floor(), - (p_discrete + radius).floor() + Vector2i::new(1, 1), - ); - - let splat_intersect = splat_bounds.union(self.pixel_bounds()); - for pi in &splat_intersect { - let pi_f: Point2f = (*pi).into(); - let wt = self - .get_filter() - .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); - if wt != 0. { - let pixel = &self.pixels[*pi]; - for i in 0..3 { - pixel.rgb_splat[i].add((wt * rgb[i]) as f64); - } - } - } - } - - fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { - let pixel = &self.pixels[p]; - let mut rgb = RGB::new( - pixel.rgb_sum[0].load() as Float, - pixel.rgb_sum[1].load() as Float, - pixel.rgb_sum[2].load() as Float, - ); - let weight_sum = pixel.weight_sum.load(); - if weight_sum != 0. { - rgb /= weight_sum as Float - } - - if let Some(splat) = splat_scale { - for c in 0..3 { - let splat_val = pixel.rgb_splat[c].load(); - rgb[c] += splat * splat_val as Float / self.filter_integral; - } - } else { - for c in 0..3 { - let splat_val = pixel.rgb_splat[c].load(); - rgb[c] += splat_val as Float / self.filter_integral; - } - } - self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) - } - - fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { - let sensor_rgb = self - .get_pixel_sensor() - .expect("Sensor must exist") - .to_sensor_rgb(l, lambda); - self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) - } - - fn uses_visible_surface(&self) -> bool { - false - } - - fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) { - let image = self.get_image(metadata, splat_scale); - image - .write(self.get_filename(), metadata) - .expect("Something please") - } -} - -impl FilmTrait for GBufferFilm { - fn base(&self) -> &FilmBase { - &self.base - } - - fn base_mut(&mut self) -> &mut FilmBase { - &mut self.base - } - - fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { - let mut rgb = self - .get_pixel_sensor() - .expect("Sensor must exist") - .to_sensor_rgb(l, lambda); - let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); - if m > self.max_component_value { - rgb *= self.max_component_value / m; - } - - let p_discrete = p + Vector2f::new(0.5, 0.5); - let radius = self.get_filter().radius(); - - let splat_bounds = Bounds2i::from_points( - (p_discrete - radius).floor(), - (p_discrete + radius).floor() + Vector2i::new(1, 1), - ); - - let splat_intersect = splat_bounds.union(self.pixel_bounds()); - for pi in &splat_intersect { - let pi_f: Point2f = (*pi).into(); - let wt = self - .get_filter() - .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); - if wt != 0. { - let pixel = &self.pixels[*pi]; - for i in 0..3 { - pixel.rgb_splat[i].add((wt * rgb[i]) as f64); - } - } - } - } - - fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { - let sensor_rgb = self - .get_pixel_sensor() - .expect("Sensor must exist") - .to_sensor_rgb(l, lambda); - self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) - } - - fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { - let pixel = &self.pixels[p]; - let mut rgb = RGB::new( - pixel.rgb_sum[0].load() as Float, - pixel.rgb_sum[1].load() as Float, - pixel.rgb_sum[2].load() as Float, - ); - let weight_sum = pixel.weight_sum.load(); - if weight_sum != 0. { - rgb /= weight_sum as Float - } - - if let Some(splat) = splat_scale { - for c in 0..3 { - let splat_val = pixel.rgb_splat[c].load(); - rgb[c] += splat * splat_val as Float / self.filter_integral; - } - } else { - for c in 0..3 { - let splat_val = pixel.rgb_splat[c].load(); - rgb[c] += splat_val as Float / self.filter_integral; - } - } - self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) - } - - fn uses_visible_surface(&self) -> bool { - true - } -} - -impl FilmTrait for SpectralFilm { - fn base(&self) -> &FilmBase { - &self.base - } - - fn base_mut(&mut self) -> &mut FilmBase { - &mut self.base - } - - fn uses_visible_surface(&self) -> bool { - true + + pub fn reset_pixel(&mut self, _p: Point2i) { + todo!() + } + + pub fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB { + todo!() } } diff --git a/shared/src/core/filter.rs b/shared/src/core/filter.rs index 984718f..b92e608 100644 --- a/shared/src/core/filter.rs +++ b/shared/src/core/filter.rs @@ -1,9 +1,7 @@ use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::pbrt::Float; use crate::utils::containers::Array2D; -use crate::utils::error::FileLoc; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; -use crate::utils::parameters::ParameterDictionary; use crate::utils::sampling::PiecewiseConstant2D; use enum_dispatch::enum_dispatch; @@ -19,6 +17,7 @@ pub struct FilterSampler { f: Array2D, } +#[cfg(not(target_os = "cuda"))] impl FilterSampler { pub fn new(radius: Vector2f, func: F) -> Self where @@ -45,27 +44,21 @@ impl FilterSampler { let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain); Self { domain, f, distrib } } +} - /// Samples the filter's distribution. +impl FilterSampler { pub fn sample(&self, u: Point2f) -> FilterSample { let (p, pdf, pi) = self.distrib.sample(u); + if pdf == 0.0 { return FilterSample { p, weight: 0.0 }; } - let weight = self.f[pi] / pdf; + + let weight = *self.f.get_linear(pi.x() as usize + self.f.x_size()) / pdf; FilterSample { p, weight } } } -#[enum_dispatch] -pub trait FilterTrait { - fn radius(&self) -> Vector2f; - fn evaluate(&self, p: Point2f) -> Float; - fn integral(&self) -> Float; - fn sample(&self, u: Point2f) -> FilterSample; -} - -#[enum_dispatch(FilterTrait)] #[derive(Clone, Debug)] pub enum Filter { Box(BoxFilter), @@ -76,310 +69,42 @@ pub enum Filter { } impl Filter { - pub fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result { - match name { - "box" => { - let filter = BoxFilter::create(¶ms, loc); - Ok(Filter::Box(filter)) - } - "gaussian" => { - let filter = GaussianFilter::create(¶ms, loc); - Ok(Filter::Gaussian(filter)) - } - "mitchell" => { - let filter = MitchellFilter::create(¶ms, loc); - Ok(Filter::Mitchell(filter)) - } - "sinc" => { - let filter = LanczosSincFilter::create(¶ms, loc); - Ok(Filter::LanczosSinc(filter)) - } - "triangle" => { - let filter = TriangleFilter::create(¶ms, loc); - Ok(Filter::Triangle(filter)) - } - _ => Err(format!("Film type '{}' unknown at {}", name, loc)), - } - } -} - -#[derive(Clone, Debug)] -pub struct BoxFilter { - pub radius: Vector2f, -} - -impl BoxFilter { - pub fn new(radius: Vector2f) -> Self { - Self { radius } - } - - pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { - let xw = params.get_one_float("xradius", 0.5); - let yw = params.get_one_float("yradius", 0.5); - Self::new(Vector2f::new(xw, yw)) - } -} - -impl FilterTrait for BoxFilter { - fn radius(&self) -> Vector2f { - self.radius - } - - fn evaluate(&self, p: Point2f) -> Float { - if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() { - 1. - } else { - 0. + pub fn radius(&self) -> Vector2f { + match self { + Filter::Box(f) => f.radius(), + Filter::Gaussian(f) => f.radius(), + Filter::Mitchell(f) => f.radius(), + Filter::LanczosSinc(f) => f.radius(), + Filter::Triangle(f) => f.radius(), } } - fn integral(&self) -> Float { - (2.0 * self.radius.x()) * (2.0 * self.radius.y()) - } - - fn sample(&self, u: Point2f) -> FilterSample { - let p = Point2f::new( - lerp(u[0], -self.radius.x(), self.radius.x()), - lerp(u[1], -self.radius.y(), self.radius.y()), - ); - FilterSample { p, weight: 1.0 } - } -} - -#[derive(Clone, Debug)] -pub struct GaussianFilter { - pub radius: Vector2f, - pub sigma: Float, - pub exp_x: Float, - pub exp_y: Float, - pub sampler: FilterSampler, -} - -impl GaussianFilter { - pub fn new(radius: Vector2f, sigma: Float) -> Self { - let exp_x = gaussian(radius.x(), 0., sigma); - let exp_y = gaussian(radius.y(), 0., sigma); - - let sampler = FilterSampler::new(radius, move |p: Point2f| { - let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0); - let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0); - gx * gy - }); - - Self { - radius, - sigma, - exp_x: gaussian(radius.x(), 0., sigma), - exp_y: gaussian(radius.y(), 0., sigma), - sampler, + pub fn evaluate(&self, p: Point2f) -> Float { + match self { + Filter::Box(f) => f.evaluate(p), + Filter::Gaussian(f) => f.evaluate(p), + Filter::Mitchell(f) => f.evaluate(p), + Filter::LanczosSinc(f) => f.evaluate(p), + Filter::Triangle(f) => f.evaluate(p), } } - pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { - let xw = params.get_one_float("xradius", 1.5); - let yw = params.get_one_float("yradius", 1.5); - let sigma = params.get_one_float("sigma", 0.5); - Self::new(Vector2f::new(xw, yw), sigma) - } -} - -impl FilterTrait for GaussianFilter { - fn radius(&self) -> Vector2f { - self.radius - } - - fn evaluate(&self, p: Point2f) -> Float { - (gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0) - * (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0) - } - - fn integral(&self) -> Float { - (gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma) - - 2.0 * self.radius.x() * self.exp_x) - * (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma) - - 2.0 * self.radius.y() * self.exp_y) - } - - fn sample(&self, u: Point2f) -> FilterSample { - self.sampler.sample(u) - } -} - -#[derive(Clone, Debug)] -pub struct MitchellFilter { - pub radius: Vector2f, - pub b: Float, - pub c: Float, - pub sampler: FilterSampler, -} - -impl MitchellFilter { - pub fn new(radius: Vector2f, b: Float, c: Float) -> Self { - let sampler = FilterSampler::new(radius, move |p: Point2f| { - let nx = 2.0 * p.x() / radius.x(); - let ny = 2.0 * p.y() / radius.y(); - Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny) - }); - - Self { - radius, - b, - c, - sampler, + pub fn integral(&self) -> Float { + match self { + Filter::Box(f) => f.integral(), + Filter::Gaussian(f) => f.integral(), + Filter::Mitchell(f) => f.integral(), + Filter::LanczosSinc(f) => f.integral(), + Filter::Triangle(f) => f.integral(), } } - - pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { - let xw = params.get_one_float("xradius", 2.); - let yw = params.get_one_float("yradius", 2.); - let b = params.get_one_float("B", 1. / 3.); - let c = params.get_one_float("C", 1. / 3.); - Self::new(Vector2f::new(xw, yw), b, c) - } - - fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float { - let x = x.abs(); - if x <= 1.0 { - ((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) - + (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2) - + (6.0 - 2.0 * b)) - * (1.0 / 6.0) - } else if x <= 2.0 { - ((-b - 6.0 * c) * x.powi(3) - + (6.0 * b + 30.0 * c) * x.powi(2) - + (-12.0 * b - 48.0 * c) * x - + (8.0 * b + 24.0 * c)) - * (1.0 / 6.0) - } else { - 0.0 + pub fn sample(&self, u: Point2f) -> FilterSample { + match self { + Filter::Box(f) => f.sample(u), + Filter::Gaussian(f) => f.sample(u), + Filter::Mitchell(f) => f.sample(u), + Filter::LanczosSinc(f) => f.sample(u), + Filter::Triangle(f) => f.sample(u), } } - - fn mitchell_1d(&self, x: Float) -> Float { - Self::mitchell_1d_eval(self.b, self.c, x) - } -} - -impl FilterTrait for MitchellFilter { - fn radius(&self) -> Vector2f { - self.radius - } - - fn evaluate(&self, p: Point2f) -> Float { - self.mitchell_1d(2.0 * p.x() / self.radius.x()) - * self.mitchell_1d(2.0 * p.y() / self.radius.y()) - } - - fn integral(&self) -> Float { - self.radius.x() * self.radius.y() / 4.0 - } - - fn sample(&self, u: Point2f) -> FilterSample { - self.sampler.sample(u) - } -} - -#[derive(Clone, Debug)] -pub struct LanczosSincFilter { - pub radius: Vector2f, - pub tau: Float, - pub sampler: FilterSampler, -} - -impl LanczosSincFilter { - pub fn new(radius: Vector2f, tau: Float) -> Self { - let sampler = FilterSampler::new(radius, move |p: Point2f| { - windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau) - }); - - Self { - radius, - tau, - sampler, - } - } - - pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { - let xw = params.get_one_float("xradius", 4.); - let yw = params.get_one_float("yradius", 4.); - let tau = params.get_one_float("tau", 3.); - Self::new(Vector2f::new(xw, yw), tau) - } -} - -impl FilterTrait for LanczosSincFilter { - fn radius(&self) -> Vector2f { - self.radius - } - - fn evaluate(&self, p: Point2f) -> Float { - windowed_sinc(p.x(), self.radius.x(), self.tau) - * windowed_sinc(p.y(), self.radius.y(), self.tau) - } - - fn integral(&self) -> Float { - let sqrt_samples = 64; - let n_samples = sqrt_samples * sqrt_samples; - let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); - let mut sum = 0.0; - let mut rng = rand::rng(); - - for y in 0..sqrt_samples { - for x in 0..sqrt_samples { - let u = Point2f::new( - (x as Float + rng.random::()) / sqrt_samples as Float, - (y as Float + rng.random::()) / sqrt_samples as Float, - ); - let p = Point2f::new( - lerp(u.x(), -self.radius.x(), self.radius.x()), - lerp(u.y(), -self.radius.y(), self.radius.y()), - ); - sum += self.evaluate(p); - } - } - sum / n_samples as Float * area - } - - fn sample(&self, u: Point2f) -> FilterSample { - self.sampler.sample(u) - } -} - -#[derive(Clone, Debug)] -pub struct TriangleFilter { - pub radius: Vector2f, -} - -impl TriangleFilter { - pub fn new(radius: Vector2f) -> Self { - Self { radius } - } - - pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { - let xw = params.get_one_float("xradius", 2.); - let yw = params.get_one_float("yradius", 2.); - Self::new(Vector2f::new(xw, yw)) - } -} - -impl FilterTrait for TriangleFilter { - fn radius(&self) -> Vector2f { - self.radius - } - - fn evaluate(&self, p: Point2f) -> Float { - (self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0) - } - - fn integral(&self) -> Float { - self.radius.x().powi(2) * self.radius.y().powi(2) - } - - fn sample(&self, u: Point2f) -> FilterSample { - let p = Point2f::new( - sample_tent(u[0], self.radius.x()), - sample_tent(u[1], self.radius.y()), - ); - FilterSample { p, weight: 1.0 } - } } diff --git a/shared/src/core/light.rs b/shared/src/core/light.rs index a46161d..098fd66 100644 --- a/shared/src/core/light.rs +++ b/shared/src/core/light.rs @@ -10,7 +10,7 @@ use crate::core::pbrt::{Float, PI}; use crate::images::Image; use crate::spectra::{ DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum, - SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, + SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, }; use crate::utils::containers::InternCache; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index 16d10f3..a336df1 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -16,7 +16,7 @@ use crate::core::texture::{ FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator, }; use crate::image::{Image, WrapMode, WrapMode2D}; -use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider}; +use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait}; use crate::utils::hash::hash_float; use crate::utils::math::clamp; diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index 2cce58f..4d3d5e2 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -8,7 +8,7 @@ use crate::core::geometry::{ use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::spectra::{ BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, - RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, + RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, }; use crate::utils::containers::SampledGrid; use crate::utils::math::{clamp, square}; diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 61872e5..8b4dc95 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -8,29 +8,22 @@ use crate::images::WrapMode; use crate::spectra::color::ColorEncoding; use crate::spectra::{ RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, - SampledWavelengths, Spectrum, SpectrumProvider, + SampledWavelengths, Spectrum, SpectrumTrait, }; +use crate::textures::*; use crate::utils::Transform; use crate::utils::math::square; -use crate::utils::transform::TransformGeneric; -use enum_dispatch::enum_dispatch; -use std::path::Path; - -struct TexCoord2D { - st: Point2f, - dsdx: Float, - dsdy: Float, - dtdx: Float, - dtdy: Float, +#[repr(C)] +pub struct TexCoord2D { + pub st: Point2f, + pub dsdx: Float, + pub dsdy: Float, + pub dtdx: Float, + pub dtdy: Float, } -#[enum_dispatch] -trait TextureMapping2DTrait { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D; -} - -#[enum_dispatch(TextureMapping2DTrait)] +#[repr(C)] #[derive(Clone, Debug)] pub enum TextureMapping2D { UV(UVMapping), @@ -39,6 +32,17 @@ pub enum TextureMapping2D { Planar(PlanarMapping), } +impl TextureMapping2D { + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { + match self { + TextureMapping2D::UV(t) => t.map(ctx), + TextureMapping2D::Spherical(t) => t.map(ctx), + TextureMapping2D::Cylindrical(t) => t.map(ctx), + TextureMapping2D::Planar(t) => t.map(ctx), + } + } +} + #[derive(Clone, Debug)] pub struct UVMapping { su: Float, @@ -47,12 +51,6 @@ pub struct UVMapping { dv: Float, } -impl UVMapping { - pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self { - Self { su, sv, du, dv } - } -} - impl Default for UVMapping { fn default() -> Self { Self { @@ -64,8 +62,12 @@ impl Default for UVMapping { } } -impl TextureMapping2DTrait for UVMapping { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { +impl UVMapping { + pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self { + Self { su, sv, du, dv } + } + + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { let dsdx = self.su * ctx.dudx; let dsdy = self.su * ctx.dudy; let dtdx = self.sv * ctx.dvdx; @@ -83,19 +85,17 @@ impl TextureMapping2DTrait for UVMapping { #[derive(Clone, Debug)] pub struct SphericalMapping { - texture_from_render: TransformGeneric, + texture_from_render: Transform, } impl SphericalMapping { - pub fn new(texture_from_render: &TransformGeneric) -> Self { + pub fn new(texture_from_render: &Transform) -> Self { Self { texture_from_render: *texture_from_render, } } -} -impl TextureMapping2DTrait for SphericalMapping { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { let pt = self.texture_from_render.apply_to_point(ctx.p); let x2y2 = square(pt.x()) + square(pt.y()); let sqrtx2y2 = x2y2.sqrt(); @@ -126,19 +126,17 @@ impl TextureMapping2DTrait for SphericalMapping { #[derive(Clone, Debug)] pub struct CylindricalMapping { - texture_from_render: TransformGeneric, + texture_from_render: Transform, } impl CylindricalMapping { - pub fn new(texture_from_render: &TransformGeneric) -> Self { + pub fn new(texture_from_render: &Transform) -> Self { Self { texture_from_render: *texture_from_render, } } -} -impl TextureMapping2DTrait for CylindricalMapping { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { let pt = self.texture_from_render.apply_to_point(ctx.p); let x2y2 = square(pt.x()) + square(pt.y()); let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2); @@ -162,7 +160,7 @@ impl TextureMapping2DTrait for CylindricalMapping { #[derive(Clone, Debug)] pub struct PlanarMapping { - texture_from_render: TransformGeneric, + texture_from_render: Transform, vs: Vector3f, vt: Vector3f, ds: Float, @@ -171,7 +169,7 @@ pub struct PlanarMapping { impl PlanarMapping { pub fn new( - texture_from_render: &TransformGeneric, + texture_from_render: &Transform, vs: Vector3f, vt: Vector3f, ds: Float, @@ -185,10 +183,8 @@ impl PlanarMapping { dt, } } -} -impl TextureMapping2DTrait for PlanarMapping { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { let vec: Vector3f = self.texture_from_render.apply_to_point(ctx.p).into(); let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx); let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy); @@ -208,9 +204,9 @@ impl TextureMapping2DTrait for PlanarMapping { } pub struct TexCoord3D { - p: Point3f, - dpdx: Vector3f, - dpdy: Vector3f, + pub p: Point3f, + pub dpdx: Vector3f, + pub dpdy: Vector3f, } pub trait TextureMapping3DTrait { @@ -218,26 +214,31 @@ pub trait TextureMapping3DTrait { } #[derive(Clone, Debug)] -#[enum_dispatch(TextureMapping3DTrait)] pub enum TextureMapping3D { PointTransform(PointTransformMapping), } -#[derive(Clone, Debug)] -pub struct PointTransformMapping { - texture_from_render: TransformGeneric, -} - -impl PointTransformMapping { - pub fn new(texture_from_render: &TransformGeneric) -> Self { - Self { - texture_from_render: *texture_from_render, +impl TextureMapping3D { + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D { + match self { + TextureMapping3D::PointTransform(t) => t.map(ctx), } } } -impl TextureMapping3DTrait for PointTransformMapping { - fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D { +#[derive(Clone, Debug)] +pub struct PointTransformMapping { + texture_from_render: Transform, +} + +impl PointTransformMapping { + pub fn new(texture_from_render: &Transform) -> Self { + Self { + texture_from_render: *texture_from_render, + } + } + + pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D { TexCoord3D { p: self.texture_from_render.apply_to_point(ctx.p), dpdx: self.texture_from_render.apply_to_vector(ctx.dpdx), @@ -323,34 +324,39 @@ impl From<&Interaction> for TextureEvalContext { } } -#[enum_dispatch] -pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug { - fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { - todo!() - } -} - -#[enum_dispatch] -pub trait SpectrumTextureTrait: Send + Sync + std::fmt::Debug { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} - #[repr(C)] pub enum GPUFloatTexture { - Constant(Float), + Constant(FloatConstantTexture), + DirectionMix(FloatDirectionMixTexture), + Scaled(FloatScaledTexture), + Bilerp(FloatBilerpTexture), + Checkerboard(FloatCheckerboardTexture), + Dots(FloatDotsTexture), + FBm(FBmTexture), + Windy(WindyTexture), + Wrinkled(WrinkledTexture), Ptex(GPUFloatPtexTexture), Image(GPUFloatImageTexture), Mix(GPUFloatMixTexture), } -#[derive(Clone, Debug)] -#[enum_dispatch(SpectrumTextureTrait)] -pub enum SpectrumTexture { - Image(GPUSpectrumImageTexture), - Ptex(GPUSpectrumPtexTexture), - Mix(GPUSpectrumMixTexture), +impl GPUFloatTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + match self { + GPUFloatTexture::Constant(t) => t.evaluate(ctx), + GPUFloatTexture::DirectionMix(t) => t.evaluate(ctx), + GPUFloatTexture::Scaled(t) => t.evaluate(ctx), + GPUFloatTexture::Bilerp(t) => t.evaluate(ctx), + GPUFloatTexture::Checkerboard(t) => t.evaluate(ctx), + GPUFloatTexture::Dots(t) => t.evaluate(ctx), + GPUFloatTexture::FBm(t) => t.evaluate(ctx), + GPUFloatTexture::Windy(t) => t.evaluate(ctx), + GPUFloatTexture::Wrinkle(t) => t.evaluate(ctx), + GPUFloatTexture::Ptex(t) => t.evaluate(ctx), + GPUFloatTexture::Image(t) => t.evaluate(ctx), + GPUFloatTexture::Mix(t) => t.evaluate(ctx), + } + } } #[derive(Clone, Copy, Debug)] @@ -359,40 +365,34 @@ pub enum SpectrumType { Albedo, Unbounded, } -pub trait TextureEvaluator: Send + Sync { - fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float; - fn evaluate_spectrum( - &self, - tex: &SpectrumTexture, - ctx: &TextureEvalContext, - lambda: &SampledWavelengths, - ) -> SampledSpectrum; - fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool; +#[repr(C)] +pub enum GPUSpectrumTexture { + Constant(SpectrumConstantTexture), + Bilerp(SpectrumBilerpTexture), + Checkerboard(SpectrumCheckerboardTexture), + Marble(MarbleTexture), + DirectionMix(SpectrumDirectionMixTexture), + Dots(SpectrumDotsTexture), + Scaled(SpectrumScaledTexture), + Image(GPUSpectrumImageTexture), + Ptex(GPUSpectrumPtexTexture), + Mix(GPUSpectrumMixTexture), } -#[derive(Copy, Clone, Default)] -pub struct UniversalTextureEvaluator; - -impl TextureEvaluator for UniversalTextureEvaluator { - fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float { - tex.evaluate(ctx) - } - - fn evaluate_spectrum( - &self, - tex: &SpectrumTexture, - ctx: &TextureEvalContext, - lambda: &SampledWavelengths, - ) -> SampledSpectrum { - tex.evaluate(ctx, lambda) - } - - fn can_evaluate( - &self, - _float_textures: &[&FloatTexture], - _spectrum_textures: &[&SpectrumTexture], - ) -> bool { - true +impl GPUSpectrumTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float { + match self { + GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Checkerboard(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Marble(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda), + GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda), + } } } diff --git a/shared/src/filters/boxf.rs b/shared/src/filters/boxf.rs new file mode 100644 index 0000000..d7e087d --- /dev/null +++ b/shared/src/filters/boxf.rs @@ -0,0 +1,38 @@ +use crate::Float; +use crate::core::filter::FilterSample; +use crate::core::geometry::{Point2f, Vector2f}; + +#[derive(Clone, Debug)] +pub struct BoxFilter { + pub radius: Vector2f, +} + +impl BoxFilter { + pub fn new(radius: Vector2f) -> Self { + Self { radius } + } + + pub fn radius(&self) -> Vector2f { + self.radius + } + + pub fn evaluate(&self, p: Point2f) -> Float { + if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() { + 1. + } else { + 0. + } + } + + pub fn integral(&self) -> Float { + (2.0 * self.radius.x()) * (2.0 * self.radius.y()) + } + + pub fn sample(&self, u: Point2f) -> FilterSample { + let p = Point2f::new( + lerp(u[0], -self.radius.x(), self.radius.x()), + lerp(u[1], -self.radius.y(), self.radius.y()), + ); + FilterSample { p, weight: 1.0 } + } +} diff --git a/shared/src/filters/gaussian.rs b/shared/src/filters/gaussian.rs new file mode 100644 index 0000000..408cda5 --- /dev/null +++ b/shared/src/filters/gaussian.rs @@ -0,0 +1,49 @@ +#[derive(Clone, Debug)] +pub struct GaussianFilter { + pub radius: Vector2f, + pub sigma: Float, + pub exp_x: Float, + pub exp_y: Float, + pub sampler: FilterSampler, +} + +impl GaussianFilter { + pub fn new(radius: Vector2f, sigma: Float) -> Self { + let exp_x = gaussian(radius.x(), 0., sigma); + let exp_y = gaussian(radius.y(), 0., sigma); + + let sampler = FilterSampler::new(radius, move |p: Point2f| { + let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0); + let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0); + gx * gy + }); + + Self { + radius, + sigma, + exp_x: gaussian(radius.x(), 0., sigma), + exp_y: gaussian(radius.y(), 0., sigma), + sampler, + } + } + + pub fn radius(&self) -> Vector2f { + self.radius + } + + pub fn evaluate(&self, p: Point2f) -> Float { + (gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0) + * (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0) + } + + pub fn integral(&self) -> Float { + (gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma) + - 2.0 * self.radius.x() * self.exp_x) + * (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma) + - 2.0 * self.radius.y() * self.exp_y) + } + + pub fn sample(&self, u: Point2f) -> FilterSample { + self.sampler.sample(u) + } +} diff --git a/shared/src/filters/lanczos.rs b/shared/src/filters/lanczos.rs new file mode 100644 index 0000000..ddbace9 --- /dev/null +++ b/shared/src/filters/lanczos.rs @@ -0,0 +1,56 @@ +#[derive(Clone, Debug)] +pub struct LanczosSincFilter { + pub radius: Vector2f, + pub tau: Float, + pub sampler: FilterSampler, +} + +impl LanczosSincFilter { + pub fn new(radius: Vector2f, tau: Float) -> Self { + let sampler = FilterSampler::new(radius, move |p: Point2f| { + windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau) + }); + + Self { + radius, + tau, + sampler, + } + } + + pub fn radius(&self) -> Vector2f { + self.radius + } + + pub fn evaluate(&self, p: Point2f) -> Float { + windowed_sinc(p.x(), self.radius.x(), self.tau) + * windowed_sinc(p.y(), self.radius.y(), self.tau) + } + + pub fn integral(&self) -> Float { + let sqrt_samples = 64; + let n_samples = sqrt_samples * sqrt_samples; + let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); + let mut sum = 0.0; + let mut rng = rand::rng(); + + for y in 0..sqrt_samples { + for x in 0..sqrt_samples { + let u = Point2f::new( + (x as Float + rng.random::()) / sqrt_samples as Float, + (y as Float + rng.random::()) / sqrt_samples as Float, + ); + let p = Point2f::new( + lerp(u.x(), -self.radius.x(), self.radius.x()), + lerp(u.y(), -self.radius.y(), self.radius.y()), + ); + sum += self.evaluate(p); + } + } + sum / n_samples as Float * area + } + + pub fn sample(&self, u: Point2f) -> FilterSample { + self.sampler.sample(u) + } +} diff --git a/shared/src/filters/mitchell.rs b/shared/src/filters/mitchell.rs new file mode 100644 index 0000000..1565985 --- /dev/null +++ b/shared/src/filters/mitchell.rs @@ -0,0 +1,67 @@ +use crate::Float; +use crate::core::filter::FilterSampler; +use crate::core::geometry::{Point2f, Vector2f}; + +#[derive(Clone, Debug)] +pub struct MitchellFilter { + pub radius: Vector2f, + pub b: Float, + pub c: Float, + pub sampler: FilterSampler, +} + +impl MitchellFilter { + pub fn new(radius: Vector2f, b: Float, c: Float) -> Self { + let sampler = FilterSampler::new(radius, move |p: Point2f| { + let nx = 2.0 * p.x() / radius.x(); + let ny = 2.0 * p.y() / radius.y(); + Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny) + }); + + Self { + radius, + b, + c, + sampler, + } + } + + fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float { + let x = x.abs(); + if x <= 1.0 { + ((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) + + (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2) + + (6.0 - 2.0 * b)) + * (1.0 / 6.0) + } else if x <= 2.0 { + ((-b - 6.0 * c) * x.powi(3) + + (6.0 * b + 30.0 * c) * x.powi(2) + + (-12.0 * b - 48.0 * c) * x + + (8.0 * b + 24.0 * c)) + * (1.0 / 6.0) + } else { + 0.0 + } + } + + fn mitchell_1d(&self, x: Float) -> Float { + Self::mitchell_1d_eval(self.b, self.c, x) + } + + pub fn radius(&self) -> Vector2f { + self.radius + } + + pub fn evaluate(&self, p: Point2f) -> Float { + self.mitchell_1d(2.0 * p.x() / self.radius.x()) + * self.mitchell_1d(2.0 * p.y() / self.radius.y()) + } + + pub fn integral(&self) -> Float { + self.radius.x() * self.radius.y() / 4.0 + } + + pub fn sample(&self, u: Point2f) -> FilterSample { + self.sampler.sample(u) + } +} diff --git a/shared/src/filters/mod.rs b/shared/src/filters/mod.rs new file mode 100644 index 0000000..f36bc92 --- /dev/null +++ b/shared/src/filters/mod.rs @@ -0,0 +1,5 @@ +pub mod boxf; +pub mod gaussian; +pub mod lanczos; +pub mod mitchell; +pub mod triangle; diff --git a/shared/src/filters/triangle.rs b/shared/src/filters/triangle.rs new file mode 100644 index 0000000..357f81a --- /dev/null +++ b/shared/src/filters/triangle.rs @@ -0,0 +1,30 @@ +#[derive(Clone, Debug)] +pub struct TriangleFilter { + pub radius: Vector2f, +} + +impl TriangleFilter { + pub fn new(radius: Vector2f) -> Self { + Self { radius } + } + + pub fn radius(&self) -> Vector2f { + self.radius + } + + pub fn evaluate(&self, p: Point2f) -> Float { + (self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0) + } + + pub fn integral(&self) -> Float { + self.radius.x().powi(2) * self.radius.y().powi(2) + } + + pub fn sample(&self, u: Point2f) -> FilterSample { + let p = Point2f::new( + sample_tent(u[0], self.radius.x()), + sample_tent(u[1], self.radius.y()), + ); + FilterSample { p, weight: 1.0 } + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 65502bf..9252dc8 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -5,6 +5,7 @@ mod cameras; mod core; mod data; +mod filters; mod images; mod integrators; mod lights; diff --git a/shared/src/spectra/data.rs b/shared/src/spectra/data.rs index 9cb826d..cc4d6a9 100644 --- a/shared/src/spectra/data.rs +++ b/shared/src/spectra/data.rs @@ -3,7 +3,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum}; use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES}; use crate::core::pbrt::Float; -use crate::spectra::{BlackbodySpectrum, SpectrumProvider}; +use crate::spectra::{BlackbodySpectrum, SpectrumTrait}; use crate::utils::math::square; use once_cell::sync::Lazy; diff --git a/shared/src/spectra/mod.rs b/shared/src/spectra/mod.rs index c5190e6..6d80457 100644 --- a/shared/src/spectra/mod.rs +++ b/shared/src/spectra/mod.rs @@ -7,10 +7,9 @@ pub mod sampled; pub mod simple; use crate::core::pbrt::Float; -use crate::utils::file::read_float_file; use enum_dispatch::enum_dispatch; -pub use color::{RGB, XYZ}; +pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ}; pub use colorspace::RGBColorSpace; pub use data::*; pub use rgb::*; @@ -19,32 +18,33 @@ pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use simple::*; // #[enum_dispatch] -pub trait SpectrumProvider: Copy { +pub trait SpectrumTrait: Copy { fn evaluate(&self, lambda: Float) -> Float; fn max_value(&self) -> Float; } -#[cfg(not(target_arch = "spirv"))] -impl SpectrumProvider for std::sync::Arc { - fn evaluate(&self, lambda: Float) -> Float { - (**self).evaluate(lambda) - } - fn max_value(&self) -> Float { - (**self).max_value() - } -} - -#[cfg(target_arch = "spirv")] // or target_os = "cuda" -impl SpectrumProvider for u32 { - fn evaluate(&self, lambda: Float) -> Float { - // Here you would call a global function that accesses - // a static buffer of spectra data - crate::gpu::lookup_global_spectrum(*self, lambda) - } - fn max_value(&self) -> Float { - crate::gpu::lookup_global_spectrum_max(*self) - } -} +// #[cfg(not(target_arch = "spirv"))] +// impl SpectrumTrait for std::sync::Arc { +// fn evaluate(&self, lambda: Float) -> Float { +// (**self).evaluate(lambda) +// } +// fn max_value(&self) -> Float { +// (**self).max_value() +// } +// } +// +// #[cfg(target_arch = "spirv")] // or target_os = "cuda" +// impl SpectrumTrait for u32 { +// fn evaluate(&self, lambda: Float) -> Float { +// // Here you would call a global function that accesses +// // a static buffer of spectra data +// crate::gpu::lookup_global_spectrum(*self, lambda) +// } +// fn max_value(&self) -> Float { +// crate::gpu::lookup_global_spectrum_max(*self) +// } +// } +// #[enum_dispatch(SpectrumTrait)] #[derive(Debug, Clone)] diff --git a/shared/src/spectra/rgb.rs b/shared/src/spectra/rgb.rs index 82e7714..2218b7e 100644 --- a/shared/src/spectra/rgb.rs +++ b/shared/src/spectra/rgb.rs @@ -1,6 +1,6 @@ use super::{ DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, - RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumProvider, XYZ, + RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ, }; use crate::Float; @@ -26,7 +26,7 @@ impl RGBAlbedoSpectrum { } } -impl SpectrumProvider for RGBAlbedoSpectrum { +impl SpectrumTrait for RGBAlbedoSpectrum { fn evaluate(&self, lambda: Float) -> Float { self.rsp.evaluate(lambda) } @@ -58,7 +58,7 @@ impl UnboundedRGBSpectrum { } } -impl SpectrumProvider for UnboundedRGBSpectrum { +impl SpectrumTrait for UnboundedRGBSpectrum { fn evaluate(&self, lambda: Float) -> Float { self.scale * self.rsp.evaluate(lambda) } @@ -69,10 +69,10 @@ impl SpectrumProvider for UnboundedRGBSpectrum { } #[derive(Debug, Clone, Default)] -pub struct RGBIlluminantSpectrum { +pub struct RGBIlluminantSpectrum { scale: Float, rsp: RGBSigmoidPolynomial, - illuminant: P, + illuminant: DenselySampledSpectrum, } // impl RGBIlluminantSpectrum { @@ -101,7 +101,7 @@ pub struct RGBIlluminantSpectrum { // } // } -impl SpectrumProvider for RGBIlluminantSpectrum { +impl SpectrumTrait for RGBIlluminantSpectrum { fn evaluate(&self, lambda: Float) -> Float { match &self.illuminant { Some(illuminant) => { @@ -161,7 +161,7 @@ impl RGBUnboundedSpectrum { } } -impl SpectrumProvider for RGBUnboundedSpectrum { +impl SpectrumTrait for RGBUnboundedSpectrum { fn evaluate(&self, lambda: Float) -> Float { self.scale * self.rsp.evaluate(lambda) } diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index 168a9f8..99929cd 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -2,7 +2,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use crate::core::cie::*; use crate::core::pbrt::Float; use crate::spectra::{ - N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, + N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, }; use crate::utils::file::read_float_file; use std::cmp::Ordering; @@ -21,7 +21,7 @@ impl ConstantSpectrum { } } -impl SpectrumProvider for ConstantSpectrum { +impl SpectrumTrait for ConstantSpectrum { fn evaluate(&self, _lambda: Float) -> Float { self.c } @@ -165,7 +165,7 @@ impl Hash for DenselySampledSpectrum { } } -impl SpectrumProvider for DenselySampledSpectrum { +impl SpectrumTrait for DenselySampledSpectrum { fn evaluate(&self, lambda: Float) -> Float { let offset = (lambda.round() as i32) - self.lambda_min; if offset < 0 || offset as usize >= self.values.len() { @@ -241,7 +241,7 @@ impl PiecewiseLinearSpectrum { } } -impl SpectrumProvider for PiecewiseLinearSpectrum { +impl SpectrumTrait for PiecewiseLinearSpectrum { fn evaluate(&self, lambda: Float) -> Float { if self.lambdas.is_empty() { return 0.0; @@ -321,7 +321,7 @@ impl BlackbodySpectrum { } } -impl SpectrumProvider for BlackbodySpectrum { +impl SpectrumTrait for BlackbodySpectrum { fn evaluate(&self, lambda: Float) -> Float { Self::planck_law(lambda, self.temperature) * self.normalization_factor } diff --git a/shared/src/textures/bilerp.rs b/shared/src/textures/bilerp.rs new file mode 100644 index 0000000..ec4a618 --- /dev/null +++ b/shared/src/textures/bilerp.rs @@ -0,0 +1,85 @@ +use crate::core::texture::{TextureEvalContext, TextureMapping2D}; +use crate::spectra::{SampledSpectrum, SampledWavelengths, SpectrumTrait}; +use crate::utils::Transform; + +#[derive(Debug, Clone)] +pub struct FloatBilerpTexture { + mapping: TextureMapping2D, + v00: Float, + v01: Float, + v10: Float, + v11: Float, +} + +#[inline(always)] +pub fn bilerp(st_frac: [Float; 2], v: [SampledSpectrum; 4]) -> SampledSpectrum { + let sx = st_frac[0]; + let sy = st_frac[1]; + let one_minus_sx = 1.0 - sx; + let one_minus_sy = 1.0 - sy; + + one_minus_sx * one_minus_sy * v[0] + + sx * one_minus_sy * v[1] + + one_minus_sx * sy * v[2] + + sx * sy * v[3] +} + +impl FloatBilerpTexture { + pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self { + Self { + mapping, + v00, + v01, + v10, + v11, + } + } + + pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { + todo!() + } +} + +#[derive(Clone, Debug)] +pub struct SpectrumBilerpTexture { + pub mapping: TextureMapping2D, + pub v00: Spectrum, + pub v01: Spectrum, + pub v10: Spectrum, + pub v11: Spectrum, +} + +impl SpectrumBilerpTexture { + pub fn new( + mapping: TextureMapping2D, + v00: Spectrum, + v01: Spectrum, + v10: Spectrum, + v11: Spectrum, + ) -> Self { + Self { + mapping, + v00, + v01, + v10, + v11, + } + } + + pub fn evaluate( + &self, + ctx: &TextureEvalContext, + _lambda: &SampledWavelengths, + ) -> SampledSpectrum { + let c = self.mapping.map(ctx); + bilerp( + [c.st[0], c.st[1], c.st[2]], + [ + v00.sample(lambda), + v01.sample(lambda), + v10.sample(lambda), + v11.sample(lambda), + ], + ) + } +} diff --git a/shared/src/textures/checkerboard.rs b/shared/src/textures/checkerboard.rs new file mode 100644 index 0000000..0d99efc --- /dev/null +++ b/shared/src/textures/checkerboard.rs @@ -0,0 +1,29 @@ +use crate::core::texture::TextureEvalContext; +use crate::spectra::{SampledSpectrum, SampledWavelengths}; + +// TODO: I have to implement somethign like a TaggedPointer, and change the whole codebase. +// Fantastic +#[derive(Debug, Clone)] +pub struct FloatCheckerboardTexture { + pub map_2d: TextureMapping2D, + pub map_3d: TextureMapping3D, + pub tex: [FloatTexture; 2], +} + +impl FloatCheckerboardTexture { + pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { + todo!() + } +} + +#[derive(Clone, Debug)] +pub struct SpectrumCheckerboardTexture; +impl SpectrumCheckerboardTexture { + pub fn evaluate( + &self, + _ctx: &TextureEvalContext, + _lambda: &SampledWavelengths, + ) -> SampledSpectrum { + todo!() + } +} diff --git a/shared/src/textures/constant.rs b/shared/src/textures/constant.rs new file mode 100644 index 0000000..3c99827 --- /dev/null +++ b/shared/src/textures/constant.rs @@ -0,0 +1,37 @@ +use crate::Float; +use crate::core::texture::TextureEvalContext; +use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum}; + +#[derive(Debug, Clone)] +pub struct FloatConstantTexture { + pub value: Float, +} + +impl FloatConstantTexture { + pub fn new(value: Float) -> Self { + Self { value } + } + + pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { + self.value + } +} + +#[derive(Clone, Debug)] +pub struct SpectrumConstantTexture { + pub value: Spectrum, +} + +impl SpectrumConstantTexture { + pub fn new(value: Spectrum) -> Self { + Self { value } + } + + pub fn evaluate( + &self, + _ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + self.value.sample(lambda) + } +} diff --git a/shared/src/textures/dots.rs b/shared/src/textures/dots.rs new file mode 100644 index 0000000..226382a --- /dev/null +++ b/shared/src/textures/dots.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone)] +pub struct FloatDotsTexture; +impl FloatDotsTexture { + pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { + todo!() + } +} + +#[derive(Clone, Debug)] +pub struct SpectrumDotsTexture; +impl SpectrumDotsTexture { + pub fn evaluate( + &self, + _ctx: &TextureEvalContext, + _lambda: &SampledWavelengths, + ) -> SampledSpectrum { + todo!() + } +} diff --git a/shared/src/textures/fbm.rs b/shared/src/textures/fbm.rs new file mode 100644 index 0000000..bfc8870 --- /dev/null +++ b/shared/src/textures/fbm.rs @@ -0,0 +1,15 @@ +use crate::core::texture::{TextureEvalContext, TextureMapping3D}; + +#[derive(Debug, Clone)] +pub struct FBmTexture { + pub mapping: TextureMapping3D, + pub omega: Float, + pub octaves: usize, +} + +impl FBmTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + let c = self.mapping.map(ctx); + fbm(c.p, c.dpdx, c.dpdy, self.omega, self.octaves) + } +} diff --git a/shared/src/textures/image.rs b/shared/src/textures/image.rs index 983fc67..38c1a05 100644 --- a/shared/src/textures/image.rs +++ b/shared/src/textures/image.rs @@ -1,9 +1,6 @@ -use crate::{ - core::texture::{ - FloatTextureTrait, ImageTextureBase, SpectrumTextureTrait, SpectrumType, TextureMapping2D, - }, - spectra::RGBColorSpace, -}; +use crate::Float; +use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; +use crate::spectra::RGBColorSpace; /* GPU heavy code, dont know if this will ever work the way Im doing things. * Leaving it here isolated, for careful handling */ @@ -15,13 +12,59 @@ pub struct GPUSpectrumImageTexture { pub tex_obj: u64, pub scale: Float, pub invert: bool, + pub is_single_channel: bool, pub color_space: RGBColorSpace, pub spectrum_type: SpectrumType, } -impl SpectrumTextureTrait for GPUSpectrumImageTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() +impl GPUSpectrumImageTexture { + pub fn evaluate( + &self, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + #[cfg(not(feature = "cuda"))] + { + return SampledSpectrum::zero(); + } + + #[cfg(feature = "cuda")] + { + use cuda_std::intrinsics; + let c = self.mapping.map(ctx); + let u = c.st.x; + let v = 1.0 - c.st.y; + + let d_p_dx = [c.dsdx, c.dtdx]; + let d_p_dy = [c.dsdy, c.dtdy]; + + let tex_color = if self.is_single_channel { + let val: Float = + unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; + RGB::new(val, val, val) + } else { + let val: [Float; 4] = + unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; + RGB::new(val[0], val[1], val[2]) + }; + + let mut rgb = tex_color * self.scale; + if self.invert { + rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero(); + } + + let color_space = unsafe { &*self.color_space }; + + match self.spectrum_type { + SpectrumType::Unbounded => { + RGBUnboundedSpectrum::new(color_space, rgb).sample(lambda) + } + SpectrumType::Albedo => { + RGBAlbedoSpectrum::new(color_space, rgb.clamp(0.0, 1.0)).sample(lambda) + } + _ => RGBIlluminantSpectrum::new(color_space, rgb).sample(lambda), + } + } } } @@ -33,4 +76,27 @@ pub struct GPUFloatImageTexture { pub invert: bool, } -impl FloatTextureTrait for GPUFloatImageTexture {} +impl GPUFloatImageTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + #[cfg(not(feature = "cuda"))] + { + return 0.; + } + #[cfg(feature = "cuda")] + { + use cuda_std::intrinsics; + let c = self.mapping.map(ctx); + let u = c.st.x; + let v = 1.0 - c.st.y; + let d_p_dx = [c.dsdx, c.dtdx]; + let d_p_dy = [c.dsdy, c.dtdy]; + let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; + + if invert { + return (1. - v).max(0.); + } else { + return v; + } + } + } +} diff --git a/shared/src/textures/marble.rs b/shared/src/textures/marble.rs new file mode 100644 index 0000000..40a17ed --- /dev/null +++ b/shared/src/textures/marble.rs @@ -0,0 +1,56 @@ +use crate::core::texture::{TextureEvalContext, TextureMapping3D}; +use crate::spectra::{RGBAlbedoSpectrum, SampledSpectrum, SampledWavelengths}; +use crate::utils::noise::fbm; +use crate::utils::splines::evaluate_cubic_bezier; +use crate::Float; + +#[derive(Clone, Debug)] +pub struct MarbleTexture { + pub mapping: TextureMapping3D, + pub octaves: usize, + pub omega: Float, + pub scale: Float, + pub variation: Float, +} +impl MarbleTexture { + pub fn evaluate( + &self, + ctx: &TextureEvalContext, + _lambda: &SampledWavelengths, + ) -> SampledSpectrum { + let mut c = self.mapping.map(ctx); + c.p *= self.scale; + let marble = c.p.y() + self.variation * fbm(c.p, self.scale, c.dpdy, omega, self.octaves); + const COLORS: [RGB; 9] = [ + RGB::new(0.58, 0.58, 0.6), + RGB::new(0.58, 0.58, 0.6), + RGB::new(0.58, 0.58, 0.6), + RGB::new(0.5, 0.5, 0.5), + RGB::new(0.6, 0.59, 0.58), + RGB::new(0.58, 0.58, 0.6), + RGB::new(0.58, 0.58, 0.6), + RGB::new(0.2, 0.2, 0.33), + RGB::new(0.58, 0.58, 0.6), + ]; + + const N_SEG: i32 = 6; // (9 - 3) + let t_clamped = t.clamp(0.0, 1.0); + let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1); + let t_segment = t_clamped * N_SEG as Float - first as Float; + let first_idx = first as usize; + let rgb = evaluate_cubic_bezier(&COLORS[first_idx..first_idx + 4], t_segment) * 1.5; + + let color_space = { + #[cfg(target_os = "cuda")] + { + unsafe { &*RGBColorSpace_sRGB } + } + + #[cfg(not(target_os = "cuda"))] + { + RGBColorSpace::srgb() + } + }; + RGBAlbedoSpectrum::new(color_space, rgb).sample(lambda) + } +} diff --git a/shared/src/textures/mod.rs b/shared/src/textures/mod.rs index 20b2697..78218f5 100644 --- a/shared/src/textures/mod.rs +++ b/shared/src/textures/mod.rs @@ -1,3 +1,23 @@ +pub mod bilerp; +pub mod checkerboard; +pub mod constant; +pub mod dots; +pub mod fbm; pub mod image; +pub mod marble; pub mod mix; pub mod ptex; +pub mod windy; +pub mod wrinkled; + +pub use bilerp::*; +pub use checkerboard::*; +pub use constant::*; +pub use dots::*; +pub use fbm::*; +pub use image::*; +pub use marble::*; +pub use mix::*; +pub use ptex::*; +pub use windy::*; +pub use wrinkled::*; diff --git a/shared/src/textures/ptex.rs b/shared/src/textures/ptex.rs index 928dc34..059e305 100644 --- a/shared/src/textures/ptex.rs +++ b/shared/src/textures/ptex.rs @@ -1,16 +1,55 @@ -use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext}; -use crate::spectra::{SampledSpectrum, SampledWavelengths}; +use crate::Float; +use crate::core::texture::{SpectrumType, TextureEvalContext}; +use crate::spectra::{ + RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, + SampledWavelengths, +}; /* GPU heavy code, have to see how to best approach this */ -#[derive(Clone, Debug)] -pub struct GPUSpectrumPtexTexture; -impl SpectrumTextureTrait for GPUSpectrumPtexTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() + +#[derive(Debug, Clone)] +pub struct GPUFloatPtexTexture { + pub face_values: Vec, +} + +impl GPUFloatPtexTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + self.face_values[ctx.face_index] } } -#[derive(Debug, Clone)] -pub struct GPUFloatPtexTexture; -impl FloatTextureTrait for GPUFloatPtexTexture {} +#[derive(Clone, Debug)] +pub struct GPUSpectrumPtexTexture { + pub face_values: *const RGB, + pub n_faces: usize, + pub spectrum_type: SpectrumType, +} + +impl GPUSpectrumPtexTexture { + pub fn evaluate( + &self, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1)); + let rgb = unsafe { *self.face_values.add(index) }; + let s_rgb = { + #[cfg(feature = "cuda")] + unsafe { + &*RGBColorSpace_sRGB + } + #[cfg(not(feature = "cuda"))] + RGBColorSpace::srgb() + }; + + match self.spectrum_type { + SpectrumType::Unbounded => RGBUnboundedSpectrum::new(s_rgb, rgb).sample(lambda), + SpectrumType::Albedo => { + let clamped_rgb = rgb.clamp(0.0, 1.0); + RGBAlbedoSpectrum::new(s_rgb, clamped_rgb).sample(lambda) + } + SpectrumType::Illuminant => RGBIlluminantSpectrum::new(s_rgb, rgb).sample(lambda), + } + } +} diff --git a/shared/src/textures/windy.rs b/shared/src/textures/windy.rs new file mode 100644 index 0000000..fcb3bc7 --- /dev/null +++ b/shared/src/textures/windy.rs @@ -0,0 +1,16 @@ +use crate::core::texture::{TextureEvalContext, TextureMapping3D}; +use crate::utils::noise::fbm; + +#[derive(Debug, Clone)] +pub struct WindyTexture { + pub mapping: TextureMapping3D, +} + +impl WindyTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + let c = self.mapping.map(ctx); + let wind_strength = fbm(0.1 * c.p, 0.1 * c.dpdx, 0.1 * c.dpdy, 0.5, 3); + let wave_height = fbm(c.p, c.dpdx, c.dpdy, 0.5, 6); + wind_strength.abs() * wave_height + } +} diff --git a/shared/src/textures/wrinkled.rs b/shared/src/textures/wrinkled.rs new file mode 100644 index 0000000..8be20d4 --- /dev/null +++ b/shared/src/textures/wrinkled.rs @@ -0,0 +1,16 @@ +use crate::core::texture::{TextureEvalContext, TextureMapping3D}; +use crate::utils::noise::turbulence; + +#[derive(Debug, Clone)] +pub struct WrinkledTexture { + pub mapping: TextureMapping3D, + pub octaves: usize, + pub omega: Float, +} + +impl WrinkledTexture { + pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { + let c = self.mapping.map(ctx); + turbulence(c.p, c.dpdx, c.dpdy, omega, octaves) + } +} diff --git a/shared/src/utils/containers.rs b/shared/src/utils/containers.rs index cbe2e45..cbd8bae 100644 --- a/shared/src/utils/containers.rs +++ b/shared/src/utils/containers.rs @@ -20,134 +20,161 @@ use crate::core::geometry::{ // { // } -#[derive(Debug, Clone)] +#[repr(C)] pub struct Array2D { - pub values: Vec, + pub values: *mut T, pub extent: Bounds2i, } impl Array2D { - pub fn new(extent: Bounds2i) -> Self - where - T: Default, - { - let size = extent.area() as usize; - let mut values = Vec::with_capacity(size); - values.resize_with(size, T::default); - Self { values, extent } + #[inline] + pub fn x_size(&self) -> usize { + (self.extent.p_max.x() - self.extent.p_min.x()) as usize } - pub fn new_with_dims(nx: usize, ny: usize) -> Self - where - T: Default, - { - Self::new(Bounds2i::from_points( - Point2i::new(0, 0), - Point2i::new(nx as i32, ny as i32), - )) + #[inline] + pub fn y_size(&self) -> usize { + (self.extent.p_max.y() - self.extent.p_min.y()) as usize } - pub fn new_filled(width: usize, height: usize, value: T) -> Self - where - T: Clone, - { + #[inline] + pub fn size(&self) -> usize { + self.extent.area() as usize + } + + #[inline] + pub fn index(&self, x: i32, y: i32) -> usize { + let nx = x - self.extent.p_min.x; + let ny = y - self.extent.p_min.y; + (nx + self.x_size() * ny) as usize + } + + #[inline] + pub unsafe fn get(&self, x: i32, y: i32) -> &T { + unsafe { &*self.values.add(self.index(x, y)) } + } + + #[inline] + pub unsafe fn get_mut(&mut self, x: i32, y: i32) -> &mut T { + unsafe { &mut *self.values.add(self.index(x, y)) } + } + + #[inline] + pub fn get_linear(&self, index: usize) -> &T { + unsafe { &*self.values.add(index) } + } + + #[inline] + pub fn get_linear_mut(&mut self, index: usize) -> &mut T { + // SAFETY: Caller must ensure index < size() + unsafe { &mut *self.values.add(index) } + } + + pub fn as_slice(&self) -> &[T] { + unsafe { core::slice::from_raw_parts(self.values, self.size()) } + } + + pub fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.values, self.size()) } + } +} + +#[cfg(not(target_os = "cuda"))] +impl Clone for Array2D { + fn clone(&self) -> Self { + let n = self.area(); + let mut v = Vec::with_capacity(n); + unsafe { + for i in 0..n { + v.push((*self.values.add(i)).clone()); + } + } + let values = v.as_mut_ptr(); + std::mem::forget(v); + Self { + extent: self.extent, + values, + } + } +} + +#[cfg(target_os = "cuda")] +impl Clone for Array2D { + fn clone(&self) -> Self { + *self + } +} + +#[cfg(target_os = "cuda")] +impl Copy for Array2D {} + +#[cfg(not(target_os = "cuda"))] +impl Array2D { + pub fn new(extent: Bounds2i) -> Self { + let n = extent.area() as usize; + let mut v = vec![T::default(); n]; + let values = v.as_mut_ptr(); + std::mem::forget(v); + Self { extent, values } + } + + pub fn new_with_dims(nx: usize, ny: usize) -> Self { + let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny)); + let n = extent.area() as usize; + + let mut v = vec![T::default(); n]; + let values = v.as_mut_ptr(); + + std::mem::forget(v); + + Self { extent, values } + } + + pub fn new_from_bounds(extent: Bounds2i, default_val: T) -> Self { + let n = extent.area() as usize; + let mut v = vec![def; n]; + let values = v.as_mut_ptr(); + std::mem::forget(v); + Self { extent, values } + } + + pub fn new_filled(width: usize, height: usize, value: T) -> Self { let extent = Bounds2i::from_points( Point2i::new(0, 0), Point2i::new(width as i32, height as i32), ); Self::new_from_bounds(extent, value) } - - pub fn new_from_bounds(extent: Bounds2i, default_val: T) -> Self - where - T: Clone, - { - let size = extent.area() as usize; - let values = vec![default_val; size]; - Self { values, extent } - } - - pub fn x_size(&self) -> usize { - (self.extent.p_max.x() - self.extent.p_min.x()) as usize - } - - pub fn y_size(&self) -> usize { - (self.extent.p_max.y() - self.extent.p_min.y()) as usize - } - - pub fn size(&self) -> usize { - self.values.len() - } - - pub fn as_slice(&self) -> &[T] { - &self.values - } - - pub fn as_mut_slice(&mut self) -> &mut [T] { - &mut self.values - } - - fn get_index(&self, p: Point2i) -> usize { - debug_assert!(p.x() >= self.extent.p_min.x() && p.x() < self.extent.p_max.x()); - debug_assert!(p.y() >= self.extent.p_min.y() && p.y() < self.extent.p_max.y()); - let width = self.x_size(); - let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y()); - (pp.y() * width as i32 + pp.x()) as usize - } - - pub fn get_linear(&self, index: usize) -> &T { - &self.values[index] - } - - pub fn get_linear_mut(&mut self, index: usize) -> &mut T { - &mut self.values[index] - } } +#[cfg(not(feature = "cuda"))] impl Index for Array2D { type Output = T; fn index(&self, mut p: Point2i) -> &Self::Output { - p -= Vector2i::from(self.extent.p_min); - let width = self.extent.p_max.x() - self.extent.p_min.x(); - let idx = (p.x() + width * p.y()) as usize; - &self.values[idx] + unsafe { self.get(pos.0, pos.1) } } } +#[cfg(not(feature = "cuda"))] impl IndexMut for Array2D { fn index_mut(&mut self, mut p: Point2i) -> &mut Self::Output { - p -= Vector2i::from(self.extent.p_min); - let width = self.extent.p_max.x() - self.extent.p_min.x(); - let idx = (p.x() + width * p.y()) as usize; - &mut self.values[idx] - } -} - -impl Index<(usize, usize)> for Array2D { - type Output = T; - - fn index(&self, (x, y): (usize, usize)) -> &Self::Output { - &self[(x as i32, y as i32)] - } -} - -impl IndexMut<(usize, usize)> for Array2D { - fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output { - &mut self[(x as i32, y as i32)] + unsafe { self.get_mut(pos.0, pos.1) } } } +#[cfg(not(feature = "cuda"))] impl Index<(i32, i32)> for Array2D { type Output = T; - fn index(&self, index: (i32, i32)) -> &Self::Output { - self.index(Point2i::new(index.0, index.1)) + fn index(&self, pos: (i32, i32)) -> &Self::Output { + unsafe { self.get(pos.0, pos.1) } } } +#[cfg(not(feature = "cuda"))] impl IndexMut<(i32, i32)> for Array2D { - fn index_mut(&mut self, index: (i32, i32)) -> &mut Self::Output { - self.index_mut(Point2i::new(index.0, index.1)) + fn index_mut(&mut self, pos: (i32, i32)) -> &mut Self::Output { + unsafe { self.get_mut(pos.0, pos.1) } } } diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index 478f4e9..e30050c 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -6,6 +6,7 @@ pub mod hash; pub mod interval; pub mod math; pub mod mesh; +pub mod noise; pub mod quaternion; pub mod rng; pub mod sampling; @@ -30,48 +31,56 @@ where i } -#[derive(Debug)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] pub struct AtomicFloat { - bits: AtomicU64, + value: f64, } impl AtomicFloat { pub fn new(value: f64) -> Self { - Self { - bits: AtomicU64::new(value.to_bits()), - } + Self { value } } pub fn load(&self) -> f64 { - f64::from_bits(self.bits.load(Ordering::Relaxed)) + #[cfg(not(target_os = "cuda"))] + { + use core::sync::atomic::{AtomicU64, Ordering}; + let ptr = &self.value as *const f64 as *const AtomicU64; + f64::from_bits(unsafe { (*ptr).load(Ordering::Relaxed) }) + } + + #[cfg(target_os = "cuda")] + self.value } - pub fn store(&self, value: f64) { - self.bits.store(value.to_bits(), Ordering::Relaxed); - } + pub fn add(&self, v: f64) { + let ptr = &self.value as *const f64 as *mut f64; - pub fn add(&self, value: f64) { - let mut current_bits = self.bits.load(Ordering::Relaxed); - loop { - let current_val = f64::from_bits(current_bits); - let new_val = current_val + value; - let new_bits = new_val.to_bits(); + #[cfg(target_os = "cuda")] + unsafe { + cuda_std::intrinsics::atomic_add(ptr, v); + } - match self.bits.compare_exchange_weak( - current_bits, - new_bits, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(x) => current_bits = x, + #[cfg(not(target_os = "cuda"))] + unsafe { + use core::sync::atomic::{AtomicU64, Ordering}; + let atomic_ptr = ptr as *const AtomicU64; + let atomic = &*atomic_ptr; + let mut current_bits = atomic.load(Ordering::Relaxed); + loop { + let current_val = f64::from_bits(current_bits); + let new_val = current_val + v; + match atomic.compare_exchange_weak( + current_bits, + new_val.to_bits(), + Ordering::Relaxed, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(x) => current_bits = x, + } } } } } - -impl Default for AtomicFloat { - fn default() -> Self { - Self::new(0.0) - } -} diff --git a/shared/src/utils/noise.rs b/shared/src/utils/noise.rs new file mode 100644 index 0000000..f5bfdf2 --- /dev/null +++ b/shared/src/utils/noise.rs @@ -0,0 +1,179 @@ +use crate::Float; +use crate::core::geometry::{Point3f, Vector3f, VectorLike}; +use crate::utils::math::{clamp, lerp, smooth_step}; + +static NOISE_PERM_SIZE: usize = 256; +static NOISE_PERM: [i32; 2 * NOISE_PERM_SIZE] = [ + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, + 142, // Remainder of the noise permutation table + 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, + 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, + 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, + 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, + 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, + 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, + 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, + 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, + 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, + 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, + 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, + 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, + 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, + 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, + 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, + 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, + 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, + 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, + 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, + 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, + 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, + 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, +]; + +#[inline(always)] +fn noise_weight(t: Float) -> Float { + let t2 = t * t; + let t3 = t2 * t; + let t4 = t3 * t; + let t5 = t4 * t; + 6.0 * t5 - 15.0 * t4 + 10.0 * t3 +} + +#[inline(always)] +fn grad(x: i32, y: i32, z: i32, dx: Float, dy: Float, dz: Float) -> Float { + let hash = + NOISE_PERM[NOISE_PERM[NOISE_PERM[x as usize] as usize + y as usize] as usize + z as usize]; + let h = h & 15; + let u = if h < 8 || h == 12 || h == 13 { dx } else { dy }; + let v = if h < 4 || h == 12 || h == 13 { dy } else { dz }; + + let mut result = 0.0; + if (h & 1) != 0 { + result -= u; + } else { + result += u; + } + if (h & 2) != 0 { + result -= v; + } else { + result += v; + } + result +} + +#[inline(always)] +pub fn noise_from_point(mut p: Point3f) -> Float { + noise(p.x(), p.y(), p.z()) +} + +#[inline(always)] +pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float { + let max_coord = (1i32 << 30) as Float; + x = x % max_coord; + y = y % max_coord; + z = z % max_coord; + if x < 0.0 { + x += max_coord; + } + if y < 0.0 { + y += max_coord; + } + if z < 0.0 { + z += max_coord; + } + + let ix = x.floor() as i32; + let iy = y.floor() as i32; + let iz = z.floor() as i32; + + let dx = x - ix as Float; + let dy = y - iy as Float; + let dz = z - iz as Float; + + let ix = ix & (NOISE_PERM_SIZE as i32 - 1); + let iy = iy & (NOISE_PERM_SIZE as i32 - 1); + let iz = iz & (NOISE_PERM_SIZE as i32 - 1); + + // Fetch gradients + let w000 = grad(ix, iy, iz, dx, dy, dz); + let w100 = grad(ix + 1, iy, iz, dx - 1, dy, dz); + let w010 = grad(ix, iy + 1, iz, dx, dy - 1, dz); + let w110 = grad(ix + 1, iy + 1, iz, dx - 1, dy - 1, dz); + let w001 = grad(ix, iy, iz + 1, dx, dy, dz - 1); + let w101 = grad(ix + 1, iy, iz + 1, dx - 1, dy, dz - 1); + let w011 = grad(ix, iy + 1, iz + 1, dx, dy - 1, dz - 1); + let w111 = grad(ix + 1, iy + 1, iz + 1, dx - 1, dy - 1, dz - 1); + + let wx = noise_weight(dx); + let wy = noise_weight(dy); + let wz = noise_weight(dz); + + let x00 = lerp(wx, w000, w100); + let x10 = lerp(wx, w010, w110); + let x01 = lerp(wx, w001, w101); + let x11 = lerp(wx, w011, w111); + + let y0 = lerp(wy, x00, x10); + let y1 = lerp(wy, x01, x11); + + lerp(wz, y0, y1) +} + +pub fn fbm(p: Point3f, dpdx: Vector3f, dpdy: Vector3f, omega: Float, max_octaves: usize) -> Float { + // Compute number of octaves for antialiased FBm + let len2 = dpdx.norm_squared().max(dpdy.norm_squared()); + let n = clamp(-1. - len.log2() / 2., 0., max_octaves); + let n_int = n.floor(); + + let mut sum = 0.; + let mut lambda = 1.; + let mut o = 1; + for i in 0..n_int { + sum += o * Noise(lambda * p); + lambda *= 1.99; + o *= omega; + } + + let n_partial = n - n_int; + sum += o * smooth_step(n_partial, 0.3, 0.7) * noise_from_point(lambda * p); + + return sum; +} + +pub fn turbulence( + p: Point3f, + dpdx: Vector3f, + dpdy: Vector3f, + omega: Float, + max_octaves: usize, +) -> Float { + // Compute number of octaves for antialiased FBm + let len2 = dpdx.norm_squared().max(dpdy.norm_squared()); + let n = clamp(-1. - len2.log2() / 2., 0, maxOctaves); + let n_int = n.floor(); + + // Compute sum of octaves of noise for turbulence + let mut sum = 0.; + let mut lambda = 1.; + let mut o = 1.; + for i in 0..n_int { + sum += o * noise_from_point(lambda * p).abs(); + lambda *= 1.99; + o *= omega; + } + + let n_partial = n - n_int; + sum += o * lerp( + smooth_step(n_partial, 0.3, 0.7), + 0.2, + noise_from_point(lambda * p).abs(), + ); + for i in n_int..max_octaves { + sum += o * 0.2; + o *= omega; + } + + return sum; +} diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index 90edcdb..3141ff2 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -700,64 +700,84 @@ impl VarianceEstimator { } #[derive(Debug, Copy, Clone, Default)] +#[repr(C)] pub struct PLSample { pub p: Point2f, pub pdf: Float, } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct PiecewiseConstant1D { - pub func: Vec, - pub cdf: Vec, + pub func: *mut Float, + pub cdf: *mut Float, pub min: Float, pub max: Float, + pub n: usize, pub func_integral: Float, } + +#[cfg(not(target_os = "cuda"))] impl PiecewiseConstant1D { - pub fn new(f: &[Float]) -> Self { - Self::new_with_bounds(f, 0., 1.) - } - pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self { - assert!(max > min); let n = f.len(); - let mut func = Vec::with_capacity(n); - for &val in f { - func.push(val.abs()); - } + let mut func_vec = f.to_vec(); + let mut cdf_vec = vec![0.0; n + 1]; - let mut cdf = vec![0.; n + 1]; + cdf_vec[0] = 0.0; for i in 1..=n { - debug_assert!(func[i - 1] >= 0.); - cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float; + cdf_vec[i] = cdf_vec[i - 1] + func_vec[i - 1] / n as Float; + } + let func_int = cdf_vec[n]; + if func_int > 0.0 { + for i in 1..=n { + cdf_vec[i] /= func_int; + } + } else { + for i in 1..=n { + cdf_vec[i] = i as Float / n as Float; + } } - let func_integral = cdf[n]; - if func_integral == 0. { - let n_float = n as Float; - cdf.iter_mut() - .enumerate() - .for_each(|(i, c)| *c = i as Float / n_float); - } else { - let inv_integral = 1.0 / func_integral; - cdf.iter_mut().for_each(|c| *c *= inv_integral); - } + let func = func_vec.as_mut_ptr(); + let cdf = cdf_vec.as_mut_ptr(); + std::mem::forget(func_vec); + std::mem::forget(cdf_vec); Self { func, cdf, - func_integral, min, max, + n, + func_integral, } } + pub fn new(f: &[Float]) -> Self { + Self::new_with_bounds(f, 0., 1.) + } +} + +#[cfg(not(target_os = "cuda"))] +impl Drop for PiecewiseConstant1D { + fn drop(&mut self) { + if !self.func.is_null() { + unsafe { + let _ = Vec::from_raw_parts(self.func, self.n, self.n); + let _ = Vec::from_raw_parts(self.cdf, self.n + 1, self.n + 1); + } + } + } +} + +impl PiecewiseConstant1D { pub fn integral(&self) -> Float { self.func_integral } pub fn size(&self) -> usize { - self.func.len() + self.n } pub fn sample(&self, u: Float) -> (Float, Float, usize) { @@ -776,37 +796,46 @@ impl PiecewiseConstant1D { (value, pdf_val, o) } } -#[derive(Debug, Clone)] + +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct PiecewiseConstant2D { - pub p_conditional_v: Vec, - pub p_marginal: PiecewiseConstant1D, pub domain: Bounds2f, + pub p_conditional_v: *mut PiecewiseConstant1D, + pub p_marginal: PiecewiseConstant1D, + pub n_conditionals: usize, } +#[cfg(not(target_os = "cuda"))] impl PiecewiseConstant2D { pub fn new(data: &Array2D, nu: usize, nv: usize, domain: Bounds2f) -> Self { - let mut p_conditional_v = Vec::with_capacity(nv); + let nu = data.x_size() as usize; + let nv = data.y_size() as usize; + let mut conditionals = Vec::with_capacity(nv); for v in 0..nv { - let start = v * nu; - let end = start + nu; - p_conditional_v.push(PiecewiseConstant1D::new_with_bounds( - &data.as_slice()[start..end], + let row = unsafe { core::slice::from_raw_parts(data.values.add(v * nu), nu) }; + conditionals.push(PiecewiseConstant1D::new_with_bounds( + row, domain.p_min.x(), domain.p_max.x(), )); } - let marginal_func: Vec = p_conditional_v.iter().map(|p| p.integral()).collect(); + let marginal_funcs: Vec = conditionals.iter().map(|c| c.func_integral).collect(); let p_marginal = PiecewiseConstant1D::new_with_bounds( - &marginal_func, + &marginal_funcs, domain.p_min.y(), domain.p_max.y(), ); + let p_conditional_v = conditionals.as_mut_ptr(); + std::mem::forget(conditionals); + Self { p_conditional_v, p_marginal, domain, + n_conditionals: nv, } } @@ -817,14 +846,28 @@ impl PiecewiseConstant2D { pub fn new_with_data(data: &Array2D) -> Self { let nx = data.x_size(); let ny = data.y_size(); - Self::new( - data, - nx, - ny, - Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)), - ) - } + let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0)); + Self::new(data, nx, ny, domain) + } +} + +#[cfg(not(target_os = "cuda"))] +impl Drop for PiecewiseConstant2D { + fn drop(&mut self) { + if !self.p_conditional_v.is_null() { + unsafe { + let _ = Vec::from_raw_parts( + self.p_conditional_v, + self.n_conditionals, + self.n_conditionals, + ); + } + } + } +} + +impl PiecewiseConstant2D { pub fn resolution(&self) -> Point2i { Point2i::new( self.p_conditional_v[0].size() as i32, diff --git a/src/core/film.rs b/src/core/film.rs new file mode 100644 index 0000000..a4d9c67 --- /dev/null +++ b/src/core/film.rs @@ -0,0 +1,349 @@ +use shared::film::{Film, FilmBase, GBufferFilm, PixelSensor, RGBFilm, SpectralFilm}; + +const N_SWATCH_REFLECTANCES: usize = 24; +const SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> = Lazy::new(|| { + std::array::from_fn(|i| { + let raw_data = crate::core::cie::SWATCHES_RAW[i]; + let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false); + Spectrum::PiecewiseLinear(pls) + }) +}); + +pub trait PixelSensorHost { + pub fn get_swatches() -> &[Spectrum; N_SWATCH_REFLECTANCES] { + &*SWATCH_REFLECTANCES + } + + pub fn create( + params: &ParameterDictionary, + output_colorspace: Arc, + exposure_time: Float, + loc: &FileLoc, + ) -> Result { + let iso = params.get_one_float("iso", 100.); + let mut white_balance_temp = params.get_one_float("whitebalance", 0.); + let sensor_name = params.get_one_string("sensor", "cie1931"); + if sensor_name != "cie1931" && white_balance_temp == 0. { + white_balance_temp = 6500.; + } + let imaging_ratio = exposure_time * iso / 100.; + + let d_illum = if white_balance_temp == 0. { + generate_cie_d(6500.) + } else { + generate_cie_d(white_balance_temp) + }; + + let sensor_illum: Option> = if white_balance_temp != 0. { + Some(Arc::new(Spectrum::DenselySampled(d_illum))) + } else { + None + }; + + if sensor_name == "cie1931" { + return Ok(PixelSensor::new_with_white_balance( + output_colorspace, + sensor_illum, + imaging_ratio, + )); + } else { + let r_opt = get_named_spectrum(&format!("{}_r", sensor_name)); + let g_opt = get_named_spectrum(&format!("{}_g", sensor_name)); + let b_opt = get_named_spectrum(&format!("{}_b", sensor_name)); + if r_opt.is_none() || g_opt.is_none() || b_opt.is_none() { + return Err(format!( + "{}: unknown sensor type '{}' (missing RGB spectral data)", + loc, sensor_name + ) + .into()); + } + + let r = Arc::new(r_opt.unwrap()); + let g = Arc::new(g_opt.unwrap()); + let b = Arc::new(b_opt.unwrap()); + + return PixelSensor::new( + r, + g, + b, + output_colorspace.clone(), + sensor_illum, + imaging_ratio, + ) + .map_err(|e| e.to_string()); + } + } +} + +pub trait FilmBaseHost { + fn create( + params: &ParameterDictionary, + filter: Filter, + sensor: Option<&PixelSensor>, + loc: &FileLoc, + ) -> Self; +} + +impl FilmBaseHost for FilmBase { + fn create( + params: &ParameterDictionary, + filter: Filter, + sensor: Option, + loc: &FileLoc, + ) -> Self { + let x_res = params.get_one_int("xresolution", 1280); + let y_res = params.get_one_int("yresolution", 720); + + if x_res <= 0 || y_res <= 0 { + eprintln!( + "{}: Film resolution must be > 0. Defaulting to 1280x720.", + loc + ); + } + let full_resolution = Point2i::new(x_res.max(1), y_res.max(1)); + + let crop_data = params.get_float_array("cropwindow"); + let crop = if crop_data.len() == 4 { + Bounds2f::from_points( + Point2f::new(crop_data[0], crop_data[2]), + Point2f::new(crop_data[1], crop_data[3]), + ) + } else { + Bounds2f::from_points(Point2f::zero(), Point2f::new(1.0, 1.0)) + }; + + let p_min = Point2i::new( + (full_resolution.x() as Float * crop.p_min.x()).ceil() as i32, + (full_resolution.y() as Float * crop.p_min.y()).ceil() as i32, + ); + let p_max = Point2i::new( + (full_resolution.x() as Float * crop.p_max.x()).ceil() as i32, + (full_resolution.y() as Float * crop.p_max.y()).ceil() as i32, + ); + + let mut pixel_bounds = Bounds2i::from_points(p_min, p_max); + + if pixel_bounds.is_empty() { + eprintln!("{}: Film crop window results in empty pixel bounds.", loc); + } + + let rad = filter.radius(); + let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32); + pixel_bounds = pixel_bounds.expand(expansion); + + let diagonal_mm = params.get_one_float("diagonal", 35.0); + let filename = params.get_one_string("filename", "pbrt.exr"); + + Self { + full_resolution, + pixel_bounds, + filter, + diagonal: diagonal_mm * 0.001, + sensor, + } + } +} + +pub trait FilmHost { + fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) { + let image = self.get_image(metadata, splat_scale); + image + .write(self.get_filename(), metadata) + .expect("Something") + } + fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { + let write_fp16 = true; + let format = if write_fp16 { + PixelFormat::F16 + } else { + PixelFormat::F32 + }; + + let channel_names = &["R", "G", "B"]; + + let pixel_bounds = self.base().pixel_bounds; + let resolution = Point2i::from(pixel_bounds.diagonal()); + + let n_clamped = Arc::new(AtomicUsize::new(0)); + let processed_rows: Vec> = (pixel_bounds.p_min.y()..pixel_bounds.p_max.y()) + .into_par_iter() + .map(|y| { + let n_clamped = Arc::clone(&n_clamped); + let mut row_data = Vec::with_capacity(resolution.x() as usize * 3); + for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() { + let p = Point2i::new(x, y); + let mut rgb = self.get_pixel_rgb(p, Some(splat_scale)); + let mut was_clamped = false; + if write_fp16 { + if rgb.r > 65504.0 { + rgb.r = 65504.0; + was_clamped = true; + } + if rgb.g > 65504.0 { + rgb.g = 65504.0; + was_clamped = true; + } + if rgb.b > 65504.0 { + rgb.b = 65504.0; + was_clamped = true; + } + } + if was_clamped { + n_clamped.fetch_add(1, Ordering::SeqCst); + } + row_data.push(rgb.r); + row_data.push(rgb.g); + row_data.push(rgb.b); + } + row_data + }) + .collect(); + + let mut image = Image::new(format, resolution, channel_names, SRGB); + let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); + + for (iy, row_data) in processed_rows.into_iter().enumerate() { + for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() { + let p_offset = Point2i::new(ix as i32, iy as i32); + let values = ImageChannelValues::from(rgb_chunk); + image.set_channels(p_offset, &rgb_desc, &values); + } + } + + let clamped_count = n_clamped.load(Ordering::SeqCst); + if clamped_count > 0 { + println!( + "{} pixel values clamped to maximum fp16 value.", + clamped_count + ); + } + + // self.base().pixel_bounds = pixel_bounds; + // self.base().full_resolution = resolution; + // self.colorspace = colorspace; + + image + } + fn get_filename(&self) -> &str; +} + +pub trait FilmFactory { + fn create( + name: &str, + params: &ParameterDictionary, + exposure_time: Float, + filter: Filter, + _camera_transform: Option, + loc: &FileLoc, + ) -> Result; +} + +impl FilmFactory for Film { + fn create( + name: &str, + params: &ParameterDictionary, + exposure_time: Float, + filter: Filter, + camera_transform: Option, + loc: &FileLoc, + ) -> Result { + match name { + "rgb" => { + let colorspace = params.color_space.as_ref().unwrap(); + let max_component_value = + params.get_one_float("maxcomponentvalue", Float::INFINITY); + let write_fp16 = params.get_one_bool("savefp16", true); + let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; + let film_base = FilmBase::create(params, filter, Some(sensor), loc); + Ok(RGBFilm::new( + film_base, + &colorspace, + max_component_value, + write_fp16, + )) + } + "gbuffer" => { + let colorspace = params.color_space.as_ref().unwrap(); + let max_component_value = + params.get_one_float("maxcomponentvalue", Float::INFINITY); + let write_fp16 = params.get_one_bool("savefp16", true); + let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; + let film_base = FilmBase::create(params, filter, Some(sensor), loc); + + let filename = params.get_one_string("filename", "pbrt.exr"); + if Path::new(&ilename).extension() != Some("exr".as_ref()) { + return Err(format!( + "{}: EXR is the only format supported by GBufferFilm", + loc + ) + .into()); + } + + let coords_system = params.get_one_string("coordinatesystem", "camera"); + let mut apply_inverse = false; + let camera_transform = camera_transform + .ok_or_else(|| "GBufferFilm requires a camera_transform".to_string())?; + let output_from_render = if coords_system == "camera" { + apply_inverse = true; + camera_transform.render_from_camera + } else if coords_system == "world" { + AnimatedTransform::from_transform(&camera_transform.world_from_render) + } else { + return Err(format!( + "{}: unknown coordinate system for GBufferFilm. (Expecting camera + or world", + loc + ) + .into()); + }; + + Ok(GBufferFilm::new( + &film_base, + &output_from_render, + apply_inverse, + colorspace, + max_component_value, + write_fp16, + )) + } + "spectral" => { + let colorspace = params.color_space.as_ref().unwrap(); + let max_component_value = + params.get_one_float("maxcomponentvalue", Float::INFINITY); + let write_fp16 = params.get_one_bool("savefp16", true); + let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; + let film_base = FilmBase::create(params, filter, Some(sensor), loc); + + let filename = params.get_one_string("filename", "pbrt.exr"); + if Path::new(&filename).extension() != Some("exr".as_ref()) { + return Err(format!( + "{}: EXR is the only format supported by GBufferFilm", + loc + ) + .into()); + } + + let n_buckets = params.get_one_int("nbuckets", 16) as usize; + let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float); + let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float); + if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float { + return Err(format!( + "{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX", + loc + )); + } + + Ok(SpectralFilm::new( + &film_base, + lambda_min, + lambda_max, + n_buckets, + colorspace, + max_component_value, + write_fp16, + )) + } + _ => Err(format!("Film type '{}' unknown at {}", name, loc)), + } + } +} diff --git a/src/core/filter.rs b/src/core/filter.rs new file mode 100644 index 0000000..7b4b131 --- /dev/null +++ b/src/core/filter.rs @@ -0,0 +1,48 @@ +use shared::filter::Filter; +use shared::filters::*; + +pub trait FilterFactory { + fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result; +} + +impl FilterFactory for Filter { + fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result { + match name { + "box" => { + let xw = params.get_one_float("xradius", 0.5); + let yw = params.get_one_float("yradius", 0.5); + let filter = BoxFilter::new(Vector2f::new(xw, yw)); + Ok(Filter::Box(filter)) + } + "gaussian" => { + let xw = params.get_one_float("xradius", 1.5); + let yw = params.get_one_float("yradius", 1.5); + let sigma = params.get_one_float("sigma", 0.5); + let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); + Ok(Filter::Gaussian(filter)) + } + "mitchell" => { + let xw = params.get_one_float("xradius", 2.); + let yw = params.get_one_float("yradius", 2.); + let b = params.get_one_float("B", 1. / 3.); + let c = params.get_one_float("C", 1. / 3.); + let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c); + Ok(Filter::Mitchell(filter)) + } + "sinc" => { + let xw = params.get_one_float("xradius", 4.); + let yw = params.get_one_float("yradius", 4.); + let tau = params.get_one_float("tau", 3.); + let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau); + Ok(Filter::LanczosSinc(filter)) + } + "triangle" => { + let xw = params.get_one_float("xradius", 2.); + let yw = params.get_one_float("yradius", 2.); + let filter = TriangleFilter::new(Vector2f::new(xw, yw)); + Ok(Filter::Triangle(filter)) + } + _ => Err(format!("Film type '{}' unknown at {}", name, loc)), + } + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index fb2ca04..fb499ad 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,2 +1,4 @@ +pub mod film; +pub mod filter; pub mod scene; pub mod texture; diff --git a/src/core/scene.rs b/src/core/scene.rs index 647314c..cb10807 100644 --- a/src/core/scene.rs +++ b/src/core/scene.rs @@ -1,3 +1,4 @@ +use crate::core::filter::FilterFactory; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::{normalize_utf8, resolve_filename}; use parking_lot::Mutex; diff --git a/src/core/texture.rs b/src/core/texture.rs index 0c3747a..cccca3b 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -1,9 +1,9 @@ use crate::textures::*; -use crate::utils::error::FileLoc; +use crate::utils::FileLoc; use crate::utils::mipmap::MIPMapFilterOptions; -use crate::utils::parameters::TextureParameterDictionary; +use crate::utils::{FileLoc, TextureParameterDictionary}; +use shared::textures::*; use shared::utils::Transform; -use shared::images:: use std::sync::{Arc, Mutex, OnceLock}; #[enum_dispatch(FloatTextureTrait)] @@ -23,6 +23,33 @@ pub enum FloatTexture { Wrinkled(WrinkledTexture), } +impl FloatConstantTexture { + pub fn create( + name: &str, + render_from_texture: &Transform, + params: &TextureParameterDictionary, + loc: &FileLoc, + ) -> Result { + Self::new(params.get_one_float("value", 1.0)) + } +} + +impl FloatBilerpTexture { + pub fn create( + name: &str, + render_from_texture: &Transform, + params: &TextureParameterDictionary, + loc: &FileLoc, + ) -> Result { + let mapping = TextureMapping2D::create(params, render_from_texture, loc); + let v00 = params.get_one_float("v00", 0.); + let v01 = params.get_one_float("v01", 1.); + let v10 = params.get_one_float("v10", 0.); + let v11 = params.get_one_float("v11", 1.); + Self::new(mapping, v00, v01, v10, v11) + } +} + impl FloatTexture { pub fn create( name: &str, @@ -223,3 +250,41 @@ struct TexInfo { wrap_mode: WrapMode, encoding: ColorEncoding, } + +pub trait TextureEvaluator: Send + Sync { + fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float; + fn evaluate_spectrum( + &self, + tex: &SpectrumTexture, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum; + + fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool; +} + +#[derive(Copy, Clone, Default)] +pub struct UniversalTextureEvaluator; + +impl TextureEvaluator for UniversalTextureEvaluator { + fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float { + tex.evaluate(ctx) + } + + fn evaluate_spectrum( + &self, + tex: &SpectrumTexture, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + tex.evaluate(ctx, lambda) + } + + fn can_evaluate( + &self, + _float_textures: &[&FloatTexture], + _spectrum_textures: &[&SpectrumTexture], + ) -> bool { + true + } +} diff --git a/src/textures/bilerp.rs b/src/textures/bilerp.rs deleted file mode 100644 index e619ed1..0000000 --- a/src/textures/bilerp.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::core::texture::{ - FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext, TextureMapping2D, -}; -use crate::spectra::{SampledSpectrum, SampledWavelengths}; - -#[derive(Debug, Clone)] -pub struct FloatBilerpTexture { - mapping: TextureMapping2D, - v00: Float, - v01: Float, - v10: Float, - v11: Float, -} - -impl FloatBilerpTexture { - pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self { - Self { - mapping, - v00, - v01, - v10, - v11, - } - } - - pub fn create( - render_from_texture: &Transform, - params: &TextureParameterDictionary, - loc: &FileLoc, - ) -> Self { - let mapping = TextureMapping2D::create(params, render_from_texture, loc); - let v00 = params.get_one_float("v00", 0.); - let v01 = params.get_one_float("v01", 1.); - let v10 = params.get_one_float("v10", 0.); - let v11 = params.get_one_float("v11", 1.); - Self::new(mapping, v00, v01, v10, v11) - } -} - -impl FloatTextureTrait for FloatBilerpTexture {} - -#[derive(Clone, Debug)] -pub struct SpectrumBilerpTexture; -impl SpectrumTextureTrait for SpectrumBilerpTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} diff --git a/src/textures/checkerboard.rs b/src/textures/checkerboard.rs deleted file mode 100644 index a584ea7..0000000 --- a/src/textures/checkerboard.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext}; -use crate::spectra::{SampledSpectrum, SampledWavelengths}; - -#[derive(Debug, Clone)] -pub struct FloatCheckerboardTexture; -impl FloatTextureTrait for FloatCheckerboardTexture {} - -#[derive(Clone, Debug)] -pub struct SpectrumCheckerboardTexture; -impl SpectrumTextureTrait for SpectrumCheckerboardTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} diff --git a/src/textures/constant.rs b/src/textures/constant.rs deleted file mode 100644 index 4c9e5c0..0000000 --- a/src/textures/constant.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::Float; -use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext}; -use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum}; - -#[derive(Debug, Clone)] -pub struct FloatConstantTexture { - value: Float, -} - -impl FloatConstantTexture { - pub fn new(value: Float) -> Self { - Self { value } - } - - pub fn create( - _render_from_texture: &Transform, - params: &TextureParameterDictionary, - _loc: &FileLoc, - ) -> Self { - Self::new(params.get_one_float("value", 1.0)) - } -} - -impl FloatTextureTrait for FloatConstantTexture { - fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { - self.value - } -} - -#[derive(Clone, Debug)] -pub struct RGBConstantTexture; -impl SpectrumTextureTrait for RGBConstantTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} - -#[derive(Clone, Debug)] -pub struct RGBReflectanceConstantTexture; -impl SpectrumTextureTrait for RGBReflectanceConstantTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} -#[derive(Clone, Debug)] -pub struct SpectrumConstantTexture { - value: Spectrum, -} - -impl SpectrumConstantTexture { - pub fn new(value: Spectrum) -> Self { - Self { value } - } -} - -impl SpectrumTextureTrait for SpectrumConstantTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum { - self.value.sample(lambda) - } -} diff --git a/src/textures/dots.rs b/src/textures/dots.rs deleted file mode 100644 index b75ee9a..0000000 --- a/src/textures/dots.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[derive(Clone, Debug)] -pub struct SpectrumDotsTexture; -impl SpectrumTextureTrait for SpectrumDotsTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} - -#[derive(Debug, Clone)] -pub struct FloatDotsTexture; -impl FloatTextureTrait for FloatDotsTexture {} diff --git a/src/textures/fbm.rs b/src/textures/fbm.rs deleted file mode 100644 index 1bca190..0000000 --- a/src/textures/fbm.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[derive(Debug, Clone)] -pub struct FBmTexture; -impl FloatTextureTrait for FBmTexture {} diff --git a/src/textures/image.rs b/src/textures/image.rs index 32aa64c..ab9f474 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -1,9 +1,10 @@ -use crate::texture::ImageTextureBase; +use crate::core::texture::ImageTextureBase; +use crate::utils::{FileLoc, TextureParameterDictionary}; #[derive(Clone, Debug)] pub struct SpectrumImageTexture { - base: ImageTextureBase, - spectrum_type: SpectrumType, + pub base: ImageTextureBase, + pub spectrum_type: SpectrumType, } impl SpectrumImageTexture { diff --git a/src/textures/marble.rs b/src/textures/marble.rs deleted file mode 100644 index 9e60be1..0000000 --- a/src/textures/marble.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Clone, Debug)] -pub struct MarbleTexture; -impl SpectrumTextureTrait for MarbleTexture { - fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { - todo!() - } -} diff --git a/src/textures/mix.rs b/src/textures/mix.rs index bac9346..c565da9 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -1,3 +1,11 @@ +use crate::core::texture::{ + FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, +}; +use crate::utils::{FileLoc, TextureParameterDictionary}; +use shared::core::geometry::Vector3f; +use shared::utils::Transform; +use std::sync::Arc; + #[derive(Debug, Clone)] pub struct FloatMixTexture { tex1: Arc, diff --git a/src/textures/ptex.rs b/src/textures/ptex.rs index 6d3ebb3..e80202d 100644 --- a/src/textures/ptex.rs +++ b/src/textures/ptex.rs @@ -1,9 +1,155 @@ +use crate::utils::{FileLoc, TextureParameterDictionary}; +use shared::core::texture::{SpectrumType, TextureEvalContext}; +use shared::spectra::ColorEncoding; +use shared::spectra::color::RGB; + +use ptex::Cache; +use ptex_sys::ffi; +use std::sync::OnceLock; + +static PTEX_CACHE: OnceLock = OnceLock::new(); + +fn get_ptex_cache() -> &'static Cache { + PTEX_CACHE.get_or_init(|| { + let max_files = 100; + let max_mem = 1 << 32; + let premultiply = true; + Cache::new(max_files, max_mem, premultiply) + }) +} + #[derive(Debug, Clone)] -pub struct FloatPtexTexture; -impl FloatTextureTrait for FloatPtexTexture {} +pub struct PtexTextureBase { + pub valid: bool, + pub filename: String, + pub encoding: ColorEncoding, + pub scale: Float, +} + +impl PtexTextureBase { + pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self { + let cache = get_ptex_cache(); + + // Attempt to get the texture to verify it exists and is valid + let (valid, num_channels) = match cache.get(&filename) { + Ok(tex) => { + let nc = tex.num_channels(); + (nc == 1 || nc == 3, nc) + } + Err(e) => { + log::error!("Ptex Error for {}: {}", filename, e); + (false, 0) + } + }; + + if !valid && num_channels != 0 { + log::error!( + "{}: only 1 and 3 channel ptex textures are supported", + filename + ); + } + + Self { + filename, + encoding, + scale, + valid, + } + } + + pub fn sample_texture(ctx: &TextureEvalContext) -> Option { + if !self.valid { + return None; + } + let mut result = [0.0; 3]; + let nc = self.ptex_handle.eval( + &mut result, + ctx.face_index, + ctx.uv[0], + ctx.uv[1], + ctx.dudx, + ctx.dvdx, + ctx.dudy, + ctx.dvdy, + ); + let cache = get_ptex_cache(); + let texture = match cache.get(&self.filename) { + Ok(t) => t, + Err(e) => { + log::error!("Ptex cache lookup failed for {}: {}", self.filename, e); + return None; + } + }; + + let nc = texture.num_channels(); + let mut result = [0.0f32; 4]; + unsafe { + let opts = ffi::PtexFilter_Options { + filter: ffi::FilterType::f_bspline, + lerp: 1, + sharpness: 0.0, + noedgeblend: 0, + __structSize: std::mem::size_of::() as i32, + }; + + // Get the raw filter from the low-level FFI + // Assuming your 'texture' can provide the raw C++ pointer + let filter_ptr = ffi::PtexFilter_getFilter(texture.as_ptr(), &opts); + if filter_ptr.is_null() { + return None; + } + + // Evaluate + (*filter_ptr).eval( + result.as_mut_ptr(), + 0, // first channel + texture.num_channels(), + ctx.face_index as i32, + ctx.uv[0], + ctx.uv[1], + ctx.dudx, + ctx.dvdx, + ctx.dudy, + ctx.dvdy, + ); + + // Crucial: Manually release the C++ object + (*filter_ptr).release(); + } + + let mut rgb = RGB::new(result[0], result[1], result[2]); + + if self.encoding != ColorEncoding::Linear { + // Convert to 8-bit, process, and convert back + let r8 = (rgb.r * 255.0 + 0.5).clamp(0.0, 255.0) as u8; + let g8 = (rgb.g * 255.0 + 0.5).clamp(0.0, 255.0) as u8; + let b8 = (rgb.b * 255.0 + 0.5).clamp(0.0, 255.0) as u8; + + rgb = self.encoding.to_linear_rgb([r8, g8, b8]); + } + + Some(rgb * self.scale) + } +} + +#[derive(Debug, Clone)] +pub struct FloatPtexTexture { + pub base: PtexTextureBase, +} + +// impl FloatPtexTexture { +// pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { +// let +// +// } +// } #[derive(Clone, Debug)] -pub struct SpectrumPtexTexture; +pub struct SpectrumPtexTexture { + pub base: PtexTextureBase, + pub spectrum_type: SpectrumType, +} + impl SpectrumTextureTrait for SpectrumPtexTexture { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() diff --git a/src/textures/scale.rs b/src/textures/scale.rs index 20c8bb8..98eb25e 100644 --- a/src/textures/scale.rs +++ b/src/textures/scale.rs @@ -1,3 +1,11 @@ +use crate::core::texture::{ + FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, +}; +use crate::utils::{FileLoc, TextureParameterDictionary}; +use shared::core::texture::TextureEvalContext; +use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use shared::utils::Transform; + #[derive(Debug, Clone)] pub struct FloatScaledTexture { tex: Arc, @@ -18,14 +26,14 @@ impl FloatScaledTexture { let mut scale = params.get_float_texture("scale", 1.); for _ in 0..2 { - if let FloatTexture::FloatConstant(c_tex) = &*scale { + if let FloatTexture::Constant(c_tex) = &*scale { let cs = c_tex.value; if cs == 1.0 { return (*tex).clone(); - } else if let FloatTexture::FloatImage(img_tex) = &*tex { + } else if let FloatTexture::Image(img_tex) = &*tex { let mut image_copy = img_tex.clone(); image_copy.base.multiply_scale(cs); - return FloatTexture::FloatImage(image_copy).into(); + return FloatTexture::Image(image_copy).into(); } } @@ -48,8 +56,8 @@ impl FloatTextureTrait for FloatScaledTexture { #[derive(Clone, Debug)] pub struct SpectrumScaledTexture { - tex: Box, - scale: Box, + tex: Arc, + scale: Arc, } impl SpectrumTextureTrait for SpectrumScaledTexture { diff --git a/src/textures/windy.rs b/src/textures/windy.rs deleted file mode 100644 index 7dfa88d..0000000 --- a/src/textures/windy.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[derive(Debug, Clone)] -pub struct WindyTexture; -impl FloatTextureTrait for WindyTexture {} diff --git a/src/textures/wrinkled.rs b/src/textures/wrinkled.rs deleted file mode 100644 index eadcc26..0000000 --- a/src/textures/wrinkled.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[derive(Debug, Clone)] -pub struct WrinkledTexture; -impl FloatTextureTrait for WrinkledTexture {} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f09b2cf..72750af 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,5 +5,9 @@ pub mod mipmap; pub mod parameters; pub mod parser; +pub use error::FileLoc; pub use file::{read_float_file, resolve_filename}; +pub use parameters::{ + ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary, +}; pub use strings::*;